From d033670920ec1ddc15713e6d4c0ac959f6da0026 Mon Sep 17 00:00:00 2001 From: Eric Payton Date: Mon, 11 Aug 2025 06:54:44 -0400 Subject: [PATCH 1/4] Update to python3 --- CHANGES.txt | 2 + LICENSE.txt | 14 + MANIFEST.in | 2 + README.txt | 52 + bin/DeGraefHenry.py | 111 + bin/TESTS.py | 98 + bin/TEST_ROTATION-CONVENTIONS.py | 61 + bin/TUTORIAL.py | 169 ++ colormaps.py | 1317 ----------- __init__.py => cryspy/__init__.py | 17 +- {cio => cryspy/cio}/__init__.py | 23 +- cryspy/cio/loadGFtype1.py | 151 ++ {cio => cryspy/cio}/loadang.py | 476 ++-- {cio => cryspy/cio}/loadctf.py | 242 +- {cio => cryspy/cio}/loadebsd.py | 45 +- {cio => cryspy/cio}/loadosc.py | 3406 ++++++++++++++--------------- {cio => cryspy/cio}/writeang.py | 122 +- ebsd.py => cryspy/ebsd.py | 1072 ++++----- cryspy/orrl.py | 3270 +++++++++++++++++++++++++++ rot.py => cryspy/rot.py | 942 ++++---- tx.py => cryspy/tx.py | 601 ++--- util.py => cryspy/util.py | 2427 ++++++++------------ vis.py => cryspy/vis.py | 1440 ++++++------ cryspy/vis.zip | Bin 0 -> 14957 bytes xtal.py => cryspy/xtal.py | 461 ++-- docs/Makefile | 153 ++ docs/README.txt | 51 + docs/conf.py | 286 +++ docs/cryspy.rst | 34 + docs/faq.rst | 45 + docs/generate_docs.py | 3 + docs/index.rst | 50 + docs/installation.rst | 58 + orrl.py | 1127 ---------- rmpyc.py | 11 - setup.py | 27 + start.py | 3 + 37 files changed, 10153 insertions(+), 8216 deletions(-) create mode 100755 CHANGES.txt create mode 100755 LICENSE.txt create mode 100755 MANIFEST.in create mode 100755 README.txt create mode 100755 bin/DeGraefHenry.py create mode 100755 bin/TESTS.py create mode 100755 bin/TEST_ROTATION-CONVENTIONS.py create mode 100755 bin/TUTORIAL.py delete mode 100644 colormaps.py rename __init__.py => cryspy/__init__.py (64%) mode change 100644 => 100755 rename {cio => cryspy/cio}/__init__.py (65%) mode change 100644 => 100755 create mode 100755 cryspy/cio/loadGFtype1.py rename {cio => cryspy/cio}/loadang.py (80%) mode change 100644 => 100755 rename {cio => cryspy/cio}/loadctf.py (87%) mode change 100644 => 100755 rename {cio => cryspy/cio}/loadebsd.py (61%) mode change 100644 => 100755 rename {cio => cryspy/cio}/loadosc.py (97%) mode change 100644 => 100755 rename {cio => cryspy/cio}/writeang.py (97%) mode change 100644 => 100755 rename ebsd.py => cryspy/ebsd.py (90%) mode change 100644 => 100755 create mode 100755 cryspy/orrl.py rename rot.py => cryspy/rot.py (85%) mode change 100644 => 100755 rename tx.py => cryspy/tx.py (86%) mode change 100644 => 100755 rename util.py => cryspy/util.py (52%) mode change 100644 => 100755 rename vis.py => cryspy/vis.py (78%) mode change 100644 => 100755 create mode 100755 cryspy/vis.zip rename xtal.py => cryspy/xtal.py (87%) mode change 100644 => 100755 create mode 100755 docs/Makefile create mode 100755 docs/README.txt create mode 100755 docs/conf.py create mode 100755 docs/cryspy.rst create mode 100755 docs/faq.rst create mode 100755 docs/generate_docs.py create mode 100755 docs/index.rst create mode 100755 docs/installation.rst delete mode 100644 orrl.py delete mode 100644 rmpyc.py create mode 100755 setup.py create mode 100755 start.py diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100755 index 0000000..5e0339d --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,2 @@ +v00.00.00, 2012-04-16 -- pre-init dev beta +v00.00.01, 2016-06-27 -- AFRL commmit \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100755 index 0000000..1d137ec --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,14 @@ +''' +Portions of this package written after August 2015 were produced by Eric Payton as part of the +in-house research of the Metallic Materials and Processes Research Team, Metals Branch, +Structural Materials Division, Materials and Manufacturing Directorate, Air Force Research Laboratory. + +As a work product prepared by an employee of the United States government during the +course of that employee's official duties, those portions of this work are not entitled to domestic +copyright protection according to section 105 of the Copyright Act, and is thus part of the public domain. + +Portions of this package written prior to August 2015 are (C) Eric Payton. + +Portions of this package included from MTEX are (C) Ralf Hielscher and Florian Bachmann and are +covered by GPL2 license. +''' \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100755 index 0000000..35d6621 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt +recursive-include docs *.txt \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100755 index 0000000..20c4b1b --- /dev/null +++ b/README.txt @@ -0,0 +1,52 @@ +============================================================================== +Cryspy: Computational Crystallography in Python:: +*A Python Toolbox for EBSD Data Analysis* +============================================================================== + +Cryspy is a toolbox for computational crystallography in Python. +It intends to provide a set of open source tools for +visualization, analysis, and postprocessing of EBSD data. + +============================================================================== + +COMPONENTS + +* io :: modules for import and export of EBSD data +* rot :: modules for rotation representation, manipulation, and conversion +* vis :: modules for data visualization +* ebsd :: modules for EBSD data analysis and manipulation +* xtal :: modules for crystallography and symmetry calculations +* util :: utilities + +============================================================================== + +CONTENT HIGHLIGHTS + +io +------------------------------------------------------------------------------ +* loadang:: support for loading TSL *.ang files + +rot +------------------------------------------------------------------------------ +* angax :: class for the angle/axis convention +* bunge :: class for Bunge Euler angles +* quat :: class for quaternion representations +* rodri :: class for Rodrigues vector representations +* tvec :: class for transformation vectors (reshaped rotation matrices) + +xtal +------------------------------------------------------------------------------ +* lattvec :: class for representing lattice vectors +* miller :: class for representing lattice plane normals +* unitcell:: class for representing unit cells +* rotsymm :: class for rotational symmetry objects + +vis +------------------------------------------------------------------------------ +* stereoproj :: class for plotting stereographic projections +* eaproj :: class for plotting equal area projections + +util +------------------------------------------------------------------------------ +* rationalize :: function for turning fractional vectors into rational indices +* sigdec :: function for rounding to n significant digits diff --git a/bin/DeGraefHenry.py b/bin/DeGraefHenry.py new file mode 100755 index 0000000..21559d5 --- /dev/null +++ b/bin/DeGraefHenry.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 15 16:59:33 2013 + +@author: epayton +""" + +import ovlib as ov +from numpy import pi + +# 4.3.1 +uc = ov.unitcell(a=3., b=4., c=6., beta=120.) +bodydiag = ov.lattvec(1,1,1) +print bodydiag.length(uc) # result should be 6.557 + +# 4.3.2 +uc = ov.unitcell(a=2., b=2., c=3.) +atom1 = ov.lattsite(1./2., 1./3., 1./4.) +atom2 = ov.lattsite(1./3., 1./2., 3./4.) +print atom1.distance(atom2, uc) # result should be 1.572 + +# 4.3.3 +uc = ov.unitcell() +atom1 = ov.lattsite(1./2., 1./2., 0.) +atom2 = ov.lattsite(1./2., 0., 1./2.) +origin = ov.lattsite(0., 0., 0.) +print atom1.angle(atom2, uc, origin)*180./pi # result should be 1.572 + +# 4.3.4 +uc = ov.unitcell(a=4., b=6., c=5., beta=120.) +vec1 = ov.lattvec(1., 0., 1.) +vec2 = ov.lattvec(-2., 0., 1.) +print vec1.angle(vec2, uc)*180./pi + +# 6.5(i) +uc = ov.unitcell(a=2., b=2., c=2.) +plane = ov.miller(1., 1., 0.) +print plane.dspacing(uc) + +# 6.5(ii) +uc = ov.unitcell(a=3., b=4., c=6., gamma=120.) +plane = ov.miller(1., 1., 1.) +print plane.dspacing(uc) + +# 6.5(iii) +uc = ov.unitcell(a=4., b=6., c=5., beta=120.) +plane1 = ov.miller( 1., 0., 1.) +plane2 = ov.miller(-2., 0., 1.) +print plane1.angle(plane2, uc)*180./pi + +# Table 7.2 +uc = ov.unitcell(beta=45.) +u = [1., 0., 0., 1., 1., 0., 1.] +v = [0., 1., 0., 1., 0., 1., 1.] +w = [0., 0., 1., 0., 1., 1., 1.] +vec1 = ov.lattvec(u, v, w) +cart1 = vec1.to_cartesian(uc) +xproj1, yproj1, hemi1 = ov.stereotrans(cart1) + +# Table 7.2 +vec2 = ov.miller(u, v, w) +cart2 = vec2.to_cartesian(uc) +xproj2, yproj2, hemi2 = ov.stereotrans(cart2) + +# Fig 7.11(a) +sg = ov.stereoproj() # initialize the stereographic projection +sg.add_lattvec(vec1,uc) # add our lattice vectors to the projection +sg.add_lattveclabels(vec1,uc) +sg.grid_greatcircles() +sg.grid_smallcircles() +sg.grid_spokes(degreestep=45) + +from matplotlib.font_manager import FontProperties +font=FontProperties() +font.set_size('large') +font.set_style('italic') +sg.add_eastlabel(fontproperties=font) +sg.add_southlabel(fontproperties=font) +sg.add_centerlabel(fontproperties=font) +sg.add_coordinatereadout(uc) + +# Fig 7.11(b) +sg = ov.stereoproj() # initialize the stereographic projection +sg.add_miller(vec2,uc) # add our lattice vectors to the projection +sg.add_millerlabels(vec2,uc) +sg.grid_wulffnet() + +from matplotlib.font_manager import FontProperties +font=FontProperties() +font.set_size('large') +font.set_style('italic') +sg.add_eastlabel(fontproperties=font) +sg.add_southlabel(fontproperties=font) +sg.add_centerlabel(fontproperties=font) +sg.add_coordinatereadout(uc) +# NOTE THAT (111) and (110) are in the wrong places in the figure in the text! + +## Fig 7.11(b) as equal area proj +sg = ov.eaproj() # initialize the stereographic projection +sg.add_miller(vec2,uc) # add our lattice vectors to the projection +sg.add_millerlabels(vec2,uc) +sg.grid_spokes() +# +from matplotlib.font_manager import FontProperties +font=FontProperties() +font.set_size('large') +font.set_style('italic') +sg.add_eastlabel(fontproperties=font) +sg.add_southlabel(fontproperties=font) +sg.add_centerlabel(fontproperties=font) +sg.add_coordinatereadout(uc) \ No newline at end of file diff --git a/bin/TESTS.py b/bin/TESTS.py new file mode 100755 index 0000000..47a45bd --- /dev/null +++ b/bin/TESTS.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 11 11:18:08 2012 + +@author: epayton +""" + +from ovlib import * +from numpy import array, pi + +################################################################################ + +print "\n check that we can initialize" +print quat() +print tvec() +print bunge() +print rodri() + + +################################################################################ +print '-----------------' +print "\n check that we can construct individual quaternions" + +# check that we can construct with a single rotation given as a tuple +q1a=quat(0.6070,-0.7043,0.0634,-0.3627) +print q1a + +# check that we can construct with a tuple of lists +q2a=quat([0.6070],[-0.7043],[0.0634],[-0.3627]) +print q2a + +# check that we can construct with an array +q3a=quat(array([0.6070]),array([-0.7043]),array([0.0634]),array([-0.3627])) +print q3a + +################################################################################ +print '-----------------' +print "\n check that we can construct multiple quaternions" + +# check that we can construct with multiple rotations given as tuples +a=0.6070,0.9688 +b=-0.7043,0.1176 +c=0.0634,0.0263 +d=-0.3627,0.2166 +q1b=quat(a,b,c,d) +print q1b + +# check that we can construct with multiple values in a tuple of lists +a=[0.6070,0.9688] +b=[-0.7043,0.1176] +c=[0.0634,0.0263] +d=[-0.3627,0.2166] +q2b=quat(a,b,c,d) +print q2b + +# check that we can construct with a tuple of arrays +a=array([0.6070,0.9688]) +b=array([-0.7043,0.1176]) +c=array([0.0634,0.0263]) +d=array([-0.3627,0.2166]) +q3b=quat(a,b,c,d) +print q3b + +################################################################################ +print '-----------------' +print "\n check that we can convert quaternions to tvecs" + +tq1=tvec.from_quat(q1a) +print tq1 + +tq2=tvec.from_quat(q1b) +print tq2 + +################################################################################ +print '-----------------' +print "\n check that we can convert tvecs to quats" + +qt1=quat.from_tvec(tq1) +print qt1 + +qt2=quat.from_tvec(tq2) +print qt2 + +################################################################################ +print '-----------------' +print "\n check that we can convert Bunge Euler to quats" + +be1=quat.from_bunge(bunge(phi1=pi,PHI=pi/7.,phi2=0.0)) + +################################################################################ +print '-----------------' +print "\n check that we can get symmetries" + +r1 = rotationelements('m-3m') # rotation symmetry elements +r2 = pointgroupelements('6/mmm') # point group elements +cs = rotsymm('m3') # rotational symmetry class +print cs + diff --git a/bin/TEST_ROTATION-CONVENTIONS.py b/bin/TEST_ROTATION-CONVENTIONS.py new file mode 100755 index 0000000..1e04695 --- /dev/null +++ b/bin/TEST_ROTATION-CONVENTIONS.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Jul 04 17:05:51 2013 + +@author: epayton +""" + +import ovlib as ov + +uc = ov.unitcell() + +# From Lecture notes for CMU 27-750, Fall 2009, "Analysis of EBSD Data (L17)" +# by B. El-Dasher, A.D. Rollett, G.S. Rohrer, P.N. Kalu, p. 35: +# "A simple test of the frames used for Euler angles is to have the softwares +# [sic] plot pole figures for a single orientation..." + +''' +Check if Euler angles are working properly in passive convention +''' +pf = ov.stereoproj() +m = ov.miller(1,0,0) +pf.add_miller(m.rotate(ov.rmat.from_bunge(ov.bunge(0.2,0,0))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) + +# the red marker should appear counterclockwise from the blue +m = ov.miller(0,0,1) +pf.add_miller(m.rotate(ov.rmat.from_bunge(ov.bunge(0.0,0.2,0))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) + +''' +Check if ang/ax is working properly in passive convention +''' +pf = ov.stereoproj() +# the red marker should appear in same directions as above: +m = ov.miller(1,0,0) +pf.add_miller(m.rotate(ov.rmat.from_angax(ov.angax(0.2,0,0,1))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) +m = ov.miller(0,0,1) +pf.add_miller(m.rotate(ov.rmat.from_angax(ov.angax(0.2,1,0,0))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) + +''' +Check if quat is working properly in passive convention +''' +# the red marker should appear in same directions as above: +pf = ov.stereoproj() +m = ov.miller(1,0,0) +pf.add_miller(m.rotate(ov.quat.from_angax(ov.angax(0.2,0,0,1))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) +m = ov.miller(0,0,1) +pf.add_miller(m.rotate(ov.quat.from_angax(ov.angax(0.2,1,0,0))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) + +pf = ov.stereoproj() +m = ov.miller(1,0,0) +pf.add_miller(m.rotate(ov.quat.from_bunge(ov.bunge(0.2,0,0))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) +m = ov.miller(0,0,1) +pf.add_miller(m.rotate(ov.quat.from_bunge(ov.bunge(0,0.2,0))), uc, uppermarkerfacecolor='r', lowermarkerfacecolor='r') +pf.add_miller(m, uc) + diff --git a/bin/TUTORIAL.py b/bin/TUTORIAL.py new file mode 100755 index 0000000..e631d08 --- /dev/null +++ b/bin/TUTORIAL.py @@ -0,0 +1,169 @@ +import ovlib as ov +import matplotlib.pyplot as plt + +fnom = 'projtutorial' +ftyp = '.png' +fnum = 0 + +''' PROJECTION PLOTTING TUTORIAL ''' + + +''' +To plot stereographic projections using ovlib, you need to import the OV +library, define a unit cell, and define either some crystal planes or lattice +vectors to plot. +''' + +uc = ov.unitcell() # default with no arguments is cubic +u = [1, 0, 0, 1, 1, 0, 1] +v = [0, 1, 0, 1, 0, 1, 1] +w = [0, 0, 1, 0, 1, 1, 1] +v = ov.lattvec(u,v,w) # define lattice vectors +sg1 = ov.stereoproj() # initialize the stereographic projection +sg1.add_lattvec(v,uc) # add our lattice vectors to the projection + +''' +Running this series of commands within Spyder produces the following figure: +''' +fnum += 1 +plt.savefig(fnom+str(fnum).zfill(2)+ftyp) + + +''' We can add the standard grid showing the intersection of {110} and +{100}-type planes by then executing the following: +''' + +sg1.grid_standard(uc) +fnum += 1 +plt.savefig(fnom+str(fnum).zfill(2)+ftyp) + +''' +Let's now jump to a more complicated example, comparing the locations of the +same poles in the cubic and monoclinic systems: +''' +plt.close('all') +from pylab import close, figure, subplot, show +close('all') +u = [1, 0, 0, 1, 1, 0, 1] +v = [0, 1, 0, 1, 0, 1, 1] +w = [0, 0, 1, 0, 1, 1, 1] +v = ov.lattvec(u,v,w) +fig1 = figure(1) +sub1 = subplot(121) +sg1 = ov.stereoproj(figure=fig1,subplot=sub1) +uc1 = ov.unitcell() # default is cubit +sg1.add_lattvec(v,uc1) + +sub2 = subplot(122) +sg2 = ov.stereoproj(figure=fig1,subplot=sub2) +uc2 = ov.unitcell(beta=45) # monoclinic with a=b=c, alpha=gamma=90, beta=45 +sg2.add_lattvec(v,uc2) + +''' +This produces a figure with two subplots. +''' +show() +fnum += 1 +plt.savefig(fnom+str(fnum).zfill(2)+ftyp) + +''' +The angles between the lattice directions can now be compared by adding Wulff +nets. +''' +sg1.grid_wulffnet() +sg2.grid_wulffnet() +show() +fnum += 1 +plt.savefig(fnom+str(fnum).zfill(2)+ftyp) + +''' +We can rotate the Wulff net by adding the keyword "rotation." We can also plot +non-standard projections by defining the center, south, and east directions. We +can label these directions using add_eastlabel, add_normallabel, and +add_northlabel. The exact locations of the labels can be controlled using x and +y keyword arguments. + +We can apply additional grid types, including spokes, rings, great circles, and +small circles. Great circles and spokes can cause a crowded appearance around +poles, and this can be reduced using the poleclip keyword argument. Spokes, +rings, great circles, and small circles can also be rotated using the keyword +"rotation." + +We can also get a readout of the directions and the planes that correspond to +where our cursor is in the figure using the add_coordinatereadout(uc) command. + +Our figures can get correspondingly more complex: +''' + +close('all') +from matplotlib.font_manager import FontProperties +u = [1, 0, 0, 1, 1, 0, 1] +v = [0, 1, 0, 1, 0, 1, 1] +w = [0, 0, 1, 0, 1, 1, 1] +v = ov.lattvec(u,v,w) +fig1 = figure(1) +sub1 = subplot(121) +sg1 = ov.stereoproj(figure=fig1, subplot=sub1, center=[1,1,1],south=[-1,-1,2],\ + east=[-1,1,0]) +uc1 = ov.unitcell() # default is cubit +sg1.add_lattvec(v,uc1) +sg1.add_lattveclabels(v,uc1) +sg1.grid_wulffnet(rotation=60) +font1=FontProperties() +font1.set_size('large') +font1.set_style('italic') +sg1.add_eastlabel(fontproperties=font1) +sg1.add_southlabel(fontproperties=font1) +sg1.add_centerlabel(fontproperties=font1) +sg1.add_coordinatereadout(uc1) +sub2 = subplot(122) +sg2 = ov.stereoproj(figure=fig1,subplot=sub2, center=[1,1,1],south=[-1,-1,2],\ + east=[-1,1,0]) +uc2 = ov.unitcell(beta=45) # monoclinic with a=b=c, alpha=gamma=90, beta=45 +sg2.add_lattvec(v,uc2,lowermarker='d',lowermarkerfacecolor='g') +sg2.add_lattveclabels(v,uc2) +sg2.grid_rings(degreestep=5) +sg2.grid_spokes(degreestep=5,poleclip=5) +sg2.add_lattveclabels(v,uc2) +font2=FontProperties() +font2.set_family('monospace') +font2.set_size('large') +sg2.add_eastlabel(fontproperties=font2) +sg2.add_southlabel(fontproperties=font2) +sg2.add_centerlabel(fontproperties=font2) +sg2.add_coordinatereadout(uc2) +show() +fnum += 1 +plt.savefig(fnom+str(fnum).zfill(2)+ftyp) + +'''One final example, showing the plotting of plane traces as well as miller +indices, so that the angles between directions and plane normals can be compared +in a non-cubic crystal: +''' +close('all') +uc = ov.unitcell(a=5,b=2,c=1,alpha=79,beta=90,gamma=108) # default with no + # arguments is cubic +u = [3, 0, 0, 1, 1, 0,-2] +v = [2,-1,-5, 0, 1, 1, 1] +w = [1, 6, 1, 3, 1,-1, 3] +vv = ov.lattvec(u,v,w) # define lattice vectors +m = ov.miller(u,v,w) # define lattice vectors +sg = ov.stereoproj() # initialize the stereographic projection +sg.add_lattvec(vv,uc) # add our lattice vectors to the projection +sg.add_lattveclabels(vv,uc) +sg.add_miller(m,uc) # add our lattice vectors to the projection +sg.add_millerlabels(m,uc) +sg.add_trace(m,uc) # add plane traces +sg.grid_greatcircles() +sg.grid_smallcircles() +sg.grid_spokes() +font=FontProperties() +font.set_size('large') +font.set_style('italic') +sg.add_eastlabel(fontproperties=font) +sg.add_southlabel(fontproperties=font) +sg.add_centerlabel(fontproperties=font) +sg.add_coordinatereadout(uc) +fnum += 1 +plt.savefig(fnom+str(fnum).zfill(2)+ftyp) +close('all') \ No newline at end of file diff --git a/colormaps.py b/colormaps.py deleted file mode 100644 index 9ac4603..0000000 --- a/colormaps.py +++ /dev/null @@ -1,1317 +0,0 @@ -# New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, -# and (in the case of viridis) Eric Firing. -# -# This file and the colormaps in it are released under the CC0 license / -# public domain dedication. We would appreciate credit if you use or -# redistribute these colormaps, but do not impose any legal restrictions. -# -# To the extent possible under law, the persons who associated CC0 with -# mpl-colormaps have waived all copyright and related or neighboring rights -# to mpl-colormaps. -# -# You should have received a copy of the CC0 legalcode along with this -# work. If not, see . - -__all__ = ['magma', 'inferno', 'plasma', 'viridis', 'zerodis'] - -_magma_data = [[0.001462, 0.000466, 0.013866], - [0.002258, 0.001295, 0.018331], - [0.003279, 0.002305, 0.023708], - [0.004512, 0.003490, 0.029965], - [0.005950, 0.004843, 0.037130], - [0.007588, 0.006356, 0.044973], - [0.009426, 0.008022, 0.052844], - [0.011465, 0.009828, 0.060750], - [0.013708, 0.011771, 0.068667], - [0.016156, 0.013840, 0.076603], - [0.018815, 0.016026, 0.084584], - [0.021692, 0.018320, 0.092610], - [0.024792, 0.020715, 0.100676], - [0.028123, 0.023201, 0.108787], - [0.031696, 0.025765, 0.116965], - [0.035520, 0.028397, 0.125209], - [0.039608, 0.031090, 0.133515], - [0.043830, 0.033830, 0.141886], - [0.048062, 0.036607, 0.150327], - [0.052320, 0.039407, 0.158841], - [0.056615, 0.042160, 0.167446], - [0.060949, 0.044794, 0.176129], - [0.065330, 0.047318, 0.184892], - [0.069764, 0.049726, 0.193735], - [0.074257, 0.052017, 0.202660], - [0.078815, 0.054184, 0.211667], - [0.083446, 0.056225, 0.220755], - [0.088155, 0.058133, 0.229922], - [0.092949, 0.059904, 0.239164], - [0.097833, 0.061531, 0.248477], - [0.102815, 0.063010, 0.257854], - [0.107899, 0.064335, 0.267289], - [0.113094, 0.065492, 0.276784], - [0.118405, 0.066479, 0.286321], - [0.123833, 0.067295, 0.295879], - [0.129380, 0.067935, 0.305443], - [0.135053, 0.068391, 0.315000], - [0.140858, 0.068654, 0.324538], - [0.146785, 0.068738, 0.334011], - [0.152839, 0.068637, 0.343404], - [0.159018, 0.068354, 0.352688], - [0.165308, 0.067911, 0.361816], - [0.171713, 0.067305, 0.370771], - [0.178212, 0.066576, 0.379497], - [0.184801, 0.065732, 0.387973], - [0.191460, 0.064818, 0.396152], - [0.198177, 0.063862, 0.404009], - [0.204935, 0.062907, 0.411514], - [0.211718, 0.061992, 0.418647], - [0.218512, 0.061158, 0.425392], - [0.225302, 0.060445, 0.431742], - [0.232077, 0.059889, 0.437695], - [0.238826, 0.059517, 0.443256], - [0.245543, 0.059352, 0.448436], - [0.252220, 0.059415, 0.453248], - [0.258857, 0.059706, 0.457710], - [0.265447, 0.060237, 0.461840], - [0.271994, 0.060994, 0.465660], - [0.278493, 0.061978, 0.469190], - [0.284951, 0.063168, 0.472451], - [0.291366, 0.064553, 0.475462], - [0.297740, 0.066117, 0.478243], - [0.304081, 0.067835, 0.480812], - [0.310382, 0.069702, 0.483186], - [0.316654, 0.071690, 0.485380], - [0.322899, 0.073782, 0.487408], - [0.329114, 0.075972, 0.489287], - [0.335308, 0.078236, 0.491024], - [0.341482, 0.080564, 0.492631], - [0.347636, 0.082946, 0.494121], - [0.353773, 0.085373, 0.495501], - [0.359898, 0.087831, 0.496778], - [0.366012, 0.090314, 0.497960], - [0.372116, 0.092816, 0.499053], - [0.378211, 0.095332, 0.500067], - [0.384299, 0.097855, 0.501002], - [0.390384, 0.100379, 0.501864], - [0.396467, 0.102902, 0.502658], - [0.402548, 0.105420, 0.503386], - [0.408629, 0.107930, 0.504052], - [0.414709, 0.110431, 0.504662], - [0.420791, 0.112920, 0.505215], - [0.426877, 0.115395, 0.505714], - [0.432967, 0.117855, 0.506160], - [0.439062, 0.120298, 0.506555], - [0.445163, 0.122724, 0.506901], - [0.451271, 0.125132, 0.507198], - [0.457386, 0.127522, 0.507448], - [0.463508, 0.129893, 0.507652], - [0.469640, 0.132245, 0.507809], - [0.475780, 0.134577, 0.507921], - [0.481929, 0.136891, 0.507989], - [0.488088, 0.139186, 0.508011], - [0.494258, 0.141462, 0.507988], - [0.500438, 0.143719, 0.507920], - [0.506629, 0.145958, 0.507806], - [0.512831, 0.148179, 0.507648], - [0.519045, 0.150383, 0.507443], - [0.525270, 0.152569, 0.507192], - [0.531507, 0.154739, 0.506895], - [0.537755, 0.156894, 0.506551], - [0.544015, 0.159033, 0.506159], - [0.550287, 0.161158, 0.505719], - [0.556571, 0.163269, 0.505230], - [0.562866, 0.165368, 0.504692], - [0.569172, 0.167454, 0.504105], - [0.575490, 0.169530, 0.503466], - [0.581819, 0.171596, 0.502777], - [0.588158, 0.173652, 0.502035], - [0.594508, 0.175701, 0.501241], - [0.600868, 0.177743, 0.500394], - [0.607238, 0.179779, 0.499492], - [0.613617, 0.181811, 0.498536], - [0.620005, 0.183840, 0.497524], - [0.626401, 0.185867, 0.496456], - [0.632805, 0.187893, 0.495332], - [0.639216, 0.189921, 0.494150], - [0.645633, 0.191952, 0.492910], - [0.652056, 0.193986, 0.491611], - [0.658483, 0.196027, 0.490253], - [0.664915, 0.198075, 0.488836], - [0.671349, 0.200133, 0.487358], - [0.677786, 0.202203, 0.485819], - [0.684224, 0.204286, 0.484219], - [0.690661, 0.206384, 0.482558], - [0.697098, 0.208501, 0.480835], - [0.703532, 0.210638, 0.479049], - [0.709962, 0.212797, 0.477201], - [0.716387, 0.214982, 0.475290], - [0.722805, 0.217194, 0.473316], - [0.729216, 0.219437, 0.471279], - [0.735616, 0.221713, 0.469180], - [0.742004, 0.224025, 0.467018], - [0.748378, 0.226377, 0.464794], - [0.754737, 0.228772, 0.462509], - [0.761077, 0.231214, 0.460162], - [0.767398, 0.233705, 0.457755], - [0.773695, 0.236249, 0.455289], - [0.779968, 0.238851, 0.452765], - [0.786212, 0.241514, 0.450184], - [0.792427, 0.244242, 0.447543], - [0.798608, 0.247040, 0.444848], - [0.804752, 0.249911, 0.442102], - [0.810855, 0.252861, 0.439305], - [0.816914, 0.255895, 0.436461], - [0.822926, 0.259016, 0.433573], - [0.828886, 0.262229, 0.430644], - [0.834791, 0.265540, 0.427671], - [0.840636, 0.268953, 0.424666], - [0.846416, 0.272473, 0.421631], - [0.852126, 0.276106, 0.418573], - [0.857763, 0.279857, 0.415496], - [0.863320, 0.283729, 0.412403], - [0.868793, 0.287728, 0.409303], - [0.874176, 0.291859, 0.406205], - [0.879464, 0.296125, 0.403118], - [0.884651, 0.300530, 0.400047], - [0.889731, 0.305079, 0.397002], - [0.894700, 0.309773, 0.393995], - [0.899552, 0.314616, 0.391037], - [0.904281, 0.319610, 0.388137], - [0.908884, 0.324755, 0.385308], - [0.913354, 0.330052, 0.382563], - [0.917689, 0.335500, 0.379915], - [0.921884, 0.341098, 0.377376], - [0.925937, 0.346844, 0.374959], - [0.929845, 0.352734, 0.372677], - [0.933606, 0.358764, 0.370541], - [0.937221, 0.364929, 0.368567], - [0.940687, 0.371224, 0.366762], - [0.944006, 0.377643, 0.365136], - [0.947180, 0.384178, 0.363701], - [0.950210, 0.390820, 0.362468], - [0.953099, 0.397563, 0.361438], - [0.955849, 0.404400, 0.360619], - [0.958464, 0.411324, 0.360014], - [0.960949, 0.418323, 0.359630], - [0.963310, 0.425390, 0.359469], - [0.965549, 0.432519, 0.359529], - [0.967671, 0.439703, 0.359810], - [0.969680, 0.446936, 0.360311], - [0.971582, 0.454210, 0.361030], - [0.973381, 0.461520, 0.361965], - [0.975082, 0.468861, 0.363111], - [0.976690, 0.476226, 0.364466], - [0.978210, 0.483612, 0.366025], - [0.979645, 0.491014, 0.367783], - [0.981000, 0.498428, 0.369734], - [0.982279, 0.505851, 0.371874], - [0.983485, 0.513280, 0.374198], - [0.984622, 0.520713, 0.376698], - [0.985693, 0.528148, 0.379371], - [0.986700, 0.535582, 0.382210], - [0.987646, 0.543015, 0.385210], - [0.988533, 0.550446, 0.388365], - [0.989363, 0.557873, 0.391671], - [0.990138, 0.565296, 0.395122], - [0.990871, 0.572706, 0.398714], - [0.991558, 0.580107, 0.402441], - [0.992196, 0.587502, 0.406299], - [0.992785, 0.594891, 0.410283], - [0.993326, 0.602275, 0.414390], - [0.993834, 0.609644, 0.418613], - [0.994309, 0.616999, 0.422950], - [0.994738, 0.624350, 0.427397], - [0.995122, 0.631696, 0.431951], - [0.995480, 0.639027, 0.436607], - [0.995810, 0.646344, 0.441361], - [0.996096, 0.653659, 0.446213], - [0.996341, 0.660969, 0.451160], - [0.996580, 0.668256, 0.456192], - [0.996775, 0.675541, 0.461314], - [0.996925, 0.682828, 0.466526], - [0.997077, 0.690088, 0.471811], - [0.997186, 0.697349, 0.477182], - [0.997254, 0.704611, 0.482635], - [0.997325, 0.711848, 0.488154], - [0.997351, 0.719089, 0.493755], - [0.997351, 0.726324, 0.499428], - [0.997341, 0.733545, 0.505167], - [0.997285, 0.740772, 0.510983], - [0.997228, 0.747981, 0.516859], - [0.997138, 0.755190, 0.522806], - [0.997019, 0.762398, 0.528821], - [0.996898, 0.769591, 0.534892], - [0.996727, 0.776795, 0.541039], - [0.996571, 0.783977, 0.547233], - [0.996369, 0.791167, 0.553499], - [0.996162, 0.798348, 0.559820], - [0.995932, 0.805527, 0.566202], - [0.995680, 0.812706, 0.572645], - [0.995424, 0.819875, 0.579140], - [0.995131, 0.827052, 0.585701], - [0.994851, 0.834213, 0.592307], - [0.994524, 0.841387, 0.598983], - [0.994222, 0.848540, 0.605696], - [0.993866, 0.855711, 0.612482], - [0.993545, 0.862859, 0.619299], - [0.993170, 0.870024, 0.626189], - [0.992831, 0.877168, 0.633109], - [0.992440, 0.884330, 0.640099], - [0.992089, 0.891470, 0.647116], - [0.991688, 0.898627, 0.654202], - [0.991332, 0.905763, 0.661309], - [0.990930, 0.912915, 0.668481], - [0.990570, 0.920049, 0.675675], - [0.990175, 0.927196, 0.682926], - [0.989815, 0.934329, 0.690198], - [0.989434, 0.941470, 0.697519], - [0.989077, 0.948604, 0.704863], - [0.988717, 0.955742, 0.712242], - [0.988367, 0.962878, 0.719649], - [0.988033, 0.970012, 0.727077], - [0.987691, 0.977154, 0.734536], - [0.987387, 0.984288, 0.742002], - [0.987053, 0.991438, 0.749504]] - -_inferno_data = [[0.001462, 0.000466, 0.013866], - [0.002267, 0.001270, 0.018570], - [0.003299, 0.002249, 0.024239], - [0.004547, 0.003392, 0.030909], - [0.006006, 0.004692, 0.038558], - [0.007676, 0.006136, 0.046836], - [0.009561, 0.007713, 0.055143], - [0.011663, 0.009417, 0.063460], - [0.013995, 0.011225, 0.071862], - [0.016561, 0.013136, 0.080282], - [0.019373, 0.015133, 0.088767], - [0.022447, 0.017199, 0.097327], - [0.025793, 0.019331, 0.105930], - [0.029432, 0.021503, 0.114621], - [0.033385, 0.023702, 0.123397], - [0.037668, 0.025921, 0.132232], - [0.042253, 0.028139, 0.141141], - [0.046915, 0.030324, 0.150164], - [0.051644, 0.032474, 0.159254], - [0.056449, 0.034569, 0.168414], - [0.061340, 0.036590, 0.177642], - [0.066331, 0.038504, 0.186962], - [0.071429, 0.040294, 0.196354], - [0.076637, 0.041905, 0.205799], - [0.081962, 0.043328, 0.215289], - [0.087411, 0.044556, 0.224813], - [0.092990, 0.045583, 0.234358], - [0.098702, 0.046402, 0.243904], - [0.104551, 0.047008, 0.253430], - [0.110536, 0.047399, 0.262912], - [0.116656, 0.047574, 0.272321], - [0.122908, 0.047536, 0.281624], - [0.129285, 0.047293, 0.290788], - [0.135778, 0.046856, 0.299776], - [0.142378, 0.046242, 0.308553], - [0.149073, 0.045468, 0.317085], - [0.155850, 0.044559, 0.325338], - [0.162689, 0.043554, 0.333277], - [0.169575, 0.042489, 0.340874], - [0.176493, 0.041402, 0.348111], - [0.183429, 0.040329, 0.354971], - [0.190367, 0.039309, 0.361447], - [0.197297, 0.038400, 0.367535], - [0.204209, 0.037632, 0.373238], - [0.211095, 0.037030, 0.378563], - [0.217949, 0.036615, 0.383522], - [0.224763, 0.036405, 0.388129], - [0.231538, 0.036405, 0.392400], - [0.238273, 0.036621, 0.396353], - [0.244967, 0.037055, 0.400007], - [0.251620, 0.037705, 0.403378], - [0.258234, 0.038571, 0.406485], - [0.264810, 0.039647, 0.409345], - [0.271347, 0.040922, 0.411976], - [0.277850, 0.042353, 0.414392], - [0.284321, 0.043933, 0.416608], - [0.290763, 0.045644, 0.418637], - [0.297178, 0.047470, 0.420491], - [0.303568, 0.049396, 0.422182], - [0.309935, 0.051407, 0.423721], - [0.316282, 0.053490, 0.425116], - [0.322610, 0.055634, 0.426377], - [0.328921, 0.057827, 0.427511], - [0.335217, 0.060060, 0.428524], - [0.341500, 0.062325, 0.429425], - [0.347771, 0.064616, 0.430217], - [0.354032, 0.066925, 0.430906], - [0.360284, 0.069247, 0.431497], - [0.366529, 0.071579, 0.431994], - [0.372768, 0.073915, 0.432400], - [0.379001, 0.076253, 0.432719], - [0.385228, 0.078591, 0.432955], - [0.391453, 0.080927, 0.433109], - [0.397674, 0.083257, 0.433183], - [0.403894, 0.085580, 0.433179], - [0.410113, 0.087896, 0.433098], - [0.416331, 0.090203, 0.432943], - [0.422549, 0.092501, 0.432714], - [0.428768, 0.094790, 0.432412], - [0.434987, 0.097069, 0.432039], - [0.441207, 0.099338, 0.431594], - [0.447428, 0.101597, 0.431080], - [0.453651, 0.103848, 0.430498], - [0.459875, 0.106089, 0.429846], - [0.466100, 0.108322, 0.429125], - [0.472328, 0.110547, 0.428334], - [0.478558, 0.112764, 0.427475], - [0.484789, 0.114974, 0.426548], - [0.491022, 0.117179, 0.425552], - [0.497257, 0.119379, 0.424488], - [0.503493, 0.121575, 0.423356], - [0.509730, 0.123769, 0.422156], - [0.515967, 0.125960, 0.420887], - [0.522206, 0.128150, 0.419549], - [0.528444, 0.130341, 0.418142], - [0.534683, 0.132534, 0.416667], - [0.540920, 0.134729, 0.415123], - [0.547157, 0.136929, 0.413511], - [0.553392, 0.139134, 0.411829], - [0.559624, 0.141346, 0.410078], - [0.565854, 0.143567, 0.408258], - [0.572081, 0.145797, 0.406369], - [0.578304, 0.148039, 0.404411], - [0.584521, 0.150294, 0.402385], - [0.590734, 0.152563, 0.400290], - [0.596940, 0.154848, 0.398125], - [0.603139, 0.157151, 0.395891], - [0.609330, 0.159474, 0.393589], - [0.615513, 0.161817, 0.391219], - [0.621685, 0.164184, 0.388781], - [0.627847, 0.166575, 0.386276], - [0.633998, 0.168992, 0.383704], - [0.640135, 0.171438, 0.381065], - [0.646260, 0.173914, 0.378359], - [0.652369, 0.176421, 0.375586], - [0.658463, 0.178962, 0.372748], - [0.664540, 0.181539, 0.369846], - [0.670599, 0.184153, 0.366879], - [0.676638, 0.186807, 0.363849], - [0.682656, 0.189501, 0.360757], - [0.688653, 0.192239, 0.357603], - [0.694627, 0.195021, 0.354388], - [0.700576, 0.197851, 0.351113], - [0.706500, 0.200728, 0.347777], - [0.712396, 0.203656, 0.344383], - [0.718264, 0.206636, 0.340931], - [0.724103, 0.209670, 0.337424], - [0.729909, 0.212759, 0.333861], - [0.735683, 0.215906, 0.330245], - [0.741423, 0.219112, 0.326576], - [0.747127, 0.222378, 0.322856], - [0.752794, 0.225706, 0.319085], - [0.758422, 0.229097, 0.315266], - [0.764010, 0.232554, 0.311399], - [0.769556, 0.236077, 0.307485], - [0.775059, 0.239667, 0.303526], - [0.780517, 0.243327, 0.299523], - [0.785929, 0.247056, 0.295477], - [0.791293, 0.250856, 0.291390], - [0.796607, 0.254728, 0.287264], - [0.801871, 0.258674, 0.283099], - [0.807082, 0.262692, 0.278898], - [0.812239, 0.266786, 0.274661], - [0.817341, 0.270954, 0.270390], - [0.822386, 0.275197, 0.266085], - [0.827372, 0.279517, 0.261750], - [0.832299, 0.283913, 0.257383], - [0.837165, 0.288385, 0.252988], - [0.841969, 0.292933, 0.248564], - [0.846709, 0.297559, 0.244113], - [0.851384, 0.302260, 0.239636], - [0.855992, 0.307038, 0.235133], - [0.860533, 0.311892, 0.230606], - [0.865006, 0.316822, 0.226055], - [0.869409, 0.321827, 0.221482], - [0.873741, 0.326906, 0.216886], - [0.878001, 0.332060, 0.212268], - [0.882188, 0.337287, 0.207628], - [0.886302, 0.342586, 0.202968], - [0.890341, 0.347957, 0.198286], - [0.894305, 0.353399, 0.193584], - [0.898192, 0.358911, 0.188860], - [0.902003, 0.364492, 0.184116], - [0.905735, 0.370140, 0.179350], - [0.909390, 0.375856, 0.174563], - [0.912966, 0.381636, 0.169755], - [0.916462, 0.387481, 0.164924], - [0.919879, 0.393389, 0.160070], - [0.923215, 0.399359, 0.155193], - [0.926470, 0.405389, 0.150292], - [0.929644, 0.411479, 0.145367], - [0.932737, 0.417627, 0.140417], - [0.935747, 0.423831, 0.135440], - [0.938675, 0.430091, 0.130438], - [0.941521, 0.436405, 0.125409], - [0.944285, 0.442772, 0.120354], - [0.946965, 0.449191, 0.115272], - [0.949562, 0.455660, 0.110164], - [0.952075, 0.462178, 0.105031], - [0.954506, 0.468744, 0.099874], - [0.956852, 0.475356, 0.094695], - [0.959114, 0.482014, 0.089499], - [0.961293, 0.488716, 0.084289], - [0.963387, 0.495462, 0.079073], - [0.965397, 0.502249, 0.073859], - [0.967322, 0.509078, 0.068659], - [0.969163, 0.515946, 0.063488], - [0.970919, 0.522853, 0.058367], - [0.972590, 0.529798, 0.053324], - [0.974176, 0.536780, 0.048392], - [0.975677, 0.543798, 0.043618], - [0.977092, 0.550850, 0.039050], - [0.978422, 0.557937, 0.034931], - [0.979666, 0.565057, 0.031409], - [0.980824, 0.572209, 0.028508], - [0.981895, 0.579392, 0.026250], - [0.982881, 0.586606, 0.024661], - [0.983779, 0.593849, 0.023770], - [0.984591, 0.601122, 0.023606], - [0.985315, 0.608422, 0.024202], - [0.985952, 0.615750, 0.025592], - [0.986502, 0.623105, 0.027814], - [0.986964, 0.630485, 0.030908], - [0.987337, 0.637890, 0.034916], - [0.987622, 0.645320, 0.039886], - [0.987819, 0.652773, 0.045581], - [0.987926, 0.660250, 0.051750], - [0.987945, 0.667748, 0.058329], - [0.987874, 0.675267, 0.065257], - [0.987714, 0.682807, 0.072489], - [0.987464, 0.690366, 0.079990], - [0.987124, 0.697944, 0.087731], - [0.986694, 0.705540, 0.095694], - [0.986175, 0.713153, 0.103863], - [0.985566, 0.720782, 0.112229], - [0.984865, 0.728427, 0.120785], - [0.984075, 0.736087, 0.129527], - [0.983196, 0.743758, 0.138453], - [0.982228, 0.751442, 0.147565], - [0.981173, 0.759135, 0.156863], - [0.980032, 0.766837, 0.166353], - [0.978806, 0.774545, 0.176037], - [0.977497, 0.782258, 0.185923], - [0.976108, 0.789974, 0.196018], - [0.974638, 0.797692, 0.206332], - [0.973088, 0.805409, 0.216877], - [0.971468, 0.813122, 0.227658], - [0.969783, 0.820825, 0.238686], - [0.968041, 0.828515, 0.249972], - [0.966243, 0.836191, 0.261534], - [0.964394, 0.843848, 0.273391], - [0.962517, 0.851476, 0.285546], - [0.960626, 0.859069, 0.298010], - [0.958720, 0.866624, 0.310820], - [0.956834, 0.874129, 0.323974], - [0.954997, 0.881569, 0.337475], - [0.953215, 0.888942, 0.351369], - [0.951546, 0.896226, 0.365627], - [0.950018, 0.903409, 0.380271], - [0.948683, 0.910473, 0.395289], - [0.947594, 0.917399, 0.410665], - [0.946809, 0.924168, 0.426373], - [0.946392, 0.930761, 0.442367], - [0.946403, 0.937159, 0.458592], - [0.946903, 0.943348, 0.474970], - [0.947937, 0.949318, 0.491426], - [0.949545, 0.955063, 0.507860], - [0.951740, 0.960587, 0.524203], - [0.954529, 0.965896, 0.540361], - [0.957896, 0.971003, 0.556275], - [0.961812, 0.975924, 0.571925], - [0.966249, 0.980678, 0.587206], - [0.971162, 0.985282, 0.602154], - [0.976511, 0.989753, 0.616760], - [0.982257, 0.994109, 0.631017], - [0.988362, 0.998364, 0.644924]] - -_plasma_data = [[0.050383, 0.029803, 0.527975], - [0.063536, 0.028426, 0.533124], - [0.075353, 0.027206, 0.538007], - [0.086222, 0.026125, 0.542658], - [0.096379, 0.025165, 0.547103], - [0.105980, 0.024309, 0.551368], - [0.115124, 0.023556, 0.555468], - [0.123903, 0.022878, 0.559423], - [0.132381, 0.022258, 0.563250], - [0.140603, 0.021687, 0.566959], - [0.148607, 0.021154, 0.570562], - [0.156421, 0.020651, 0.574065], - [0.164070, 0.020171, 0.577478], - [0.171574, 0.019706, 0.580806], - [0.178950, 0.019252, 0.584054], - [0.186213, 0.018803, 0.587228], - [0.193374, 0.018354, 0.590330], - [0.200445, 0.017902, 0.593364], - [0.207435, 0.017442, 0.596333], - [0.214350, 0.016973, 0.599239], - [0.221197, 0.016497, 0.602083], - [0.227983, 0.016007, 0.604867], - [0.234715, 0.015502, 0.607592], - [0.241396, 0.014979, 0.610259], - [0.248032, 0.014439, 0.612868], - [0.254627, 0.013882, 0.615419], - [0.261183, 0.013308, 0.617911], - [0.267703, 0.012716, 0.620346], - [0.274191, 0.012109, 0.622722], - [0.280648, 0.011488, 0.625038], - [0.287076, 0.010855, 0.627295], - [0.293478, 0.010213, 0.629490], - [0.299855, 0.009561, 0.631624], - [0.306210, 0.008902, 0.633694], - [0.312543, 0.008239, 0.635700], - [0.318856, 0.007576, 0.637640], - [0.325150, 0.006915, 0.639512], - [0.331426, 0.006261, 0.641316], - [0.337683, 0.005618, 0.643049], - [0.343925, 0.004991, 0.644710], - [0.350150, 0.004382, 0.646298], - [0.356359, 0.003798, 0.647810], - [0.362553, 0.003243, 0.649245], - [0.368733, 0.002724, 0.650601], - [0.374897, 0.002245, 0.651876], - [0.381047, 0.001814, 0.653068], - [0.387183, 0.001434, 0.654177], - [0.393304, 0.001114, 0.655199], - [0.399411, 0.000859, 0.656133], - [0.405503, 0.000678, 0.656977], - [0.411580, 0.000577, 0.657730], - [0.417642, 0.000564, 0.658390], - [0.423689, 0.000646, 0.658956], - [0.429719, 0.000831, 0.659425], - [0.435734, 0.001127, 0.659797], - [0.441732, 0.001540, 0.660069], - [0.447714, 0.002080, 0.660240], - [0.453677, 0.002755, 0.660310], - [0.459623, 0.003574, 0.660277], - [0.465550, 0.004545, 0.660139], - [0.471457, 0.005678, 0.659897], - [0.477344, 0.006980, 0.659549], - [0.483210, 0.008460, 0.659095], - [0.489055, 0.010127, 0.658534], - [0.494877, 0.011990, 0.657865], - [0.500678, 0.014055, 0.657088], - [0.506454, 0.016333, 0.656202], - [0.512206, 0.018833, 0.655209], - [0.517933, 0.021563, 0.654109], - [0.523633, 0.024532, 0.652901], - [0.529306, 0.027747, 0.651586], - [0.534952, 0.031217, 0.650165], - [0.540570, 0.034950, 0.648640], - [0.546157, 0.038954, 0.647010], - [0.551715, 0.043136, 0.645277], - [0.557243, 0.047331, 0.643443], - [0.562738, 0.051545, 0.641509], - [0.568201, 0.055778, 0.639477], - [0.573632, 0.060028, 0.637349], - [0.579029, 0.064296, 0.635126], - [0.584391, 0.068579, 0.632812], - [0.589719, 0.072878, 0.630408], - [0.595011, 0.077190, 0.627917], - [0.600266, 0.081516, 0.625342], - [0.605485, 0.085854, 0.622686], - [0.610667, 0.090204, 0.619951], - [0.615812, 0.094564, 0.617140], - [0.620919, 0.098934, 0.614257], - [0.625987, 0.103312, 0.611305], - [0.631017, 0.107699, 0.608287], - [0.636008, 0.112092, 0.605205], - [0.640959, 0.116492, 0.602065], - [0.645872, 0.120898, 0.598867], - [0.650746, 0.125309, 0.595617], - [0.655580, 0.129725, 0.592317], - [0.660374, 0.134144, 0.588971], - [0.665129, 0.138566, 0.585582], - [0.669845, 0.142992, 0.582154], - [0.674522, 0.147419, 0.578688], - [0.679160, 0.151848, 0.575189], - [0.683758, 0.156278, 0.571660], - [0.688318, 0.160709, 0.568103], - [0.692840, 0.165141, 0.564522], - [0.697324, 0.169573, 0.560919], - [0.701769, 0.174005, 0.557296], - [0.706178, 0.178437, 0.553657], - [0.710549, 0.182868, 0.550004], - [0.714883, 0.187299, 0.546338], - [0.719181, 0.191729, 0.542663], - [0.723444, 0.196158, 0.538981], - [0.727670, 0.200586, 0.535293], - [0.731862, 0.205013, 0.531601], - [0.736019, 0.209439, 0.527908], - [0.740143, 0.213864, 0.524216], - [0.744232, 0.218288, 0.520524], - [0.748289, 0.222711, 0.516834], - [0.752312, 0.227133, 0.513149], - [0.756304, 0.231555, 0.509468], - [0.760264, 0.235976, 0.505794], - [0.764193, 0.240396, 0.502126], - [0.768090, 0.244817, 0.498465], - [0.771958, 0.249237, 0.494813], - [0.775796, 0.253658, 0.491171], - [0.779604, 0.258078, 0.487539], - [0.783383, 0.262500, 0.483918], - [0.787133, 0.266922, 0.480307], - [0.790855, 0.271345, 0.476706], - [0.794549, 0.275770, 0.473117], - [0.798216, 0.280197, 0.469538], - [0.801855, 0.284626, 0.465971], - [0.805467, 0.289057, 0.462415], - [0.809052, 0.293491, 0.458870], - [0.812612, 0.297928, 0.455338], - [0.816144, 0.302368, 0.451816], - [0.819651, 0.306812, 0.448306], - [0.823132, 0.311261, 0.444806], - [0.826588, 0.315714, 0.441316], - [0.830018, 0.320172, 0.437836], - [0.833422, 0.324635, 0.434366], - [0.836801, 0.329105, 0.430905], - [0.840155, 0.333580, 0.427455], - [0.843484, 0.338062, 0.424013], - [0.846788, 0.342551, 0.420579], - [0.850066, 0.347048, 0.417153], - [0.853319, 0.351553, 0.413734], - [0.856547, 0.356066, 0.410322], - [0.859750, 0.360588, 0.406917], - [0.862927, 0.365119, 0.403519], - [0.866078, 0.369660, 0.400126], - [0.869203, 0.374212, 0.396738], - [0.872303, 0.378774, 0.393355], - [0.875376, 0.383347, 0.389976], - [0.878423, 0.387932, 0.386600], - [0.881443, 0.392529, 0.383229], - [0.884436, 0.397139, 0.379860], - [0.887402, 0.401762, 0.376494], - [0.890340, 0.406398, 0.373130], - [0.893250, 0.411048, 0.369768], - [0.896131, 0.415712, 0.366407], - [0.898984, 0.420392, 0.363047], - [0.901807, 0.425087, 0.359688], - [0.904601, 0.429797, 0.356329], - [0.907365, 0.434524, 0.352970], - [0.910098, 0.439268, 0.349610], - [0.912800, 0.444029, 0.346251], - [0.915471, 0.448807, 0.342890], - [0.918109, 0.453603, 0.339529], - [0.920714, 0.458417, 0.336166], - [0.923287, 0.463251, 0.332801], - [0.925825, 0.468103, 0.329435], - [0.928329, 0.472975, 0.326067], - [0.930798, 0.477867, 0.322697], - [0.933232, 0.482780, 0.319325], - [0.935630, 0.487712, 0.315952], - [0.937990, 0.492667, 0.312575], - [0.940313, 0.497642, 0.309197], - [0.942598, 0.502639, 0.305816], - [0.944844, 0.507658, 0.302433], - [0.947051, 0.512699, 0.299049], - [0.949217, 0.517763, 0.295662], - [0.951344, 0.522850, 0.292275], - [0.953428, 0.527960, 0.288883], - [0.955470, 0.533093, 0.285490], - [0.957469, 0.538250, 0.282096], - [0.959424, 0.543431, 0.278701], - [0.961336, 0.548636, 0.275305], - [0.963203, 0.553865, 0.271909], - [0.965024, 0.559118, 0.268513], - [0.966798, 0.564396, 0.265118], - [0.968526, 0.569700, 0.261721], - [0.970205, 0.575028, 0.258325], - [0.971835, 0.580382, 0.254931], - [0.973416, 0.585761, 0.251540], - [0.974947, 0.591165, 0.248151], - [0.976428, 0.596595, 0.244767], - [0.977856, 0.602051, 0.241387], - [0.979233, 0.607532, 0.238013], - [0.980556, 0.613039, 0.234646], - [0.981826, 0.618572, 0.231287], - [0.983041, 0.624131, 0.227937], - [0.984199, 0.629718, 0.224595], - [0.985301, 0.635330, 0.221265], - [0.986345, 0.640969, 0.217948], - [0.987332, 0.646633, 0.214648], - [0.988260, 0.652325, 0.211364], - [0.989128, 0.658043, 0.208100], - [0.989935, 0.663787, 0.204859], - [0.990681, 0.669558, 0.201642], - [0.991365, 0.675355, 0.198453], - [0.991985, 0.681179, 0.195295], - [0.992541, 0.687030, 0.192170], - [0.993032, 0.692907, 0.189084], - [0.993456, 0.698810, 0.186041], - [0.993814, 0.704741, 0.183043], - [0.994103, 0.710698, 0.180097], - [0.994324, 0.716681, 0.177208], - [0.994474, 0.722691, 0.174381], - [0.994553, 0.728728, 0.171622], - [0.994561, 0.734791, 0.168938], - [0.994495, 0.740880, 0.166335], - [0.994355, 0.746995, 0.163821], - [0.994141, 0.753137, 0.161404], - [0.993851, 0.759304, 0.159092], - [0.993482, 0.765499, 0.156891], - [0.993033, 0.771720, 0.154808], - [0.992505, 0.777967, 0.152855], - [0.991897, 0.784239, 0.151042], - [0.991209, 0.790537, 0.149377], - [0.990439, 0.796859, 0.147870], - [0.989587, 0.803205, 0.146529], - [0.988648, 0.809579, 0.145357], - [0.987621, 0.815978, 0.144363], - [0.986509, 0.822401, 0.143557], - [0.985314, 0.828846, 0.142945], - [0.984031, 0.835315, 0.142528], - [0.982653, 0.841812, 0.142303], - [0.981190, 0.848329, 0.142279], - [0.979644, 0.854866, 0.142453], - [0.977995, 0.861432, 0.142808], - [0.976265, 0.868016, 0.143351], - [0.974443, 0.874622, 0.144061], - [0.972530, 0.881250, 0.144923], - [0.970533, 0.887896, 0.145919], - [0.968443, 0.894564, 0.147014], - [0.966271, 0.901249, 0.148180], - [0.964021, 0.907950, 0.149370], - [0.961681, 0.914672, 0.150520], - [0.959276, 0.921407, 0.151566], - [0.956808, 0.928152, 0.152409], - [0.954287, 0.934908, 0.152921], - [0.951726, 0.941671, 0.152925], - [0.949151, 0.948435, 0.152178], - [0.946602, 0.955190, 0.150328], - [0.944152, 0.961916, 0.146861], - [0.941896, 0.968590, 0.140956], - [0.940015, 0.975158, 0.131326]] - -_viridis_data = [[0.267004, 0.004874, 0.329415], - [0.268510, 0.009605, 0.335427], - [0.269944, 0.014625, 0.341379], - [0.271305, 0.019942, 0.347269], - [0.272594, 0.025563, 0.353093], - [0.273809, 0.031497, 0.358853], - [0.274952, 0.037752, 0.364543], - [0.276022, 0.044167, 0.370164], - [0.277018, 0.050344, 0.375715], - [0.277941, 0.056324, 0.381191], - [0.278791, 0.062145, 0.386592], - [0.279566, 0.067836, 0.391917], - [0.280267, 0.073417, 0.397163], - [0.280894, 0.078907, 0.402329], - [0.281446, 0.084320, 0.407414], - [0.281924, 0.089666, 0.412415], - [0.282327, 0.094955, 0.417331], - [0.282656, 0.100196, 0.422160], - [0.282910, 0.105393, 0.426902], - [0.283091, 0.110553, 0.431554], - [0.283197, 0.115680, 0.436115], - [0.283229, 0.120777, 0.440584], - [0.283187, 0.125848, 0.444960], - [0.283072, 0.130895, 0.449241], - [0.282884, 0.135920, 0.453427], - [0.282623, 0.140926, 0.457517], - [0.282290, 0.145912, 0.461510], - [0.281887, 0.150881, 0.465405], - [0.281412, 0.155834, 0.469201], - [0.280868, 0.160771, 0.472899], - [0.280255, 0.165693, 0.476498], - [0.279574, 0.170599, 0.479997], - [0.278826, 0.175490, 0.483397], - [0.278012, 0.180367, 0.486697], - [0.277134, 0.185228, 0.489898], - [0.276194, 0.190074, 0.493001], - [0.275191, 0.194905, 0.496005], - [0.274128, 0.199721, 0.498911], - [0.273006, 0.204520, 0.501721], - [0.271828, 0.209303, 0.504434], - [0.270595, 0.214069, 0.507052], - [0.269308, 0.218818, 0.509577], - [0.267968, 0.223549, 0.512008], - [0.266580, 0.228262, 0.514349], - [0.265145, 0.232956, 0.516599], - [0.263663, 0.237631, 0.518762], - [0.262138, 0.242286, 0.520837], - [0.260571, 0.246922, 0.522828], - [0.258965, 0.251537, 0.524736], - [0.257322, 0.256130, 0.526563], - [0.255645, 0.260703, 0.528312], - [0.253935, 0.265254, 0.529983], - [0.252194, 0.269783, 0.531579], - [0.250425, 0.274290, 0.533103], - [0.248629, 0.278775, 0.534556], - [0.246811, 0.283237, 0.535941], - [0.244972, 0.287675, 0.537260], - [0.243113, 0.292092, 0.538516], - [0.241237, 0.296485, 0.539709], - [0.239346, 0.300855, 0.540844], - [0.237441, 0.305202, 0.541921], - [0.235526, 0.309527, 0.542944], - [0.233603, 0.313828, 0.543914], - [0.231674, 0.318106, 0.544834], - [0.229739, 0.322361, 0.545706], - [0.227802, 0.326594, 0.546532], - [0.225863, 0.330805, 0.547314], - [0.223925, 0.334994, 0.548053], - [0.221989, 0.339161, 0.548752], - [0.220057, 0.343307, 0.549413], - [0.218130, 0.347432, 0.550038], - [0.216210, 0.351535, 0.550627], - [0.214298, 0.355619, 0.551184], - [0.212395, 0.359683, 0.551710], - [0.210503, 0.363727, 0.552206], - [0.208623, 0.367752, 0.552675], - [0.206756, 0.371758, 0.553117], - [0.204903, 0.375746, 0.553533], - [0.203063, 0.379716, 0.553925], - [0.201239, 0.383670, 0.554294], - [0.199430, 0.387607, 0.554642], - [0.197636, 0.391528, 0.554969], - [0.195860, 0.395433, 0.555276], - [0.194100, 0.399323, 0.555565], - [0.192357, 0.403199, 0.555836], - [0.190631, 0.407061, 0.556089], - [0.188923, 0.410910, 0.556326], - [0.187231, 0.414746, 0.556547], - [0.185556, 0.418570, 0.556753], - [0.183898, 0.422383, 0.556944], - [0.182256, 0.426184, 0.557120], - [0.180629, 0.429975, 0.557282], - [0.179019, 0.433756, 0.557430], - [0.177423, 0.437527, 0.557565], - [0.175841, 0.441290, 0.557685], - [0.174274, 0.445044, 0.557792], - [0.172719, 0.448791, 0.557885], - [0.171176, 0.452530, 0.557965], - [0.169646, 0.456262, 0.558030], - [0.168126, 0.459988, 0.558082], - [0.166617, 0.463708, 0.558119], - [0.165117, 0.467423, 0.558141], - [0.163625, 0.471133, 0.558148], - [0.162142, 0.474838, 0.558140], - [0.160665, 0.478540, 0.558115], - [0.159194, 0.482237, 0.558073], - [0.157729, 0.485932, 0.558013], - [0.156270, 0.489624, 0.557936], - [0.154815, 0.493313, 0.557840], - [0.153364, 0.497000, 0.557724], - [0.151918, 0.500685, 0.557587], - [0.150476, 0.504369, 0.557430], - [0.149039, 0.508051, 0.557250], - [0.147607, 0.511733, 0.557049], - [0.146180, 0.515413, 0.556823], - [0.144759, 0.519093, 0.556572], - [0.143343, 0.522773, 0.556295], - [0.141935, 0.526453, 0.555991], - [0.140536, 0.530132, 0.555659], - [0.139147, 0.533812, 0.555298], - [0.137770, 0.537492, 0.554906], - [0.136408, 0.541173, 0.554483], - [0.135066, 0.544853, 0.554029], - [0.133743, 0.548535, 0.553541], - [0.132444, 0.552216, 0.553018], - [0.131172, 0.555899, 0.552459], - [0.129933, 0.559582, 0.551864], - [0.128729, 0.563265, 0.551229], - [0.127568, 0.566949, 0.550556], - [0.126453, 0.570633, 0.549841], - [0.125394, 0.574318, 0.549086], - [0.124395, 0.578002, 0.548287], - [0.123463, 0.581687, 0.547445], - [0.122606, 0.585371, 0.546557], - [0.121831, 0.589055, 0.545623], - [0.121148, 0.592739, 0.544641], - [0.120565, 0.596422, 0.543611], - [0.120092, 0.600104, 0.542530], - [0.119738, 0.603785, 0.541400], - [0.119512, 0.607464, 0.540218], - [0.119423, 0.611141, 0.538982], - [0.119483, 0.614817, 0.537692], - [0.119699, 0.618490, 0.536347], - [0.120081, 0.622161, 0.534946], - [0.120638, 0.625828, 0.533488], - [0.121380, 0.629492, 0.531973], - [0.122312, 0.633153, 0.530398], - [0.123444, 0.636809, 0.528763], - [0.124780, 0.640461, 0.527068], - [0.126326, 0.644107, 0.525311], - [0.128087, 0.647749, 0.523491], - [0.130067, 0.651384, 0.521608], - [0.132268, 0.655014, 0.519661], - [0.134692, 0.658636, 0.517649], - [0.137339, 0.662252, 0.515571], - [0.140210, 0.665859, 0.513427], - [0.143303, 0.669459, 0.511215], - [0.146616, 0.673050, 0.508936], - [0.150148, 0.676631, 0.506589], - [0.153894, 0.680203, 0.504172], - [0.157851, 0.683765, 0.501686], - [0.162016, 0.687316, 0.499129], - [0.166383, 0.690856, 0.496502], - [0.170948, 0.694384, 0.493803], - [0.175707, 0.697900, 0.491033], - [0.180653, 0.701402, 0.488189], - [0.185783, 0.704891, 0.485273], - [0.191090, 0.708366, 0.482284], - [0.196571, 0.711827, 0.479221], - [0.202219, 0.715272, 0.476084], - [0.208030, 0.718701, 0.472873], - [0.214000, 0.722114, 0.469588], - [0.220124, 0.725509, 0.466226], - [0.226397, 0.728888, 0.462789], - [0.232815, 0.732247, 0.459277], - [0.239374, 0.735588, 0.455688], - [0.246070, 0.738910, 0.452024], - [0.252899, 0.742211, 0.448284], - [0.259857, 0.745492, 0.444467], - [0.266941, 0.748751, 0.440573], - [0.274149, 0.751988, 0.436601], - [0.281477, 0.755203, 0.432552], - [0.288921, 0.758394, 0.428426], - [0.296479, 0.761561, 0.424223], - [0.304148, 0.764704, 0.419943], - [0.311925, 0.767822, 0.415586], - [0.319809, 0.770914, 0.411152], - [0.327796, 0.773980, 0.406640], - [0.335885, 0.777018, 0.402049], - [0.344074, 0.780029, 0.397381], - [0.352360, 0.783011, 0.392636], - [0.360741, 0.785964, 0.387814], - [0.369214, 0.788888, 0.382914], - [0.377779, 0.791781, 0.377939], - [0.386433, 0.794644, 0.372886], - [0.395174, 0.797475, 0.367757], - [0.404001, 0.800275, 0.362552], - [0.412913, 0.803041, 0.357269], - [0.421908, 0.805774, 0.351910], - [0.430983, 0.808473, 0.346476], - [0.440137, 0.811138, 0.340967], - [0.449368, 0.813768, 0.335384], - [0.458674, 0.816363, 0.329727], - [0.468053, 0.818921, 0.323998], - [0.477504, 0.821444, 0.318195], - [0.487026, 0.823929, 0.312321], - [0.496615, 0.826376, 0.306377], - [0.506271, 0.828786, 0.300362], - [0.515992, 0.831158, 0.294279], - [0.525776, 0.833491, 0.288127], - [0.535621, 0.835785, 0.281908], - [0.545524, 0.838039, 0.275626], - [0.555484, 0.840254, 0.269281], - [0.565498, 0.842430, 0.262877], - [0.575563, 0.844566, 0.256415], - [0.585678, 0.846661, 0.249897], - [0.595839, 0.848717, 0.243329], - [0.606045, 0.850733, 0.236712], - [0.616293, 0.852709, 0.230052], - [0.626579, 0.854645, 0.223353], - [0.636902, 0.856542, 0.216620], - [0.647257, 0.858400, 0.209861], - [0.657642, 0.860219, 0.203082], - [0.668054, 0.861999, 0.196293], - [0.678489, 0.863742, 0.189503], - [0.688944, 0.865448, 0.182725], - [0.699415, 0.867117, 0.175971], - [0.709898, 0.868751, 0.169257], - [0.720391, 0.870350, 0.162603], - [0.730889, 0.871916, 0.156029], - [0.741388, 0.873449, 0.149561], - [0.751884, 0.874951, 0.143228], - [0.762373, 0.876424, 0.137064], - [0.772852, 0.877868, 0.131109], - [0.783315, 0.879285, 0.125405], - [0.793760, 0.880678, 0.120005], - [0.804182, 0.882046, 0.114965], - [0.814576, 0.883393, 0.110347], - [0.824940, 0.884720, 0.106217], - [0.835270, 0.886029, 0.102646], - [0.845561, 0.887322, 0.099702], - [0.855810, 0.888601, 0.097452], - [0.866013, 0.889868, 0.095953], - [0.876168, 0.891125, 0.095250], - [0.886271, 0.892374, 0.095374], - [0.896320, 0.893616, 0.096335], - [0.906311, 0.894855, 0.098125], - [0.916242, 0.896091, 0.100717], - [0.926106, 0.897330, 0.104071], - [0.935904, 0.898570, 0.108131], - [0.945636, 0.899815, 0.112838], - [0.955300, 0.901065, 0.118128], - [0.964894, 0.902323, 0.123941], - [0.974417, 0.903590, 0.130215], - [0.983868, 0.904867, 0.136897], - [0.993248, 0.906157, 0.143936]] - -_zerodis_data = [[1.000000, 1.000000, 1.000000], - [0.268510, 0.009605, 0.335427], - [0.269944, 0.014625, 0.341379], - [0.271305, 0.019942, 0.347269], - [0.272594, 0.025563, 0.353093], - [0.273809, 0.031497, 0.358853], - [0.274952, 0.037752, 0.364543], - [0.276022, 0.044167, 0.370164], - [0.277018, 0.050344, 0.375715], - [0.277941, 0.056324, 0.381191], - [0.278791, 0.062145, 0.386592], - [0.279566, 0.067836, 0.391917], - [0.280267, 0.073417, 0.397163], - [0.280894, 0.078907, 0.402329], - [0.281446, 0.084320, 0.407414], - [0.281924, 0.089666, 0.412415], - [0.282327, 0.094955, 0.417331], - [0.282656, 0.100196, 0.422160], - [0.282910, 0.105393, 0.426902], - [0.283091, 0.110553, 0.431554], - [0.283197, 0.115680, 0.436115], - [0.283229, 0.120777, 0.440584], - [0.283187, 0.125848, 0.444960], - [0.283072, 0.130895, 0.449241], - [0.282884, 0.135920, 0.453427], - [0.282623, 0.140926, 0.457517], - [0.282290, 0.145912, 0.461510], - [0.281887, 0.150881, 0.465405], - [0.281412, 0.155834, 0.469201], - [0.280868, 0.160771, 0.472899], - [0.280255, 0.165693, 0.476498], - [0.279574, 0.170599, 0.479997], - [0.278826, 0.175490, 0.483397], - [0.278012, 0.180367, 0.486697], - [0.277134, 0.185228, 0.489898], - [0.276194, 0.190074, 0.493001], - [0.275191, 0.194905, 0.496005], - [0.274128, 0.199721, 0.498911], - [0.273006, 0.204520, 0.501721], - [0.271828, 0.209303, 0.504434], - [0.270595, 0.214069, 0.507052], - [0.269308, 0.218818, 0.509577], - [0.267968, 0.223549, 0.512008], - [0.266580, 0.228262, 0.514349], - [0.265145, 0.232956, 0.516599], - [0.263663, 0.237631, 0.518762], - [0.262138, 0.242286, 0.520837], - [0.260571, 0.246922, 0.522828], - [0.258965, 0.251537, 0.524736], - [0.257322, 0.256130, 0.526563], - [0.255645, 0.260703, 0.528312], - [0.253935, 0.265254, 0.529983], - [0.252194, 0.269783, 0.531579], - [0.250425, 0.274290, 0.533103], - [0.248629, 0.278775, 0.534556], - [0.246811, 0.283237, 0.535941], - [0.244972, 0.287675, 0.537260], - [0.243113, 0.292092, 0.538516], - [0.241237, 0.296485, 0.539709], - [0.239346, 0.300855, 0.540844], - [0.237441, 0.305202, 0.541921], - [0.235526, 0.309527, 0.542944], - [0.233603, 0.313828, 0.543914], - [0.231674, 0.318106, 0.544834], - [0.229739, 0.322361, 0.545706], - [0.227802, 0.326594, 0.546532], - [0.225863, 0.330805, 0.547314], - [0.223925, 0.334994, 0.548053], - [0.221989, 0.339161, 0.548752], - [0.220057, 0.343307, 0.549413], - [0.218130, 0.347432, 0.550038], - [0.216210, 0.351535, 0.550627], - [0.214298, 0.355619, 0.551184], - [0.212395, 0.359683, 0.551710], - [0.210503, 0.363727, 0.552206], - [0.208623, 0.367752, 0.552675], - [0.206756, 0.371758, 0.553117], - [0.204903, 0.375746, 0.553533], - [0.203063, 0.379716, 0.553925], - [0.201239, 0.383670, 0.554294], - [0.199430, 0.387607, 0.554642], - [0.197636, 0.391528, 0.554969], - [0.195860, 0.395433, 0.555276], - [0.194100, 0.399323, 0.555565], - [0.192357, 0.403199, 0.555836], - [0.190631, 0.407061, 0.556089], - [0.188923, 0.410910, 0.556326], - [0.187231, 0.414746, 0.556547], - [0.185556, 0.418570, 0.556753], - [0.183898, 0.422383, 0.556944], - [0.182256, 0.426184, 0.557120], - [0.180629, 0.429975, 0.557282], - [0.179019, 0.433756, 0.557430], - [0.177423, 0.437527, 0.557565], - [0.175841, 0.441290, 0.557685], - [0.174274, 0.445044, 0.557792], - [0.172719, 0.448791, 0.557885], - [0.171176, 0.452530, 0.557965], - [0.169646, 0.456262, 0.558030], - [0.168126, 0.459988, 0.558082], - [0.166617, 0.463708, 0.558119], - [0.165117, 0.467423, 0.558141], - [0.163625, 0.471133, 0.558148], - [0.162142, 0.474838, 0.558140], - [0.160665, 0.478540, 0.558115], - [0.159194, 0.482237, 0.558073], - [0.157729, 0.485932, 0.558013], - [0.156270, 0.489624, 0.557936], - [0.154815, 0.493313, 0.557840], - [0.153364, 0.497000, 0.557724], - [0.151918, 0.500685, 0.557587], - [0.150476, 0.504369, 0.557430], - [0.149039, 0.508051, 0.557250], - [0.147607, 0.511733, 0.557049], - [0.146180, 0.515413, 0.556823], - [0.144759, 0.519093, 0.556572], - [0.143343, 0.522773, 0.556295], - [0.141935, 0.526453, 0.555991], - [0.140536, 0.530132, 0.555659], - [0.139147, 0.533812, 0.555298], - [0.137770, 0.537492, 0.554906], - [0.136408, 0.541173, 0.554483], - [0.135066, 0.544853, 0.554029], - [0.133743, 0.548535, 0.553541], - [0.132444, 0.552216, 0.553018], - [0.131172, 0.555899, 0.552459], - [0.129933, 0.559582, 0.551864], - [0.128729, 0.563265, 0.551229], - [0.127568, 0.566949, 0.550556], - [0.126453, 0.570633, 0.549841], - [0.125394, 0.574318, 0.549086], - [0.124395, 0.578002, 0.548287], - [0.123463, 0.581687, 0.547445], - [0.122606, 0.585371, 0.546557], - [0.121831, 0.589055, 0.545623], - [0.121148, 0.592739, 0.544641], - [0.120565, 0.596422, 0.543611], - [0.120092, 0.600104, 0.542530], - [0.119738, 0.603785, 0.541400], - [0.119512, 0.607464, 0.540218], - [0.119423, 0.611141, 0.538982], - [0.119483, 0.614817, 0.537692], - [0.119699, 0.618490, 0.536347], - [0.120081, 0.622161, 0.534946], - [0.120638, 0.625828, 0.533488], - [0.121380, 0.629492, 0.531973], - [0.122312, 0.633153, 0.530398], - [0.123444, 0.636809, 0.528763], - [0.124780, 0.640461, 0.527068], - [0.126326, 0.644107, 0.525311], - [0.128087, 0.647749, 0.523491], - [0.130067, 0.651384, 0.521608], - [0.132268, 0.655014, 0.519661], - [0.134692, 0.658636, 0.517649], - [0.137339, 0.662252, 0.515571], - [0.140210, 0.665859, 0.513427], - [0.143303, 0.669459, 0.511215], - [0.146616, 0.673050, 0.508936], - [0.150148, 0.676631, 0.506589], - [0.153894, 0.680203, 0.504172], - [0.157851, 0.683765, 0.501686], - [0.162016, 0.687316, 0.499129], - [0.166383, 0.690856, 0.496502], - [0.170948, 0.694384, 0.493803], - [0.175707, 0.697900, 0.491033], - [0.180653, 0.701402, 0.488189], - [0.185783, 0.704891, 0.485273], - [0.191090, 0.708366, 0.482284], - [0.196571, 0.711827, 0.479221], - [0.202219, 0.715272, 0.476084], - [0.208030, 0.718701, 0.472873], - [0.214000, 0.722114, 0.469588], - [0.220124, 0.725509, 0.466226], - [0.226397, 0.728888, 0.462789], - [0.232815, 0.732247, 0.459277], - [0.239374, 0.735588, 0.455688], - [0.246070, 0.738910, 0.452024], - [0.252899, 0.742211, 0.448284], - [0.259857, 0.745492, 0.444467], - [0.266941, 0.748751, 0.440573], - [0.274149, 0.751988, 0.436601], - [0.281477, 0.755203, 0.432552], - [0.288921, 0.758394, 0.428426], - [0.296479, 0.761561, 0.424223], - [0.304148, 0.764704, 0.419943], - [0.311925, 0.767822, 0.415586], - [0.319809, 0.770914, 0.411152], - [0.327796, 0.773980, 0.406640], - [0.335885, 0.777018, 0.402049], - [0.344074, 0.780029, 0.397381], - [0.352360, 0.783011, 0.392636], - [0.360741, 0.785964, 0.387814], - [0.369214, 0.788888, 0.382914], - [0.377779, 0.791781, 0.377939], - [0.386433, 0.794644, 0.372886], - [0.395174, 0.797475, 0.367757], - [0.404001, 0.800275, 0.362552], - [0.412913, 0.803041, 0.357269], - [0.421908, 0.805774, 0.351910], - [0.430983, 0.808473, 0.346476], - [0.440137, 0.811138, 0.340967], - [0.449368, 0.813768, 0.335384], - [0.458674, 0.816363, 0.329727], - [0.468053, 0.818921, 0.323998], - [0.477504, 0.821444, 0.318195], - [0.487026, 0.823929, 0.312321], - [0.496615, 0.826376, 0.306377], - [0.506271, 0.828786, 0.300362], - [0.515992, 0.831158, 0.294279], - [0.525776, 0.833491, 0.288127], - [0.535621, 0.835785, 0.281908], - [0.545524, 0.838039, 0.275626], - [0.555484, 0.840254, 0.269281], - [0.565498, 0.842430, 0.262877], - [0.575563, 0.844566, 0.256415], - [0.585678, 0.846661, 0.249897], - [0.595839, 0.848717, 0.243329], - [0.606045, 0.850733, 0.236712], - [0.616293, 0.852709, 0.230052], - [0.626579, 0.854645, 0.223353], - [0.636902, 0.856542, 0.216620], - [0.647257, 0.858400, 0.209861], - [0.657642, 0.860219, 0.203082], - [0.668054, 0.861999, 0.196293], - [0.678489, 0.863742, 0.189503], - [0.688944, 0.865448, 0.182725], - [0.699415, 0.867117, 0.175971], - [0.709898, 0.868751, 0.169257], - [0.720391, 0.870350, 0.162603], - [0.730889, 0.871916, 0.156029], - [0.741388, 0.873449, 0.149561], - [0.751884, 0.874951, 0.143228], - [0.762373, 0.876424, 0.137064], - [0.772852, 0.877868, 0.131109], - [0.783315, 0.879285, 0.125405], - [0.793760, 0.880678, 0.120005], - [0.804182, 0.882046, 0.114965], - [0.814576, 0.883393, 0.110347], - [0.824940, 0.884720, 0.106217], - [0.835270, 0.886029, 0.102646], - [0.845561, 0.887322, 0.099702], - [0.855810, 0.888601, 0.097452], - [0.866013, 0.889868, 0.095953], - [0.876168, 0.891125, 0.095250], - [0.886271, 0.892374, 0.095374], - [0.896320, 0.893616, 0.096335], - [0.906311, 0.894855, 0.098125], - [0.916242, 0.896091, 0.100717], - [0.926106, 0.897330, 0.104071], - [0.935904, 0.898570, 0.108131], - [0.945636, 0.899815, 0.112838], - [0.955300, 0.901065, 0.118128], - [0.964894, 0.902323, 0.123941], - [0.974417, 0.903590, 0.130215], - [0.983868, 0.904867, 0.136897], - [0.993248, 0.906157, 0.143936]] - -import matplotlib as mpl - -cmaps = {} -for (name, data) in (('magma', _magma_data), - ('inferno', _inferno_data), - ('plasma', _plasma_data), - ('viridis', _viridis_data), - ('zerodis', _zerodis_data)): - - cmaps[name] = mpl.colors.ListedColormap(data, name=name) - -magma = cmaps['magma'] -inferno = cmaps['inferno'] -plasma = cmaps['plasma'] -viridis = cmaps['viridis'] -zerodis = cmaps['zerodis'] \ No newline at end of file diff --git a/__init__.py b/cryspy/__init__.py old mode 100644 new mode 100755 similarity index 64% rename from __init__.py rename to cryspy/__init__.py index 8ad03de..304d065 --- a/__init__.py +++ b/cryspy/__init__.py @@ -1,10 +1,7 @@ -import cryspy.rmpyc # this will not be needed for release versions - -from cryspy.cio import * -from cryspy.rot import * -from cryspy.xtal import * -from cryspy.util import * -from cryspy.vis import * -from cryspy.ebsd import * -from cryspy.orrl import * -from cryspy.colormaps import * +from cryspy.cio import * +from cryspy.rot import * +from cryspy.xtal import * +from cryspy.util import * +from cryspy.vis import * +from cryspy.ebsd import * +from cryspy.orrl import * diff --git a/cio/__init__.py b/cryspy/cio/__init__.py old mode 100644 new mode 100755 similarity index 65% rename from cio/__init__.py rename to cryspy/cio/__init__.py index 31dcb9a..625a79b --- a/cio/__init__.py +++ b/cryspy/cio/__init__.py @@ -1,12 +1,11 @@ -import cryspy.rmpyc # this will not be needed for release versions - -''' -ovlib.io: Data Import and Export -=============================================================================== -''' - -# import the submodules so their functions are directly available -from .loadosc import loadosc -from .loadang import loadang -from .loadctf import loadctf -from .loadebsd import loadebsd \ No newline at end of file +''' +ovlib.cio: Data Import and Export +=============================================================================== +''' + +# import the submodules so their functions are directly available +from .loadosc import loadosc +from .loadang import loadang +from .loadctf import loadctf +from .loadebsd import loadebsd +from .loadGFtype1 import loadGFtype1, returnPhaseNames \ No newline at end of file diff --git a/cryspy/cio/loadGFtype1.py b/cryspy/cio/loadGFtype1.py new file mode 100755 index 0000000..c2671d9 --- /dev/null +++ b/cryspy/cio/loadGFtype1.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +def returnPhaseNames(filepath): + ''' + Returns the phase names within a TSL Grain File Type 1 + + Parameters + ---------- + filepath : str + path to Grain File type 1 + + Returns + ------- + phasenames : list + phase names + ''' + + # open the file and separate out the header from the data + dat=[]; + with open(filepath,'r') as f: + for line in f: + if line[0] != '#': + dat.append(line.strip()) + + phasenames = [] + for line in dat: + s = line.split() + name = ' '.join(s[10:]) + if name not in phasenames: + phasenames.append(name) + + return phasenames + + +def loadGFtype1(filepath, phasedict, notation='international'): + ''' + Parameters + ---------- + filepath : str + path to Grain File type 1 + + phasedict : dict + dictionary for interpreting phase results + + Returns + ------- + ebsd : obj of ebsd class + + Notes + ----- + TSL Grain File Type 1 includes most of the data needed to interpret an + EBSD scan, including the euler angles and xy locations for each point. + However, the phase is not listed as an integer. Rather, it is a string for + the phase name (which may contain spaces). This is impossible to interpret + without + ''' + + import numpy as np + from cryspy import ebsd + from cryspy import rot + from cryspy import xtal + import righthand as rh + + # open the file and separate out the header from the data + hdr=[];dat=[]; + with open(filepath,'r') as f: + for line in f: + if line[0]=='#': + hdr.append(line.strip()[1:].strip()) # remove #'s and whitespace + else: + dat.append(line.strip()) + + + # We should obtain the grid, the x step, the y step, the numbers of + # columns, and the number of rows from the x and y data themselves, + # because WE CAN... just in case the header was copied and pasted + # from a different file. Then compare to what the header says and + # print something if there is a discrepancy + + pb = rh.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') + # Parse the data into lists + + eul1=np.zeros(np.shape(dat)) + eul2=np.zeros(np.shape(dat)) + eul3=np.zeros(np.shape(dat)) + x=np.zeros(np.shape(dat)) + y=np.zeros(np.shape(dat)) + iq=np.zeros(np.shape(dat)) + ci=np.zeros(np.shape(dat)) + fit=np.zeros(np.shape(dat)) + gid=np.zeros(np.shape(dat)) + edge=np.zeros(np.shape(dat)) + phasetmp=[] + + ndex = 0 + for line in dat: + ls = line.split() + phasetmp.append(' '.join(ls[10:])) + + s = [float(item) for item in ls[0:10]] + + eul1[ndex] = s[0] + eul2[ndex] = s[1] + eul3[ndex] = s[2] + x[ndex] = s[3] + y[ndex] = s[4] + iq[ndex] = s[5] + ci[ndex] = s[6] + fit[ndex] = s[7] + gid[ndex] = s[8] + edge[ndex] = s[9] + + pb.update(ndex) + ndex += 1 + + numangcols = len(s) + + phasedict2 = {} + index = 0 + for item in [key for key in phasedict.keys()]: + phasedict2[item] = index + index += 1 + + s = np.zeros(np.shape(dat)) + phase=np.zeros(np.shape(dat)) + index = 0 + for item in phasetmp: + phase[index] = phasedict2[item] + s[index] = xtal.interpret_point_group_name(phasedict[item], notation=notation) + index += 1 + + # Start working with what we've interpreted now + o = xtal.orientation(quaternions = rot.quat.from_bunge(rot.bunge(eul1,eul2,eul3)),\ + pointgroupnumbers = np.squeeze(s.astype(np.int))) + pb.update(-1) + + ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are TSL-specific, and that two of + # the fields (fit and ...) have not been added yet. + ebsd_data.iq = iq + ebsd_data.ci = ci + + ebsd_data._anghdr = u''.join(['# {0}\n'.format(line) for line in (hdr)]) + ebsd_data._originalheaderdata = hdr + ebsd_data._oimversion_numangcols = numangcols + ebsd_data._original_phase = phase - 1 + + return ebsd_data diff --git a/cio/loadang.py b/cryspy/cio/loadang.py old mode 100644 new mode 100755 similarity index 80% rename from cio/loadang.py rename to cryspy/cio/loadang.py index 60e4adf..d867492 --- a/cio/loadang.py +++ b/cryspy/cio/loadang.py @@ -1,238 +1,238 @@ -# -*- coding: utf-8 -*- - -def loadang(filepath, alldata=False): - - # TODO: Add an interpreter of what version based on number of columns etc - - import string - import numpy as np - from cryspy.ebsd import ebsd - from cryspy.rot import bunge, quat - from cryspy.xtal import orientation, interpret_point_group_name - from cryspy.util import progbar - - # open the file and separate out the header from the data - hdr=[];dat=[]; - with open(filepath,'r') as f: - for line in f: - if line[0]=='#': - hdr.append(line.strip()[1:].strip()) # remove #'s and whitespace - else: - dat.append(line.strip()) - - - # Find the locations of information to extract from header - phaseloc=[]; - for i,istr in enumerate(hdr): - if (string.lower(istr).find('phase') != -1): - phaseloc.append(i) - - # TODO: THE FOLLOWING ARE NOT YET NEEDED IN THE CRYSPY CODE, BUT - # ARE NEEDED FOR WRITING ANG FILES - - #nom='TEM_PIXperUM' - #loc=lower(str).find(nom) - #if (loc != -1): - # tem_pix_per_um=float(str[(loc+len(nom)):].strip()) - - #nom='x-star' - #loc=lower(str).find(nom) - #if (loc != -1): - # x_star=float(str[(loc+len(nom)):].strip()) - - #nom='y-star' - #loc=lower(str).find(nom) - #if (loc != -1): - # y_star=float(str[(loc+len(nom)):].strip()) - - #nom='z-star' - #loc=lower(str).find(nom) - #if (loc != -1): - # z_star=float(str[(loc+len(nom)):].strip()) - - #nom='workingdistance' - #loc=lower(str).find(nom) - #if (loc != -1): - # workingdistance=float(str[(loc+len(nom)):].strip()) - - #nom='grid:' - #loc=lower(str).find(nom) - #if (loc != -1): - # grid=str[(loc+len(nom)):].strip() - - #nom='xstep:' - #loc=lower(str).find(nom) - #if (loc != -1): - # xstp=float(str[(loc+len(nom)):].strip()) - - #nom='ystep:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ystp=float(str[(loc+len(nom)):].strip()) - - #nom='ncols_odd:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ncols_odd=float(str[(loc+len(nom)):].strip()) - - #nom='ncols_even:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ncols_even=float(str[(loc+len(nom)):].strip()) - - #nom='nrows:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ncols_even=float(str[(loc+len(nom)):].strip()) - - #nom='operator:' - #loc=lower(str).find(nom) - #if (loc != -1): - # operator=str[(loc+len(nom)):].strip() - - #nom='sampleid:' - #loc=lower(str).find(nom) - #if (loc != -1): - # sampleid=str[(loc+len(nom)):].strip() - - #nom='scanid:' - #loc=lower(str).find(nom) - #if (loc != -1): - # scanid=str[(loc+len(nom)):].strip() - - - # Parse phase data - phaseidnums=[];materialname=[];formula=[];info=[];symmetry=[];latticeconstants=[]; - numberfamilies=[];famloc=[]; - for i in phaseloc: - # Get the phase number - nom='phase' - str=hdr[i];loc=string.lower(str).find(nom) - phaseidnums.append(float(str[loc+len(nom):].strip())) - - # Assume that the material name always follows - nom='materialname' - str=hdr[i+1];loc=string.lower(str).find(nom) - materialname.append(str[loc+len(nom):].strip()) - - # Assume that the formula is always next - nom='formula' - str=hdr[i+2];loc=string.lower(str).find(nom) - formula.append(str[loc+len(nom):].strip()) - - # Assume that the info is always after the formula... - nom='info' - str=hdr[i+3];loc=string.lower(str).find(nom) - info.append(str[loc+len(nom):].strip()) - - # ... and then the symmetry - nom='symmetry' - str=hdr[i+4];loc=string.lower(str).find(nom) - symmetry.append(str[loc+len(nom):].strip()) - - # ... and then the lattice constants - nom='latticeconstants' - str=hdr[i+5];loc=string.lower(str).find(nom) - latticeconstants.append(str[loc+len(nom):].strip()) - - # ... and then the number of families - nom='numberfamilies' - str=hdr[i+6];loc=string.lower(str).find(nom) - numberfamilies.append(float(str[loc+len(nom):].strip())) - - # store the line location of the beginning of the families - famloc.append(i+7) - - # We should obtain the grid, the x step, the y step, the numbers of - # columns, and the number of rows from the x and y data themselves, - # because WE CAN... just in case the header was copied and pasted - # from a different file. Then compare to what the header says and - # print something if there is a discrepancy - - pb = progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') - # Parse the data into lists - - eul1=np.zeros(np.shape(dat)) - eul2=np.zeros(np.shape(dat)) - eul3=np.zeros(np.shape(dat)) - x=np.zeros(np.shape(dat)) - y=np.zeros(np.shape(dat)) - iq=np.zeros(np.shape(dat)) - ci=np.zeros(np.shape(dat)) - phase=np.zeros(np.shape(dat)) - other = [] # XXX: other = np.array([]) # see comments below - - if alldata==True: - - ndex = 0 - for line in dat: - - s= map(float, line.split()) - - eul1[ndex] = s[0] - eul2[ndex] = s[1] - eul3[ndex] = s[2] - x[ndex] = s[3] - y[ndex] = s[4] - iq[ndex] = s[5] - ci[ndex] = s[6] - phase[ndex] = s[7] - pb.update(ndex) - ndex += 1 - - # Throw the other data into an "other" group of flexible size - for i in range(8, len(s)): - #other = np.hstack([other, np.array([np.float(s[i])])]).T # FIXME: This adds a LOT of time!!! There must be a faster way to do this. - other.append(s[i]) - - else: - - ndex = 0 - for line in dat: - - s= map(float, line.split()) - - eul1[ndex] = s[0] - eul2[ndex] = s[1] - eul3[ndex] = s[2] - x[ndex] = s[3] - y[ndex] = s[4] - iq[ndex] = s[5] - ci[ndex] = s[6] - phase[ndex] = s[7] - pb.update(ndex) - ndex += 1 - - numangcols = len(s) - - # Cast data lists into arrays - phase=np.array(phase)+1 # because tsl prints 0 for the phase when there is - # only one phase. But what about when there are - # more than one? - - s = np.zeros(np.shape(phase)) - index = 0 - for item in phaseidnums: - loc = phase == item - s[loc] = interpret_point_group_name(symmetry[index], 'tsl') - index += 1 - # Start working with what we've interpreted now - o = orientation(quaternions = quat.from_bunge(bunge(eul1,eul2,eul3)),\ - pointgroupnumbers = np.squeeze(s.astype(np.int))) - pb.update(-1) - - ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are TSL-specific, and that two of - # the fields (fit and ...) have not been added yet. - ebsd_data.iq = iq - ebsd_data.ci = ci - - ebsd_data._anghdr = u''.join(['# {0}\n'.format(line) for line in (hdr)]) - ebsd_data._originalheaderdata = hdr - ebsd_data._other = other - ebsd_data._oimversion_numangcols = numangcols - - return ebsd_data +# -*- coding: utf-8 -*- + +def loadang(filepath, alldata=False): + + # TODO: Add an interpreter of what version based on number of columns etc + + import numpy as np + from cryspy import ebsd + from cryspy import rot + from cryspy import xtal + import righthand as rh + + # open the file and separate out the header from the data + hdr=[];dat=[]; + with open(filepath,'r') as f: + for line in f: + if line[0]=='#': + hdr.append(line.strip()[1:].strip()) # remove #'s and whitespace + else: + dat.append(line.strip()) + + + # Find the locations of information to extract from header + phaseloc=[]; + for i,istr in enumerate(hdr): + if (istr.lower().find('phase') != -1): + phaseloc.append(i) + + # TODO: THE FOLLOWING ARE NOT YET NEEDED IN THE CRYSPY CODE, BUT + # ARE NEEDED FOR WRITING ANG FILES + + #nom='TEM_PIXperUM' + #loc=lower(str).find(nom) + #if (loc != -1): + # tem_pix_per_um=float(str[(loc+len(nom)):].strip()) + + #nom='x-star' + #loc=lower(str).find(nom) + #if (loc != -1): + # x_star=float(str[(loc+len(nom)):].strip()) + + #nom='y-star' + #loc=lower(str).find(nom) + #if (loc != -1): + # y_star=float(str[(loc+len(nom)):].strip()) + + #nom='z-star' + #loc=lower(str).find(nom) + #if (loc != -1): + # z_star=float(str[(loc+len(nom)):].strip()) + + #nom='workingdistance' + #loc=lower(str).find(nom) + #if (loc != -1): + # workingdistance=float(str[(loc+len(nom)):].strip()) + + #nom='grid:' + #loc=lower(str).find(nom) + #if (loc != -1): + # grid=str[(loc+len(nom)):].strip() + + #nom='xstep:' + #loc=lower(str).find(nom) + #if (loc != -1): + # xstp=float(str[(loc+len(nom)):].strip()) + + #nom='ystep:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ystp=float(str[(loc+len(nom)):].strip()) + + #nom='ncols_odd:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ncols_odd=float(str[(loc+len(nom)):].strip()) + + #nom='ncols_even:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ncols_even=float(str[(loc+len(nom)):].strip()) + + #nom='nrows:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ncols_even=float(str[(loc+len(nom)):].strip()) + + #nom='operator:' + #loc=lower(str).find(nom) + #if (loc != -1): + # operator=str[(loc+len(nom)):].strip() + + #nom='sampleid:' + #loc=lower(str).find(nom) + #if (loc != -1): + # sampleid=str[(loc+len(nom)):].strip() + + #nom='scanid:' + #loc=lower(str).find(nom) + #if (loc != -1): + # scanid=str[(loc+len(nom)):].strip() + + + # Parse phase data + phaseidnums=[];materialname=[];formula=[];info=[];symmetry=[];latticeconstants=[]; + numberfamilies=[];famloc=[]; + for i in phaseloc: + # Get the phase number + nom='phase' + sstr=hdr[i];loc=sstr.lower().find(nom) + phaseidnums.append(float(sstr[loc+len(nom):].strip())) + + # Assume that the material name always follows + nom='materialname' + sstr=hdr[i+1];loc=sstr.lower().find(nom) + materialname.append(sstr[loc+len(nom):].strip()) + + # Assume that the formula is always next + nom='formula' + sstr=hdr[i+2];loc=sstr.lower().find(nom) + formula.append(sstr[loc+len(nom):].strip()) + + # Assume that the info is always after the formula... + nom='info' + sstr=hdr[i+3];loc=sstr.lower().find(nom) + info.append(sstr[loc+len(nom):].strip()) + + # ... and then the symmetry + nom='symmetry' + sstr=hdr[i+4];loc=sstr.lower().find(nom) + symmetry.append(sstr[loc+len(nom):].strip()) + + # ... and then the lattice constants + nom='latticeconstants' + sstr=hdr[i+5];loc=sstr.lower().find(nom) + latticeconstants.append(sstr[loc+len(nom):].strip()) + + # ... and then the number of families + nom='numberfamilies' + sstr=hdr[i+6];loc=sstr.lower().find(nom) + numberfamilies.append(float(sstr[loc+len(nom):].strip())) + + # store the line location of the beginning of the families + famloc.append(i+7) + + # We should obtain the grid, the x step, the y step, the numbers of + # columns, and the number of rows from the x and y data themselves, + # because WE CAN... just in case the header was copied and pasted + # from a different file. Then compare to what the header says and + # print something if there is a discrepancy + + pb = rh.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') + # Parse the data into lists + + eul1=np.zeros(np.shape(dat)) + eul2=np.zeros(np.shape(dat)) + eul3=np.zeros(np.shape(dat)) + x=np.zeros(np.shape(dat)) + y=np.zeros(np.shape(dat)) + iq=np.zeros(np.shape(dat)) + ci=np.zeros(np.shape(dat)) + phase=np.zeros(np.shape(dat)) + other = [] # XXX: other = np.array([]) # see comments below + + if alldata==True: + + ndex = 0 + for line in dat: + + s = [float(item) for item in line.split()] + + eul1[ndex] = s[0] + eul2[ndex] = s[1] + eul3[ndex] = s[2] + x[ndex] = s[3] + y[ndex] = s[4] + iq[ndex] = s[5] + ci[ndex] = s[6] + phase[ndex] = s[7] + pb.update(ndex) + ndex += 1 + + # Throw the other data into an "other" group of flexible size + for i in range(8, len(s)): + #other = np.hstack([other, np.array([np.float(s[i])])]).T # FIXME: This adds a LOT of time!!! There must be a faster way to do this. + other.append(s[i]) + + else: + + ndex = 0 + for line in dat: + + s = [float(item) for item in line.split()] + + eul1[ndex] = s[0] + eul2[ndex] = s[1] + eul3[ndex] = s[2] + x[ndex] = s[3] + y[ndex] = s[4] + iq[ndex] = s[5] + ci[ndex] = s[6] + phase[ndex] = s[7] + pb.update(ndex) + ndex += 1 + + numangcols = len(s) + + # Cast data lists into arrays + phase=np.array(phase)+1 # because tsl prints 0 for the phase when there is + # only one phase. But what about when there are + # more than one? + + s = np.zeros(np.shape(phase)) + index = 0 + for item in phaseidnums: + loc = phase == item + s[loc] = xtal.interpret_point_group_name(symmetry[index], 'tsl') + index += 1 + # Start working with what we've interpreted now + o = xtal.orientation(quaternions = rot.quat.from_bunge(rot.bunge(eul1,eul2,eul3)),\ + pointgroupnumbers = np.squeeze(s.astype(np.int))) + pb.update(-1) + + ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are TSL-specific, and that two of + # the fields (fit and ...) have not been added yet. + ebsd_data.iq = iq + ebsd_data.ci = ci + + ebsd_data._anghdr = u''.join(['# {0}\n'.format(line) for line in (hdr)]) + ebsd_data._originalheaderdata = hdr + ebsd_data._other = other + ebsd_data._oimversion_numangcols = numangcols + ebsd_data._original_phase = phase - 1 + + return ebsd_data diff --git a/cio/loadctf.py b/cryspy/cio/loadctf.py old mode 100644 new mode 100755 similarity index 87% rename from cio/loadctf.py rename to cryspy/cio/loadctf.py index e164619..b84a9ad --- a/cio/loadctf.py +++ b/cryspy/cio/loadctf.py @@ -1,121 +1,123 @@ -# -*- coding: utf-8 -*- - -def loadctf(filepath): - - import numpy as np - import cryspy.ebsd as ebsd - import cryspy.rot as rot - import cryspy.util as util - import cryspy.xtal as xtal - - # open the file and separate out the header from the data - hdr=[]; dat=[]; hdr_flag = True - with open(filepath,'r') as f: - for line in f: - ll = line.strip() - if hdr_flag: - hdr.append(ll) - if not hdr_flag: - dat.append(line) - if ''.join(ll[0:9].lower().split())=='phasexy': # assume users will - # never call their - # data 'phasexy' - hdr_flag = False - - # Find the locations of information to extract from header - phaseloc=[]; - for i,str in enumerate(hdr): - if (str.lower().find('phases\t') != -1): - phaseloc.append(i) - phaseloc = phaseloc[-1] # assume the last usage is the correct one, in case - # the user named their project something including - #'phases\t' - - # Parse phase data - phaseidnums=[]; materialname=[]; symmetry=[];#formula=[];info=[]; - latticeconstants=[]; #numberfamilies=[];famloc=[]; - for i in range(phaseloc+1, np.shape(hdr)[0]-1): - - strdat = hdr[i].split() - - # get the lattice constants - latticeconstants.append((strdat[0].split(';')+strdat[1].split(';'))) - - # Get the phase number - phaseidnums.append(i-phaseloc) - - # Assume that the material name always follows - materialname.append(strdat[2]) - - # Symmetry - symmetry.append( - xtal.point_group_number_from_space_group_number(\ - int(strdat[4].strip()))) - - - pb = util.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') - - # Parse the data into lists - shdat = np.shape(dat) - eul1=np.zeros(shdat) - eul2=np.zeros(shdat) - eul3=np.zeros(shdat) - x=np.zeros(shdat) - y=np.zeros(shdat) - bc=np.zeros(shdat) - bs=np.zeros(shdat) - nbands=np.zeros(shdat) - error=np.zeros(shdat) - mad=np.zeros(shdat) - phase=np.zeros(shdat) - - ndex = 0 - for line in dat: - - s= map(float, line.split()) - - phase[ndex] = s[ 0] - x[ndex] = s[ 1] - y[ndex] = s[ 2] - nbands[ndex] = s[ 3] - error[ndex] = s[ 4] - eul1[ndex] = s[ 5] - eul2[ndex] = s[ 6] - eul3[ndex] = s[ 7] - mad[ndex] = s[ 8] - bc[ndex] = s[ 9] - bs[ndex] = s[10] - pb.update(ndex) - ndex += 1 - - # Cast data lists into arrays - phase=np.array(phase)+1 # because 0 printed for the phase when there is - # only one phase. but what about when there are - # more than one? EJP! Needs check. - - s = np.zeros(np.shape(phase)) - index = 0 - for item in phaseidnums: - loc = phase == item - s[loc] = symmetry[index] - index += 1 - #return s, eul1, eul2, eul3 - # Start working with what we've interpreted now - o = xtal.orientation(quaternions = rot.quat.from_bunge( - rot.bunge(eul1,eul2,eul3)), - pointgroupnumbers = np.atleast_1d(s.astype(np.int))) - pb.update(-1) - - ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are CTF-specific. - ebsd_data.bc = bc - ebsd_data.bs = bs - ebsd_data.mad = mad - ebsd_data.error = error - ebsd_data.nbands = nbands - ebsd_data._original_header = hdr - +# -*- coding: utf-8 -*- + +def loadctf(filepath): + + import numpy as np + import cryspy.ebsd as ebsd + import cryspy.rot as rot + import righthand as rh + import cryspy.xtal as xtal + + # open the file and separate out the header from the data + hdr=[]; dat=[]; hdr_flag = True + with open(filepath,'r') as f: + for line in f: + ll = line.strip() + if hdr_flag: + hdr.append(ll) + if not hdr_flag: + dat.append(line) + if ''.join(ll[0:9].lower().split())=='phasexy': # assume users will + # never call their + # data 'phasexy' + hdr_flag = False + + # Find the locations of information to extract from header + phaseloc=[]; + for i,str in enumerate(hdr): + if (str.lower().find('phases\t') != -1): + phaseloc.append(i) + phaseloc = phaseloc[-1] # assume the last usage is the correct one, in case + # the user named their project something including + #'phases\t' + + # Parse phase data + phaseidnums=[]; materialname=[]; symmetry=[];#formula=[];info=[]; + latticeconstants=[]; #numberfamilies=[];famloc=[]; + for i in range(phaseloc+1, np.shape(hdr)[0]-1): + + strdat = hdr[i].split() + + # get the lattice constants + latticeconstants.append((strdat[0].split(';')+strdat[1].split(';'))) + + # Get the phase number + phaseidnums.append(i-phaseloc) + + # Assume that the material name always follows + materialname.append(strdat[2]) + + # Symmetry + symmetry.append( + xtal.point_group_number_from_space_group_number(\ + int(strdat[4].strip()))) + + + pb = rh.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') + + # Parse the data into lists + shdat = np.shape(dat) + eul1=np.zeros(shdat) + eul2=np.zeros(shdat) + eul3=np.zeros(shdat) + x=np.zeros(shdat) + y=np.zeros(shdat) + bc=np.zeros(shdat) + bs=np.zeros(shdat) + nbands=np.zeros(shdat) + error=np.zeros(shdat) + mad=np.zeros(shdat) + phase=np.zeros(shdat) + + ndex = 0 + for line in dat: + + s = [float(item) for item in line.split()] + + phase[ndex] = s[ 0] - 1 + x[ndex] = s[ 1] + y[ndex] = s[ 2] + nbands[ndex] = s[ 3] + error[ndex] = s[ 4] + eul1[ndex] = s[ 5] * np.pi/180.0 + eul2[ndex] = s[ 6] * np.pi/180.0 + eul3[ndex] = s[ 7] * np.pi/180.0 + mad[ndex] = s[ 8] + bc[ndex] = s[ 9] + bs[ndex] = s[10] + pb.update(ndex) + ndex += 1 + + # Cast data lists into arrays + phase=np.array(phase)+1 # because 0 printed for the phase when there is + # only one phase. but what about when there are + # more than one? EJP! Needs check. + + s = np.zeros(np.shape(phase)) + index = 0 + for item in phaseidnums: + loc = phase == item + s[loc] = symmetry[index] + index += 1 + #return s, eul1, eul2, eul3 + # Start working with what we've interpreted now + o = xtal.orientation(quaternions = rot.quat.from_bunge( + rot.bunge(eul1,eul2,eul3)), + pointgroupnumbers = np.atleast_1d(s.astype(np.int))) + pb.update(-1) + + ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are CTF-specific. + ebsd_data.bc = bc + ebsd_data.bs = bs + ebsd_data.mad = mad + ebsd_data.error = error + ebsd_data.nbands = nbands + ebsd_data._original_header = hdr + ebsd_data._original_phase = phase - 1 + ebsd_data._original_euler = np.vstack([eul1, eul2, eul3]).T + return ebsd_data \ No newline at end of file diff --git a/cio/loadebsd.py b/cryspy/cio/loadebsd.py old mode 100644 new mode 100755 similarity index 61% rename from cio/loadebsd.py rename to cryspy/cio/loadebsd.py index ecbcb4f..ea87bea --- a/cio/loadebsd.py +++ b/cryspy/cio/loadebsd.py @@ -1,25 +1,22 @@ -# -*- coding: utf-8 -*- - - -def loadebsd(filepath): - - ''' - Load EBSD data based on filename extension. - ''' - test = filepath.split('.')[-1] - if test == 'ctf': - from cryspy.io import loadctf - ovdat = loadctf(filepath) - elif test == 'ang': - from cryspy.io import loadang - ovdat = loadang(filepath) - elif test == 'osc': - from cryspy.io import loadosc - ovdat = loadosc(filepath, create_ebsd_object = True, \ - ang_output = False, ang_output_filename = None) - else: - 'EBSD data can be read in ctf, ang, and osc formats.' - - return ovdat - +# -*- coding: utf-8 -*- + +def loadebsd(filepath): + + from cryspy import cio + ''' + Load EBSD data based on filename extension. + ''' + test = filepath.split('.')[-1] + if test == 'ctf': + ovdat = cio.loadctf(filepath) + elif test == 'ang': + ovdat = cio.loadang(filepath) + elif test == 'osc': + ovdat = cio.loadosc(filepath, create_ebsd_object = True, \ + ang_output = False, ang_output_filename = None) + else: + 'EBSD data can be read in ctf, ang, and osc formats.' + + return ovdat + \ No newline at end of file diff --git a/cio/loadosc.py b/cryspy/cio/loadosc.py old mode 100644 new mode 100755 similarity index 97% rename from cio/loadosc.py rename to cryspy/cio/loadosc.py index ddbd5b1..c63d680 --- a/cio/loadosc.py +++ b/cryspy/cio/loadosc.py @@ -1,1703 +1,1703 @@ -# -*- coding: utf-8 -*- - - -#filename = '8816-1_01_center.osc' -#fnom = '8816-1.osc' -#fnom = '8816-1_0_prevScan.osc' -#fnom = '8816-1_02_nearEdge.osc' - - -from cryspy.ebsd import ebsd -from cryspy.rot import bunge, quat -from cryspy.xtal import orientation, interpret_point_group_name -import struct -import numpy as np - -def loadosc(filename, create_ebsd_object = True, ang_output = False, \ - ang_output_filename = None): - """ loads osc files and converts to ang format, if desired - - This Python code performs the OSC to ANG conversion approximately 25% faster - than the previous script in Matlab [#TODO: add reference to matlab], with - some corrections in output and a potential improvement in memory usage - for opening very large OSC files. - - NOTES - ----- - - There are at least two Matlab versions of this floating around. One is from - Adam Shiveley (AFRL/RXCM) and the other is in the MTEX Matlab Toolbox. The - MTEX version is a partially rewritten version of Shiveley's by Florian - Bachmann. I was unable to get it to work in my testing. One problem is was that - the buffer size was set too small. - - Adam Shiveley's version from 9 Feb 2012 for Matlab is still in use at AFRL as - of 21 June 2016. My translation includes all of his original code as comments. - All Matlab code will be indicated by a % sign at the beginning of the comment - line. - - NOTES SPECIFIC TO THIS PYTHON VERSION: - - 1. HKL_COL_4 for first phase is wrong - 2. TEM_PX_PER_MICRON not yet correctly located in data file. We put in a dummy - when we write to ang. - 3. Not yet tested for more than 2 phases. - 4. We don't locate the elastic constants or "categories" in the osc file. - ANG reading works without the elastic constants, and we put in a dummy - for the categories. - 5. Some items that appear as zeroes to 5 or so decimal places appear as very - small floats ~1E-38 when I import them. - - PERFORMANCE TESTS: - A. 8234 kB OSC file: 3.75 s Python version; 5.05 s Matlab version - (~25% improvement) - B. 277635 kB OSC file: 98.9 s Python version; 132.8 s Matlab version - (~25% improvement) - - #------------------------------------------------------------------------------ - ADAM SHIVELEY'S ORIGINAL HEADER: - %function TSL_version = Decode_OSC_Header(OscFile, fileWriteID) - % Decode_Osc_Header(OscFile, fileWriteID) - % - % Inputs: OscFile is a string containing the path and file name of the OSC - % file to decode - % fileWriteID is an integer resulting from fileWriteID = - fopen('output.ang','w'); - % - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - % coded by: Adam Shiveley 9 Feb 12, AFRL/RXCM - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - %The OSC file is basically structured like this: - %Phase 1 name - %Phase 1 symmetry - %Phase 1 LatticeConstants - %Phase 1 Number of Families - %Phase 1 hklFamilies (read in every third one) - %Phase 1 Formula name - %This repeats for each phase in the scan - %sounds simple, right? Wrong the values are stored in decimal - %because of the way I choose to read the file in. - %This means data(10) = 70 which is actually the letter - %F as explained in this example: - %This is in decimal!!!! - %Example: data(10) = 70 - %char(data(10)) = F Look at ASCII table, Dec 70 = F - %This is where the fun begins. Most of the header information - %can be extracted directly using the decimal values and running - %the char command, however, the Euler angles must be extracted - %and converted into columns of length 4 then typecasted to a single - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - """ - - def _unpack_from_mmap(mmap_id, location_start): - - mmap_id.seek(location_start) - check = mmap_id.read_byte() - mmap_id.seek(location_start) # go back - counter = 0 - - if check != '\x00': - - temp_variable = '\x01' - - while temp_variable != '\x00': - - temp_variable = mmap_id.read_byte() - counter += 1 - - counter -= 1 - - mmap_id.seek(location_start) - result = mmap_id.read(counter) - - else: - - result = "" - - return result - - - #------------------------------------------------------------------------------ - - # So we can measure the time it takes to read the data - import time - t = time.clock() - - """ - % %Now let's get the handle to open the scan file - % fid=fopen(OscFile); - % - % %This is in decimal!!!! - % %Example: data(10) = 70 - % %char(data(10)) = F Look at ASCII table, Dec 70 = F - % data = transpose( fread( fid, '*bit8' ) ); - - - ------------------------------------------ - - We have a couple options for opening the file in Python, e.g.: - # with open(fnom, mode='rb') as file: # b is important -> binary - # bindat = file.read() - - I have chosen to proceed with memory mapping rather than reading the whole file - into memory because it's supposed to offer benefits for large files. - - My translation of Adam Shiveley's original code takes advantage of this for - some functions but memory management could still use future improvement. - - Shiveley reads in the entire binary file as 8 bit int and then casts the - integers into other data types. I am generally trying to selectively read the - portions we need as binary and unpack them as the correct data type. - """ - - import mmap - with open(filename, "r+b") as f: - # memory-map the file, size 0 means whole file - mm = mmap.mmap(f.fileno(), 0) - - """ The expected locations of header information in the data array""" - info_location_start = 72 - operator_location_start = 1095 - sample_ID_location_start = 1350 - scan_ID_location_start = 1605 - calibration_location_start = 1860 - - """ - % %Let's locate if there is any info the user typed in - % %There might be an issue here depending on how long the user types the - % %comments. Will need further testing to see if it breaks the hard-coded - % %index locations of the SampleID, Operator, and ScanID - % if(data(73) ~= 0) - % - % info_temp_variable = 1; - % info_counter = 73; - % - % while info_temp_variable ~= 0 - % - % info_temp_variable = data(info_counter); - % info_counter = info_counter +1; - % - % end - % - % %Convert the data into characters - % info = char(data(73:info_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear info_temp - % - % else - % - % info = ''; - % - % end - % - % %Now let's parse out the operator name - % if(data(1096) ~= 0) - % - % operator_temp_variable = 1; - % operator_counter = 1096; - % - % while operator_temp_variable ~= 0 - % - % operator_temp_variable = data(operator_counter); - % operator_counter = operator_counter +1; - % - % end - % - % %Convert the data into characters - % operator = char(data(1096:operator_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear operator_temp - % - % else - % - % operator = ' '; - % - % end - % - % - % %Now let's parse out the Sample ID. The index location could change if - % %a long info string is typed in. Seems to not change on the test I have - % %ran - % if(data(1351) ~= 0) - % - % sample_ID_temp_variable = 1; - % sample_ID_counter = 1351; - % - % while sample_ID_temp_variable ~= 0 - % - % sample_ID_temp_variable = data(sample_ID_counter); - % sample_ID_counter = sample_ID_counter +1; - % - % end - % - % %Convert the data into characters - % Sample_ID = char(data(1351:sample_ID_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear sample_ID_temp - % - % - % else - % - % Sample_ID = ' '; - % - % end - % - % %Now let's parse out the Scan ID. Again, the index could change if the - % %info is really long - % if(data(1606) ~= 0) - % - % scan_ID_temp_variable = 1; - % scan_ID_counter = 1606; - % - % while scan_ID_temp_variable ~= 0 - % - % scan_ID_temp_variable = data(scan_ID_counter); - % scan_ID_counter = scan_ID_counter +1; - % - % end - % - % %Convert the data into characters - % Scan_ID = char(data(1606:scan_ID_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear scan_ID_temp - % - % else - % - % Scan_ID =' '; - % - % end - - """ - info = _unpack_from_mmap(mm, info_location_start) # FIXME: write this info data somewhere to be used for writing ang files - operator_name = _unpack_from_mmap(mm, operator_location_start) - sample_ID = _unpack_from_mmap(mm, sample_ID_location_start) - scan_ID = _unpack_from_mmap(mm, scan_ID_location_start) - - """ - % Now let's check the file for the TEM_PIXperUm, x-star, y-star, z-star - % and working distance - - % Not sure where the TEM_PIXperUM is located because I don't have the TEM - % version, so I'll just hard code it for now - """ - TEM_PIXperUM = 1 - - """ - % %This extracts the calibration info - % calibration_encoded = data(1861:1876); - % - % %Now we need to reshape the calibration info into groups of 4 - % calibration_reshaped = reshape(calibration_encoded, - [4 length(calibration_encoded)/4]); - % - % %Let's typecast the data into a single representation - % calibration_decoded = typecast(calibration_reshaped(:), 'single'); - % - % %Here's the final calibration info - % x_star = calibration_decoded(1); - % - % y_star = calibration_decoded(2); - % - % z_star = calibration_decoded(3); - % - % working_distance = calibration_decoded(4); - """ - mm.seek(calibration_location_start) - calibration_encoded = mm.read(16) - calibration_decoded = struct.unpack('<4f', calibration_encoded) - del calibration_encoded - - x_star = calibration_decoded[0] - y_star = calibration_decoded[1] - z_star = calibration_decoded[2] - working_distance = calibration_decoded[3] - del calibration_decoded - - """ - % %Find the starting location for phase 1. This never seems to change - % %so far it stays constant, but I would like to find a more secure way - % %of finding the 1st phase. Seems like the command: - % %strfind(data, [-71 11 -17 -1 1]) would work... - % %maybe something like: - % start_phase_location = strfind(data, [-71 11 -17 -1 1 0 0 0]); - % phase_one_temp = data(start_phase_location:end); - % begin_phase_one = strfind(phase_one_temp, [0 0 0]); - % start_phase_1 = begin_phase_one(1) + 3 + start_phase_location(1); - % EJP! same as start_phase_location + 8??? - % phase_1_data = data(start_phase_1(1):end); - % EJP! this and the previous several lines are - a complicated way of getting to the end of - the -71 11 ... 0 0 0 match string! - % %something like that might work, but it needs further testing to verify - % - % %%%%%%%%%%%%%%%%%%%%%%%%%% - % %Added 3 Feb 2012 - % %This will locate the begining of the actual phase - % - % %Alright, so it now appears that the number after -128 43 is the number - % %of phases in the sample. - % begin_phase_1_check = strfind(phase_1_data, [-128 63]); - % - % %get the number of phases from the scan - % number_of_phases = phase_1_data(begin_phase_1_check(1)+2); - % - % %This will extract the begining of the acutal phase to the end - % phase_1_data = phase_1_data(begin_phase_1_check(1)+3:end); - % - % %So, now we need to ignore all the zeros until we hit the actual phase - % %name - % phase_1_zero_check = 0; - % phase_1_zero_check_counter = 1; - % - % %locate the begining of the actual phase 1 name - % %The way this loop is structured, you have to subtract 2 from the final - % %number in phase_1_zero_check_counter. Since the loop starts at 1 and - % %increments after it gets the current position, this will add 2 extra - % %iteration counts, 1 because it starts at 1 and 1 because it will - % %increment after the non-zero number is found - % while phase_1_zero_check == 0 - % - % phase_1_zero_check = phase_1_data(phase_1_zero_check_counter); - % phase_1_zero_check_counter = phase_1_zero_check_counter + 1; - % - % end - % %%%%%%%%%%%%%%%%%%%%%%%%%%% - % - % - % %Find the ending location for phase 1 search reigon - % %This is used until I write a code to begin searching - % %at a given offset. Then you can just say: - % %end_phase_1 = strfind(data(1945), 0); - % %This would mean begin searching data from position 1945 - % %until you find a "0" - % end_phase_1 = strfind(phase_1_data, [110 0 0]); - % % end_phase_1 = strfind(phase_1_data(begin_phase_1_check+3+ ... - % % phase_1_zero_check_counter), 0); - % - % - % if length(end_phase_1) > 1 - % - % end_phase_1 = end_phase_1(1); - % - % end - % - % %Extract phase 1 from the file using the 1989 index plus the ending index - % %since we searched for the ending_phase in an array starting at 1945 from - % %the original data set. The -1 here is because since we start at 1989 - % %we calculated everything else according to starting at 0. - % phase_1_location = data(start_phase_1-1+begin_phase_1_check(1) + 3 + ... - % phase_1_zero_check_counter-2:start_phase_1+end_phase_1); - % - % %new let's clear the phase_1_data array - % %clear phase_1_data - % - % %Find the end location of phase 1 - % end_phase_1_temp = strfind(phase_1_location, 0); - % - % %Convert the decimal values into characters using ASCII - % final_phase_1 = char(phase_1_location(1:end_phase_1_temp-1)); - """ - - # First Phase -- There must be at least one! - # It appears that each value is padded by a byte. - start_phase_location = mm.find(struct.pack(\ - '<8B', 185, 11, 239, 255, 1, 0, 0, 0)) - - tmpini = start_phase_location + 16 - end_phase_1 = mm[tmpini:].find(struct.pack('<3b', 110, 0, 0)) + 4 - - tmpini = start_phase_location+16 - tmpfin = start_phase_location+16+end_phase_1-3 - temp = mm[tmpini:tmpfin].split('\x00') - number_of_phases = struct.unpack('2d} {1: >2d} {2: >2d} '+\ - '{3:d} {4:.6f} {5:d}\n' - for i in np.arange(0, num_hkl_families_out[phase_num-1]): - fo.write(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ - hkl4[i], hkl5[i], hklfin[i])) - - # Shiveley puts this only for v6, but it seems to also be output - # for v7. We will write dummy numbers for now. - if start_phase_location == 1988 or start_phase_location == 1992: - for i in np.arange(0, 6): - fo.write(u'# ElasticConstants \t-1.000000 -1.000000 \ - -1.000000 -1.000000 -1.000000 -1.000000\n') - - # Writing dummy numbers... - fo.write(u'# Categories0 0 0 0 0 \n') - fo.write(u'#\n') - - fo.write(u'# GRID: {0}\n'.format(grid)) - fo.write(u'# XSTEP: {0:.6f}\n'.format(xstep)) - fo.write(u'# YSTEP: {0:.6f}\n'.format(ystep)) - fo.write(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) - fo.write(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) - fo.write(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) - fo.write(u'#\n') - fo.write(u'# OPERATOR: \t{0}\n'.format(operator_name)) - fo.write(u'#\n') - fo.write(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) - fo.write(u'#\n') - fo.write(u'# SCANID: \t{0}\n'.format(scan_ID)) - fo.write(u'#\n') - - if start_phase_location == 1992: # TSL OIM v7 - for i in np.arange(0, data_length): - fmt_str =u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+\ - u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} '+ \ - u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} '+\ - u'{12: >9.6f} {13: >9.6f} \n' - p = out_data[i, :] - fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ - p[6], p[7].astype(np.int), \ - p[8].astype(np.int), p[9], p[10], \ - p[11], p[12], p[13])) - elif start_phase_location == 1988 or start_phase_location == 1928: # TSL OIM v5.31 thru 6 - for i in np.arange(0, data_length): - fmt_str = u'{0: > 9.5f} {1: > 9.5f} {2: > 9.5f} {3: > 12.5f} \ - {4: > 12.5f} {5: > 8.1f} {6: > 6.3f} {7: > 2d} \ - {8: > 6d} {9: > 6.3f} \n' - p = out_data[i, :] - fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ - p[6], p[7].astype(np.int), \ - p[8].astype(np.int), p[9])) - - # NOW, create an ebsd object if the user wants one - if create_ebsd_object == True: - - phase = out_data[:,7].astype(np.int) - phaseidnums = np.arange(np.shape(phase_out)[0], 0, -1) - 1 - - s = np.zeros(np.shape(phase)) - index = np.amax(phaseidnums) - for item in phaseidnums: - loc = phase == item - s[loc] = interpret_point_group_name(np.str(phase_symmetry_out[index]),'tsl') - index -= 1 - - # Start working with what we've interpreted now - o = orientation(quaternions = quat.from_bunge(bunge(out_data[:,0], out_data[:,1], out_data[:,2])),\ - pointgroupnumbers = np.squeeze(s.astype(np.int))) - - ebsd_data = ebsd(orientations=o, x=out_data[:,3], y=out_data[:,4], phaseid=phase) # FIXME: need eventually to fix confusing variable naming here - - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are TSL-specific, and that two of - # the fields (fit and ...) have not been added yet. - ebsd_data.iq = out_data[:,5] - ebsd_data.ci = out_data[:,6] - - # Write header stuff - hdr = [] - hdr.append(u'# TEM_PIXperUM {0:.6f}\n'.format(TEM_PIXperUM)) - hdr.append(u'# x-star {0:.6f}\n'.format(x_star)) - hdr.append(u'# y-star {0:.6f}\n'.format(y_star)) - hdr.append(u'# z-star {0:.6f}\n'.format(z_star)) - hdr.append(u'# WorkingDistance {0:.6f}\n'.format(working_distance)) - hdr.append(u'#\n') - - for phase_num in np.arange(number_of_phases, 0, -1): - hdr.append(u'# Phase {0:d}\n'.format(phase_num)) - hdr.append(u'# MaterialName \t{0}\n'.format(phase_out[phase_num-1])) - hdr.append(u'# Formula \t{0}\n'.format(formula_out[phase_num-1])) - hdr.append(u'# Info \t\t\n') - hdr.append(u'# Symmetry {0:d}\n'.format(\ - phase_symmetry_out[phase_num-1])) - hdr.append(u'# LatticeConstants \ - {0:5.3f} {1:5.3f} {2:5.3f} {3:7.3f} {4:7.3f} {5:7.3f}\n'.format(\ - lattice_constants_out[phase_num-1][0], \ - lattice_constants_out[phase_num-1][1], \ - lattice_constants_out[phase_num-1][2], \ - lattice_constants_out[phase_num-1][3], \ - lattice_constants_out[phase_num-1][4], \ - lattice_constants_out[phase_num-1][5])) - hdr.append(u'# NumberFamilies {0}\n'.format(\ - num_hkl_families_out[phase_num-1])) - - hklfam = hkl_families_out[phase_num-1] - hkl4 = hkl_4_col_out[phase_num-1] - hkl5 = hkl_5_col_out[phase_num-1] - hklfin = hkl_last_col_out[phase_num-1] - tmpstr = u'# hklFamilies \t{0: >2d} {1: >2d} {2: >2d} '+\ - '{3:d} {4:.6f} {5:d}\n' - for i in np.arange(0, num_hkl_families_out[phase_num-1]): - hdr.append(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ - hkl4[i], hkl5[i], hklfin[i])) - - # Shiveley puts this only for v6, but it seems to also be output - # for v7. We will write dummy numbers for now. - if start_phase_location == 1988 or start_phase_location == 1992: - for i in np.arange(0, 6): - hdr.append(u'# ElasticConstants \t-1.000000 -1.000000 \ - -1.000000 -1.000000 -1.000000 -1.000000\n') - - # Writing dummy numbers... - hdr.append(u'# Categories0 0 0 0 0 \n') - hdr.append(u'#\n') - - hdr.append(u'# GRID: {0}\n'.format(grid)) - hdr.append(u'# XSTEP: {0:.6f}\n'.format(xstep)) - hdr.append(u'# YSTEP: {0:.6f}\n'.format(ystep)) - hdr.append(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) - hdr.append(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) - hdr.append(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) - hdr.append(u'#\n') - hdr.append(u'# OPERATOR: \t{0}\n'.format(operator_name)) - hdr.append(u'#\n') - hdr.append(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) - hdr.append(u'#\n') - hdr.append(u'# SCANID: \t{0}\n'.format(scan_ID)) - hdr.append(u'#\n') - ebsd_data._anghdr = "".join(hdr) - - return ebsd_data - - # This is it. - print 'Elapsed time = {0}'.format(time.clock() -t) \ No newline at end of file +# -*- coding: utf-8 -*- + + +#filename = '8816-1_01_center.osc' +#fnom = '8816-1.osc' +#fnom = '8816-1_0_prevScan.osc' +#fnom = '8816-1_02_nearEdge.osc' + + +def loadosc(filename, create_ebsd_object = True, ang_output = False, \ + ang_output_filename = None): + """ loads osc files and converts to ang format, if desired + + This Python code performs the OSC to ANG conversion approximately 25% faster + than the previous script in Matlab [#TODO: add reference to matlab], with + some corrections in output and a potential improvement in memory usage + for opening very large OSC files. + + NOTES + ----- + + There are at least two Matlab versions of this floating around. One is from + Adam Shiveley (AFRL/RXCM) and the other is in the MTEX Matlab Toolbox. The + MTEX version is a partially rewritten version of Shiveley's by Florian + Bachmann. I was unable to get it to work in my testing. One problem is was that + the buffer size was set too small. + + Adam Shiveley's version from 9 Feb 2012 for Matlab is still in use at AFRL as + of 21 June 2016. My translation includes all of his original code as comments. + All Matlab code will be indicated by a % sign at the beginning of the comment + line. + + NOTES SPECIFIC TO THIS PYTHON VERSION: + + 1. HKL_COL_4 for first phase is wrong + 2. TEM_PX_PER_MICRON not yet correctly located in data file. We put in a dummy + when we write to ang. + 3. Not yet tested for more than 2 phases. + 4. We don't locate the elastic constants or "categories" in the osc file. + ANG reading works without the elastic constants, and we put in a dummy + for the categories. + 5. Some items that appear as zeroes to 5 or so decimal places appear as very + small floats ~1E-38 when I import them. + + PERFORMANCE TESTS: + A. 8234 kB OSC file: 3.75 s Python version; 5.05 s Matlab version + (~25% improvement) + B. 277635 kB OSC file: 98.9 s Python version; 132.8 s Matlab version + (~25% improvement) + + #------------------------------------------------------------------------------ + ADAM SHIVELEY'S ORIGINAL HEADER: + %function TSL_version = Decode_OSC_Header(OscFile, fileWriteID) + % Decode_Osc_Header(OscFile, fileWriteID) + % + % Inputs: OscFile is a string containing the path and file name of the OSC + % file to decode + % fileWriteID is an integer resulting from fileWriteID = + fopen('output.ang','w'); + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % coded by: Adam Shiveley 9 Feb 12, AFRL/RXCM + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %The OSC file is basically structured like this: + %Phase 1 name + %Phase 1 symmetry + %Phase 1 LatticeConstants + %Phase 1 Number of Families + %Phase 1 hklFamilies (read in every third one) + %Phase 1 Formula name + %This repeats for each phase in the scan + %sounds simple, right? Wrong the values are stored in decimal + %because of the way I choose to read the file in. + %This means data(10) = 70 which is actually the letter + %F as explained in this example: + %This is in decimal!!!! + %Example: data(10) = 70 + %char(data(10)) = F Look at ASCII table, Dec 70 = F + %This is where the fun begins. Most of the header information + %can be extracted directly using the decimal values and running + %the char command, however, the Euler angles must be extracted + %and converted into columns of length 4 then typecasted to a single + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + """ + + from cryspy.ebsd import ebsd + from cryspy.rot import bunge, quat + from cryspy.xtal import orientation, interpret_point_group_name + import struct + import numpy as np + import mmap + import time + + def _unpack_from_mmap(mmap_id, location_start): + + mmap_id.seek(location_start) + check = mmap_id.read_byte() + mmap_id.seek(location_start) # go back + counter = 0 + + if check != '\x00': + + temp_variable = '\x01' + + while temp_variable != 0: + + temp_variable = mmap_id.read_byte() + counter += 1 + + counter -= 1 + + mmap_id.seek(location_start) + result = mmap_id.read(counter) + + else: + + result = "" + + return result + + + #------------------------------------------------------------------------------ + + # So we can measure the time it takes to read the data + t = time.clock() + + """ + % %Now let's get the handle to open the scan file + % fid=fopen(OscFile); + % + % %This is in decimal!!!! + % %Example: data(10) = 70 + % %char(data(10)) = F Look at ASCII table, Dec 70 = F + % data = transpose( fread( fid, '*bit8' ) ); + + + ------------------------------------------ + + We have a couple options for opening the file in Python, e.g.: + # with open(fnom, mode='rb') as file: # b is important -> binary + # bindat = file.read() + + I have chosen to proceed with memory mapping rather than reading the whole file + into memory because it's supposed to offer benefits for large files. + + My translation of Adam Shiveley's original code takes advantage of this for + some functions but memory management could still use future improvement. + + Shiveley reads in the entire binary file as 8 bit int and then casts the + integers into other data types. I am generally trying to selectively read the + portions we need as binary and unpack them as the correct data type. + """ + + with open(filename, "r+b") as f: + # memory-map the file, size 0 means whole file + mm = mmap.mmap(f.fileno(), 0) + + """ The expected locations of header information in the data array""" + info_location_start = 72 + operator_location_start = 1095 + sample_ID_location_start = 1350 + scan_ID_location_start = 1605 + calibration_location_start = 1860 + + """ + % %Let's locate if there is any info the user typed in + % %There might be an issue here depending on how long the user types the + % %comments. Will need further testing to see if it breaks the hard-coded + % %index locations of the SampleID, Operator, and ScanID + % if(data(73) ~= 0) + % + % info_temp_variable = 1; + % info_counter = 73; + % + % while info_temp_variable ~= 0 + % + % info_temp_variable = data(info_counter); + % info_counter = info_counter +1; + % + % end + % + % %Convert the data into characters + % info = char(data(73:info_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear info_temp + % + % else + % + % info = ''; + % + % end + % + % %Now let's parse out the operator name + % if(data(1096) ~= 0) + % + % operator_temp_variable = 1; + % operator_counter = 1096; + % + % while operator_temp_variable ~= 0 + % + % operator_temp_variable = data(operator_counter); + % operator_counter = operator_counter +1; + % + % end + % + % %Convert the data into characters + % operator = char(data(1096:operator_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear operator_temp + % + % else + % + % operator = ' '; + % + % end + % + % + % %Now let's parse out the Sample ID. The index location could change if + % %a long info string is typed in. Seems to not change on the test I have + % %ran + % if(data(1351) ~= 0) + % + % sample_ID_temp_variable = 1; + % sample_ID_counter = 1351; + % + % while sample_ID_temp_variable ~= 0 + % + % sample_ID_temp_variable = data(sample_ID_counter); + % sample_ID_counter = sample_ID_counter +1; + % + % end + % + % %Convert the data into characters + % Sample_ID = char(data(1351:sample_ID_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear sample_ID_temp + % + % + % else + % + % Sample_ID = ' '; + % + % end + % + % %Now let's parse out the Scan ID. Again, the index could change if the + % %info is really long + % if(data(1606) ~= 0) + % + % scan_ID_temp_variable = 1; + % scan_ID_counter = 1606; + % + % while scan_ID_temp_variable ~= 0 + % + % scan_ID_temp_variable = data(scan_ID_counter); + % scan_ID_counter = scan_ID_counter +1; + % + % end + % + % %Convert the data into characters + % Scan_ID = char(data(1606:scan_ID_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear scan_ID_temp + % + % else + % + % Scan_ID =' '; + % + % end + + """ + info = _unpack_from_mmap(mm, info_location_start) # FIXME: write this info data somewhere to be used for writing ang files + operator_name = _unpack_from_mmap(mm, operator_location_start) + sample_ID = _unpack_from_mmap(mm, sample_ID_location_start) + scan_ID = _unpack_from_mmap(mm, scan_ID_location_start) + + """ + % Now let's check the file for the TEM_PIXperUm, x-star, y-star, z-star + % and working distance + + % Not sure where the TEM_PIXperUM is located because I don't have the TEM + % version, so I'll just hard code it for now + """ + TEM_PIXperUM = 1 + + """ + % %This extracts the calibration info + % calibration_encoded = data(1861:1876); + % + % %Now we need to reshape the calibration info into groups of 4 + % calibration_reshaped = reshape(calibration_encoded, + [4 length(calibration_encoded)/4]); + % + % %Let's typecast the data into a single representation + % calibration_decoded = typecast(calibration_reshaped(:), 'single'); + % + % %Here's the final calibration info + % x_star = calibration_decoded(1); + % + % y_star = calibration_decoded(2); + % + % z_star = calibration_decoded(3); + % + % working_distance = calibration_decoded(4); + """ + mm.seek(calibration_location_start) + calibration_encoded = mm.read(16) + calibration_decoded = struct.unpack('<4f', calibration_encoded) + del calibration_encoded + + x_star = calibration_decoded[0] + y_star = calibration_decoded[1] + z_star = calibration_decoded[2] + working_distance = calibration_decoded[3] + del calibration_decoded + + """ + % %Find the starting location for phase 1. This never seems to change + % %so far it stays constant, but I would like to find a more secure way + % %of finding the 1st phase. Seems like the command: + % %strfind(data, [-71 11 -17 -1 1]) would work... + % %maybe something like: + % start_phase_location = strfind(data, [-71 11 -17 -1 1 0 0 0]); + % phase_one_temp = data(start_phase_location:end); + % begin_phase_one = strfind(phase_one_temp, [0 0 0]); + % start_phase_1 = begin_phase_one(1) + 3 + start_phase_location(1); + % EJP! same as start_phase_location + 8??? + % phase_1_data = data(start_phase_1(1):end); + % EJP! this and the previous several lines are + a complicated way of getting to the end of + the -71 11 ... 0 0 0 match string! + % %something like that might work, but it needs further testing to verify + % + % %%%%%%%%%%%%%%%%%%%%%%%%%% + % %Added 3 Feb 2012 + % %This will locate the begining of the actual phase + % + % %Alright, so it now appears that the number after -128 43 is the number + % %of phases in the sample. + % begin_phase_1_check = strfind(phase_1_data, [-128 63]); + % + % %get the number of phases from the scan + % number_of_phases = phase_1_data(begin_phase_1_check(1)+2); + % + % %This will extract the begining of the acutal phase to the end + % phase_1_data = phase_1_data(begin_phase_1_check(1)+3:end); + % + % %So, now we need to ignore all the zeros until we hit the actual phase + % %name + % phase_1_zero_check = 0; + % phase_1_zero_check_counter = 1; + % + % %locate the begining of the actual phase 1 name + % %The way this loop is structured, you have to subtract 2 from the final + % %number in phase_1_zero_check_counter. Since the loop starts at 1 and + % %increments after it gets the current position, this will add 2 extra + % %iteration counts, 1 because it starts at 1 and 1 because it will + % %increment after the non-zero number is found + % while phase_1_zero_check == 0 + % + % phase_1_zero_check = phase_1_data(phase_1_zero_check_counter); + % phase_1_zero_check_counter = phase_1_zero_check_counter + 1; + % + % end + % %%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % + % %Find the ending location for phase 1 search reigon + % %This is used until I write a code to begin searching + % %at a given offset. Then you can just say: + % %end_phase_1 = strfind(data(1945), 0); + % %This would mean begin searching data from position 1945 + % %until you find a "0" + % end_phase_1 = strfind(phase_1_data, [110 0 0]); + % % end_phase_1 = strfind(phase_1_data(begin_phase_1_check+3+ ... + % % phase_1_zero_check_counter), 0); + % + % + % if length(end_phase_1) > 1 + % + % end_phase_1 = end_phase_1(1); + % + % end + % + % %Extract phase 1 from the file using the 1989 index plus the ending index + % %since we searched for the ending_phase in an array starting at 1945 from + % %the original data set. The -1 here is because since we start at 1989 + % %we calculated everything else according to starting at 0. + % phase_1_location = data(start_phase_1-1+begin_phase_1_check(1) + 3 + ... + % phase_1_zero_check_counter-2:start_phase_1+end_phase_1); + % + % %new let's clear the phase_1_data array + % %clear phase_1_data + % + % %Find the end location of phase 1 + % end_phase_1_temp = strfind(phase_1_location, 0); + % + % %Convert the decimal values into characters using ASCII + % final_phase_1 = char(phase_1_location(1:end_phase_1_temp-1)); + """ + + # First Phase -- There must be at least one! + # It appears that each value is padded by a byte. + start_phase_location = mm.find(struct.pack(\ + '<8B', 185, 11, 239, 255, 1, 0, 0, 0)) + + tmpini = start_phase_location + 16 + end_phase_1 = mm[tmpini:].find(struct.pack('<3b', 110, 0, 0)) + 4 + + tmpini = start_phase_location+16 + tmpfin = start_phase_location+16+end_phase_1-3 + temp = mm[tmpini:tmpfin].split(b'\x00') + number_of_phases = struct.unpack('2d} {1: >2d} {2: >2d} '+\ + '{3:d} {4:.6f} {5:d}\n' + for i in np.arange(0, num_hkl_families_out[phase_num-1]): + fo.write(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ + hkl4[i], hkl5[i], hklfin[i])) + + # Shiveley puts this only for v6, but it seems to also be output + # for v7. We will write dummy numbers for now. + if start_phase_location == 1988 or start_phase_location == 1992: + for i in np.arange(0, 6): + fo.write(u'# ElasticConstants \t-1.000000 -1.000000 \ + -1.000000 -1.000000 -1.000000 -1.000000\n') + + # Writing dummy numbers... + fo.write(u'# Categories0 0 0 0 0 \n') + fo.write(u'#\n') + + fo.write(u'# GRID: {0}\n'.format(grid)) + fo.write(u'# XSTEP: {0:.6f}\n'.format(xstep)) + fo.write(u'# YSTEP: {0:.6f}\n'.format(ystep)) + fo.write(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) + fo.write(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) + fo.write(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) + fo.write(u'#\n') + fo.write(u'# OPERATOR: \t{0}\n'.format(operator_name)) + fo.write(u'#\n') + fo.write(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) + fo.write(u'#\n') + fo.write(u'# SCANID: \t{0}\n'.format(scan_ID)) + fo.write(u'#\n') + + if start_phase_location == 1992: # TSL OIM v7 + for i in np.arange(0, data_length): + fmt_str =u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+\ + u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} '+ \ + u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} '+\ + u'{12: >9.6f} {13: >9.6f} \n' + p = out_data[i, :] + fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ + p[6], p[7].astype(np.int), \ + p[8].astype(np.int), p[9], p[10], \ + p[11], p[12], p[13])) + elif start_phase_location == 1988 or start_phase_location == 1928: # TSL OIM v5.31 thru 6 + for i in np.arange(0, data_length): + fmt_str = u'{0: > 9.5f} {1: > 9.5f} {2: > 9.5f} {3: > 12.5f} \ + {4: > 12.5f} {5: > 8.1f} {6: > 6.3f} {7: > 2d} \ + {8: > 6d} {9: > 6.3f} \n' + p = out_data[i, :] + fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ + p[6], p[7].astype(np.int), \ + p[8].astype(np.int), p[9])) + + # NOW, create an ebsd object if the user wants one + if create_ebsd_object == True: + + phase = out_data[:,7].astype(np.int) + phaseidnums = np.arange(np.shape(phase_out)[0], 0, -1) - 1 + + s = np.zeros(np.shape(phase)) + index = np.amax(phaseidnums) + for item in phaseidnums: + loc = phase == item + s[loc] = interpret_point_group_name(np.str(phase_symmetry_out[index]),'tsl') + index -= 1 + + # Start working with what we've interpreted now + o = orientation(quaternions = quat.from_bunge(bunge(out_data[:,0], out_data[:,1], out_data[:,2])),\ + pointgroupnumbers = np.squeeze(s.astype(np.int))) + + ebsd_data = ebsd(orientations=o, x=out_data[:,3], y=out_data[:,4], phaseid=phase) # FIXME: need eventually to fix confusing variable naming here + + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are TSL-specific, and that two of + # the fields (fit and ...) have not been added yet. + ebsd_data.iq = out_data[:,5] + ebsd_data.ci = out_data[:,6] + + # Write header stuff + hdr = [] + hdr.append(u'# TEM_PIXperUM {0:.6f}\n'.format(TEM_PIXperUM)) + hdr.append(u'# x-star {0:.6f}\n'.format(x_star)) + hdr.append(u'# y-star {0:.6f}\n'.format(y_star)) + hdr.append(u'# z-star {0:.6f}\n'.format(z_star)) + hdr.append(u'# WorkingDistance {0:.6f}\n'.format(working_distance)) + hdr.append(u'#\n') + + for phase_num in np.arange(number_of_phases, 0, -1): + hdr.append(u'# Phase {0:d}\n'.format(phase_num)) + hdr.append(u'# MaterialName \t{0}\n'.format(phase_out[phase_num-1])) + hdr.append(u'# Formula \t{0}\n'.format(formula_out[phase_num-1])) + hdr.append(u'# Info \t\t\n') + hdr.append(u'# Symmetry {0:d}\n'.format(\ + phase_symmetry_out[phase_num-1])) + hdr.append(u'# LatticeConstants \ + {0:5.3f} {1:5.3f} {2:5.3f} {3:7.3f} {4:7.3f} {5:7.3f}\n'.format(\ + lattice_constants_out[phase_num-1][0], \ + lattice_constants_out[phase_num-1][1], \ + lattice_constants_out[phase_num-1][2], \ + lattice_constants_out[phase_num-1][3], \ + lattice_constants_out[phase_num-1][4], \ + lattice_constants_out[phase_num-1][5])) + hdr.append(u'# NumberFamilies {0}\n'.format(\ + num_hkl_families_out[phase_num-1])) + + hklfam = hkl_families_out[phase_num-1] + hkl4 = hkl_4_col_out[phase_num-1] + hkl5 = hkl_5_col_out[phase_num-1] + hklfin = hkl_last_col_out[phase_num-1] + tmpstr = u'# hklFamilies \t{0: >2d} {1: >2d} {2: >2d} '+\ + '{3:d} {4:.6f} {5:d}\n' + for i in np.arange(0, num_hkl_families_out[phase_num-1]): + hdr.append(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ + hkl4[i], hkl5[i], hklfin[i])) + + # Shiveley puts this only for v6, but it seems to also be output + # for v7. We will write dummy numbers for now. + if start_phase_location == 1988 or start_phase_location == 1992: + for i in np.arange(0, 6): + hdr.append(u'# ElasticConstants \t-1.000000 -1.000000 \ + -1.000000 -1.000000 -1.000000 -1.000000\n') + + # Writing dummy numbers... + hdr.append(u'# Categories0 0 0 0 0 \n') + hdr.append(u'#\n') + + hdr.append(u'# GRID: {0}\n'.format(grid)) + hdr.append(u'# XSTEP: {0:.6f}\n'.format(xstep)) + hdr.append(u'# YSTEP: {0:.6f}\n'.format(ystep)) + hdr.append(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) + hdr.append(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) + hdr.append(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) + hdr.append(u'#\n') + hdr.append(u'# OPERATOR: \t{0}\n'.format(operator_name)) + hdr.append(u'#\n') + hdr.append(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) + hdr.append(u'#\n') + hdr.append(u'# SCANID: \t{0}\n'.format(scan_ID)) + hdr.append(u'#\n') + ebsd_data._anghdr = "".join(hdr) + + return ebsd_data + + # This is it. + print('Elapsed time = {0}'.format(time.clock() -t)) \ No newline at end of file diff --git a/cio/writeang.py b/cryspy/cio/writeang.py old mode 100644 new mode 100755 similarity index 97% rename from cio/writeang.py rename to cryspy/cio/writeang.py index cfd7584..594c2ee --- a/cio/writeang.py +++ b/cryspy/cio/writeang.py @@ -1,62 +1,62 @@ -# -*- coding: utf-8 -*- -# Created on Fri Feb 17 17:41:38 2017 by paytonej - -def writeang(ebsd_object, filename): - - import cryspy.util as util - import cryspy.rot as rot - import io - import numpy as np - - pb = util.progbar(finalcount=ebsd_object.nr, message='Writing to ANG') - with io.open(filename, 'w', newline='\r\n') as fo: - - # FIXME: We need to fill in the appropriate stuff for the header from what - # is stored internally in the ebsd_data object. Until then, this works for - # data taken from *.ang files. - for item in ebsd_object._anghdr: - fo.write(u'# {0:s}\n'.format(item)) - - euler = rot.bunge.from_quat( - ebsd_object.orientations.rotations).to_array() - - xy = np.vstack([ebsd_object.x, ebsd_object.y]).T - - # TODO: Check if we are saving image quality and ci in this way from a - # ctf import. If we are not, what should we write here? - iqciphase = np.vstack([ebsd_object.iq, ebsd_object.ci, - ebsd_object.phaseid-1]).T - - num_other_cols = ebsd_object._oimversion_numangcols - 8 - - # FIXME: What if there is no ebsd_data._other because it didn't come from a previous TSL dataset? - oth = np.reshape(np.asanyarray(ebsd_object._other), - [len(ebsd_object._other) / num_other_cols, - num_other_cols]) - - out_data = np.hstack([euler, xy, iqciphase, oth]) - - if out_data.shape[1] == 14: # see loadosc - fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+ \ - u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ - u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} ' + \ - u'{12: >9.6f} {13: >9.6f} \n' - elif out_data.shape[1] == 10: # see loadosc - fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f}' + \ - u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ - u'{8: >6d} {9: >6.3f} \n' - elif out_data.shape[1] == 9: # Taken from my old writeang.m 2010-03-06 EJP - fmt_str = u'{0: >9.3f} {1: >9.3f} {2: >9.3f} {3: >12.3f}' + \ - u'{4: >12.3f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ - u'{8: >6d} \n' - - for i in np.arange(0, ebsd_object.nr): - - p = out_data[i, :] - fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ - p[6], p[7].astype(np.int), \ - p[8].astype(np.int), p[9], p[10], \ - p[11], p[12], p[13])) - pb.update(i) - +# -*- coding: utf-8 -*- +# Created on Fri Feb 17 17:41:38 2017 by paytonej + +def writeang(ebsd_object, filename): + + import cryspy.util as util + import cryspy.rot as rot + import io + import numpy as np + + pb = util.progbar(finalcount=ebsd_object.nr, message='Writing to ANG') + with io.open(filename, 'w', newline='\r\n') as fo: + + # FIXME: We need to fill in the appropriate stuff for the header from what + # is stored internally in the ebsd_data object. Until then, this works for + # data taken from *.ang files. + for item in ebsd_object._anghdr: + fo.write(u'# {0:s}\n'.format(item)) + + euler = rot.bunge.from_quat( + ebsd_object.orientations.rotations).to_array() + + xy = np.vstack([ebsd_object.x, ebsd_object.y]).T + + # TODO: Check if we are saving image quality and ci in this way from a + # ctf import. If we are not, what should we write here? + iqciphase = np.vstack([ebsd_object.iq, ebsd_object.ci, + ebsd_object.phaseid-1]).T + + num_other_cols = ebsd_object._oimversion_numangcols - 8 + + # FIXME: What if there is no ebsd_data._other because it didn't come from a previous TSL dataset? + oth = np.reshape(np.asanyarray(ebsd_object._other), + [len(ebsd_object._other) / num_other_cols, + num_other_cols]) + + out_data = np.hstack([euler, xy, iqciphase, oth]) + + if out_data.shape[1] == 14: # see loadosc + fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+ \ + u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ + u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} ' + \ + u'{12: >9.6f} {13: >9.6f} \n' + elif out_data.shape[1] == 10: # see loadosc + fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f}' + \ + u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ + u'{8: >6d} {9: >6.3f} \n' + elif out_data.shape[1] == 9: # Taken from my old writeang.m 2010-03-06 EJP + fmt_str = u'{0: >9.3f} {1: >9.3f} {2: >9.3f} {3: >12.3f}' + \ + u'{4: >12.3f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ + u'{8: >6d} \n' + + for i in np.arange(0, ebsd_object.nr): + + p = out_data[i, :] + fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ + p[6], p[7].astype(np.int), \ + p[8].astype(np.int), p[9], p[10], \ + p[11], p[12], p[13])) + pb.update(i) + pb.update(-1) \ No newline at end of file diff --git a/ebsd.py b/cryspy/ebsd.py old mode 100644 new mode 100755 similarity index 90% rename from ebsd.py rename to cryspy/ebsd.py index 9f3cfc0..4b55235 --- a/ebsd.py +++ b/cryspy/ebsd.py @@ -1,536 +1,536 @@ -# -*- coding: utf-8 -*- -""" -ovlib.ebsd: EBSD data analysis -=============================================================================== -""" - -class ebsd(object): - ''' - EBSD data object - - Parameters - ---------- - orientations : - - cryspy.xtal.orientation object - - x : numpy array of floats - - x locations of orientations - - y : numpy array of floats - - y locations of orientations - - phaseid : numpy array of ints - - phase identifiers - - Notes - ----- - - At minimum, an EBSD object is an orientation object with a phase and - x and y positions associated with each individual orientation. - - ''' - def __init__(self, orientations=[], x=[], y=[], phaseid=[]): - # initialize the minimum amount of information in order to have EBSD - # data. We need to have x and y coordinates and an orientation measured - # at those coordinates. - self.orientations = orientations - self.x = x - self.y = y - self.phaseid = phaseid - - #def __repr__(self): - # TODO: Add __repr__ method - # Needs to show number of phases, number of data points, etc. - - def calc_scanstepsize(self): - ''' adds step and scan size data to the ebsd object - - EBSD class method that uses x and y location information to add step - size and scan size data to the ebsd object. - ''' - import numpy as np - nr = np.size(self.x) - rows, cols = scansize(self.x, self.y) - xstp, ystp = scanstep(self.x, self.y, rows, cols) - idx = scanrowlims(self.x, self.y, rows, cols) - self.rows = rows - self.cols = cols - self.xstp = xstp - self.ystp = ystp - self.idx = idx - self.nr = nr - - def prepare_for_plotting(self): - ''' adds information to ebsd object required for plotting - - class method that adds information necessary for plotting to the object - ''' - import numpy as np - - if not hasattr(self, 'x') or not hasattr(self, 'y'): - print 'x and y coordinates are needed for plotting ebsd data.' - - if not hasattr(self, 'rows') or not hasattr(self, 'cols'): - rows, cols = scansize(self.x, self.y) - self.rows = rows - self.cols = cols - del rows, cols - - if not hasattr(self, 'xstp') or not hasattr(self, 'ystp'): - xstp, ystp = scanstep(self.x, self.y, self.rows, self.cols) - self.xstp = xstp - self.ystp = ystp - del xstp, ystp - - if not hasattr(self, 'idx'): - idx = scanrowlims(self.x, self.y, self.rows, self.cols) - self.idx = idx - del idx - - if not hasattr(self, 'nr'): - nr = np.size(self.x) - self.nr = nr - del nr - - # Kernel vertices and faces are needed for plotting similar to matlab's - # patch function using matplotlib - v, p = kernverts(self.x, self.y, self.rows, self.cols, - self.xstp, self.ystp, self.idx) - f = kernfaces(v, p, self.idx, self.rows, self.cols, self.nr) - self.v = v - self.p = p - self.f = f - - # For plotting using opengl, we need a different format of vertices and - # faces - tv, vf = triverts(self.nr, self.v, self.f, self.cols) - self.tv = tv - self.vf = vf - -#------------------------------------------------------------------------------- - -def scansize(x, y): - """ - scansize returns the number of rows and columns in oimdata on either - a square or hexagonal grid. Two values for the # columns are - returned in the case of a hex grid: the first for odd and the - second for even. See hdrload for correct importing of the - data matrix. - - Notes - ----- - - This one isn't as straightforward as you might assume. - The data can come on either a hex grid or a square - grid. When on a hex grid, the data is staggered between - rows, so the number of data points differs between rows, - and the x-coordinate varies while the y-coordinate is - constant within each row. - - If there is no header for the data, or if the header doesn't match the - file contents, we can figure out this info from looking at the XY data. - - Ultimately, the old-school way I am using here to count - the number of vertical pixels below isn't efficient but - doesn't take very long. - - To get the oimdata input variable, see hdrload. - - If the input file is on a hex grid, columns returns first the odd - then the even number. - """ - import numpy as np - - if np.size(x) != np.size(y): - print 'scansize: the lengths of x and y are not equal. this may result\ - in errors.' - - # count the number of pixels in the horizontal direction - # i.e, the maximum number of columns - # (even numbered rows have fewer columns in the scan) and - # the first row (which is always ==0 in my TSL scans) is - # by definition an odd row. - cols1=np.sum(y==0) # cols in first row - - # if the first entry for the x-coordinate value in the first row - # doesn't match the first entry for the x-coordinate value in the - # second row, then it's hexagonal. This could maybe also be done - # using the 'unique' function. - if x[cols1] != x[0]: - rows = np.sum(x==0) + np.sum(x==x[cols1]) - cols = np.array([1,2], dtype=np.uint32) - cols[0]=cols1 - cols[1]=cols[0]-1 - else: - nr = np.size(x) - rows= np.uint32(nr / cols1) - cols= cols1 - - return rows, cols - -#------------------------------------------------------------------------------- - -def scanstep(x, y, nr, nc): - ''' finds the x and y step sizes of an ebsd scan - - Parameters - ---------- - x : array - x locations - y : array - y locations - nr : scalar - number of rows - nc : array - number of columns (can be 2 vector for hexagonal grid) - - Returns - ------- - xstep : scalar - step size in x direction - ystep : scalar - step size in y direction - ''' - import numpy as np - - if np.size(x) != np.size(y): - print 'scanstep: the lengths of x and y are not equal. this may result\ - in errors.' - - # Get the scan size - if np.size(nc)==2: # hexagonal scan - nc=nc[0]; - - # Get the hex data x step size - if nr<5: - # we need at least two points in the row to get the step size - # empirically (or we could read the header, but I think this method is - # generally better in case the header doesn't match the data). - # This means a hex scan size of at least 5 points is a reasonable - # minimum: 2 points in the first and third rows, 1 point in the middle - # row. - print 'scanstep error: at least 5 data points required' - else: - xstep = x[1]-x[0] - ystep = y[nc]-y[nc-1] - - return xstep, ystep - -#------------------------------------------------------------------------------- - -def scanrowlims(x, y, nrows, ncols): - ''' - - Notes - ----- - SCANROWLIMS finds the indices that correspond to the left and right - limits of each row. Assumes a regular hexagonal or square grid. - - The algorithm below is slow, but since it typically only needs to be run - once for a scan in a session, it seems ok for the time being. - ''' - import numpy as np - import cryspy.util as util - - if np.size(x) != np.size(y): - print 'scanrowlims: the lengths of x and y are not equal. this may \ - result in errors.' - - if np.size(ncols)==2: # then we have a hex grid - - # Find the indices corresponding to the left and right edges of each row - rowlims=np.zeros([nrows,2], dtype=np.uint32) - - l = 0 - for i in range(1, nrows+1): - - b = ~util.iseven(i) - if b[0]: - r = l + ncols[0] - 1 - else: - r = l + ncols[0] - 2 - - rowlims[i-1, 0] = l - rowlims[i-1, 1] = r - - l = r + 1 - - elif np.size(ncols)==1: # then we have a square grid - - rowlims=np.zeros([nrows,2]) - - l = 0 - for i in range(1, nrows+1): - - r = l + ncols - 1 - - rowlims[i-1, 0] = l - rowlims[i-1, 1] = r - - l = r + 1 - - return rowlims - -#------------------------------------------------------------------------------- - -def kernverts(x, y, rows, cols, xstp, ystp, idx, clipverts=True): - ''' - KERNVERTS returns the grid of vertices corresponding to the boundaries of - the data point kernels in an ebsd scan, as well as the indices corresponding - to the edges of the rows in this grid. The results from the edges here - differ from scanrowlims: In scanrowlims, we return the limits of the rows - for the measurement grid. Here, we return the limits of the rows of the - vertices grid. - - inputs: x and y coordinates of scan - ''' - import numpy as np - import cryspy.util as util - - if np.size(cols)==2: - - # Make hex grid - xv1 = xstp / 2.0 - yv1 = ystp / np.sqrt(3.0) - yv2 = yv1 / 2.0 - - # first row, we need to cover the indices that are above the face nodes - v = np.zeros([(rows+1)*(2*cols[0]+1), 2], dtype=float) - p = np.zeros([2 * rows + 2, 2], dtype=np.uint32) - yy = y[[idx[0][0]]] - v1 = cols[0] - v2 = cols[0] + 1 - xvs1 = np.arange(1, v1+1) * xstp - xstp - xvs2 = np.arange(1, v2+1) * xstp - xstp - v[0:v1, 0] = xvs1 - v[0:v1, 1] = np.tile(yy - yv1, v1) - p[0, 0] = 0 - p[0, 1] = v1-1 - - v[v1:v1+v2, 0] = xvs2 - xv1 - v[v1:v1+v2, 1] = np.tile(yy - yv2, v2) - p[1, 0] = v1 - p[1, 1] = v1+v2-1 - - # All other nodes - nV=v1+v2 - j=2 - for i in range(1, rows+1): - yy = y[idx[i-1][0]] - v1 = cols[0] - v2 = cols[0] + 1 - xvs1 = np.arange(1, v1+1) * xstp - xstp - xvs2 = np.arange(1, v2+1) * xstp - xstp - - if ~util.iseven(i): - - v[nV:nV+v2, 0] = xvs2-xv1 - v[nV:nV+v2, 1] = np.tile(yy+yv2, v2) - p[j, 0] = nV - p[j, 1] = nV+v2-1 - j += 1 - v[nV+v2:nV+v2+v1, 0] = xvs1 - v[nV+v2:nV+v2+v1, 1] = np.tile(yy+yv1, v1) - p[j, 0] = nV+v2 - p[j, 1] = nV+v2+v1-1 - j += 1 - nV=nV+v1+v2 - - else: - - v[nV:nV+v1, 0] = xvs1 - v[nV:nV+v1, 1] = np.tile(yy+yv2, v1) - p[j, 0] = nV - p[j, 1] = nV+v1-1 - j += 1 - v[nV+v1:nV+v1+v2, 0] = xvs2-xv1 - v[nV+v1:nV+v1+v2, 1] = np.tile(yy+yv1, v2) - p[j, 0] = nV+v1 - p[j, 1] = nV+v1+v2-1 - j += 1 - nV=nV+v1+v2 - - elif np.size(cols)==1: # square grid - - v = np.zeros([(cols+1)*(rows+1), 2], dtype=float) - p = np.zeros([rows+1, 2], dtype=np.uint32) - - v[:,0] = np.tile(xstp*np.arange(0,cols+1),rows+1)-xstp/2.0 - v[:,1] = np.reshape(np.tile(ystp*np.arange(0,rows+1), - (cols+1,1)).T-ystp/2.0, -1) - - p[:,0] = np.arange(1,(rows+1)*(cols+1),cols+1)-1 - p[:,1] = np.arange(1,(rows+1)*(cols+1),cols+1)-1+cols - - if clipverts==True and np.size(cols)==2: - # Fix the top row of points - v[p[0,0]:p[0,1]+1][:,1] = np.tile(y[idx[0,0]]-np.sqrt(np.spacing(1)), - (p[0,1]-p[0,0]+1)) - v[p[1,0]:p[1,1]+1][:,1] = np.tile(y[idx[0,0]], (p[1,1]-p[1,0]+1)) - - # Fix the bottom row of points - ploc = np.shape(p)[0]-1 - iloc = np.shape(idx)[0]-1 - v[p[ploc,0]:p[ploc,1]+1][:,1] = np.tile(y[idx[iloc,0]] + - np.sqrt(np.spacing(1)), - (p[ploc,1]-p[ploc,0]+1)) - v[p[ploc-1,0]:p[ploc-1,1]+1][:,1] = np.tile(y[idx[iloc,0]], - (p[ploc-1,1]-p[ploc-1,0]+1)) - - # Fix the left and right columns of points - for i in range(0,np.size(p[:,0])-1): - valmin = np.amin(x)-np.sqrt(np.spacing(1)) - valmax = np.amax(x)+np.sqrt(np.spacing(1)) - v[p[i,0]][0] = valmin - v[p[i,1]][0] = valmax - - - return v, p # v is the array of vertices, p is the array of edge indices - -#------------------------------------------------------------------------------ - -def kernfaces(v, p, idx, rows, cols, nr): - ''' - KERNFACES associates the faces of the kernels with their surrounding - vertices, for both hexagonal and square grid patterns. - ''' - import numpy as np - import cryspy.util as util - - # Associate faces with vertices - ndx=0; - - if np.size(cols)==2: # hex grid - - pb = util.progbar(finalcount=nr, - message='ASSOCIATING FACES WITH VERTICES') - - f=np.zeros([nr,6], dtype=np.uint32) - for i in range(1,rows+1): - - j=(i-1)*2+1 - - if ~util.iseven(i): # odd row - - tmp=np.array([ - np.arange( p[j-1,0] , p[j-1,1]+1), - np.arange( p[j ,0], p[j ,1] ), - np.arange( p[j ,0]+1, p[j ,1]+1), - np.arange( p[j+1,0], p[j+1,1] ), - np.arange( p[j+1,0]+1, p[j+1,1]+1), - np.arange( p[j+2,0], p[j+2,1]+1) ]).T - - else: # even row - - tmp=np.array([ - np.arange(p[j-1,0]+1, p[j-1,1] ), - np.arange(p[j ,0], p[j ,1] ), - np.arange(p[j ,0]+1, p[j ,1]+1), - np.arange(p[j+1,0], p[j+1,1] ), - np.arange(p[j+1,0]+1, p[j+1,1]+1), - np.arange(p[j+2,0]+1, p[j+2,1] ) ]).T - - - ntmp = np.size(tmp)/6 - - for k in range(0, ntmp): - f[ndx, 0:6] = [tmp[k,0], tmp[k,1], tmp[k,3], tmp[k,5], tmp[k,4], tmp[k,2]] - ndx += 1 - - pb.update(i) - - pb.update(-1) - - elif np.size(cols)==1: # square grid - - f = np.zeros([nr, 4], dtype=np.uint32) - f1 = np.arange(0, cols, dtype=np.uint32) - f2 = f1 + 1 - for i in range(0,rows): - f3 = np.arange(0, cols, dtype=np.uint32) + (cols + 1) * (i + 1) - f4 = f3 + 1 - f[idx[i,0]:idx[i,1]+1, 0] = f3 - f[idx[i,0]:idx[i,1]+1, 1] = f1 - f[idx[i,0]:idx[i,1]+1, 2] = f2 - f[idx[i,0]:idx[i,1]+1, 3] = f4 - f1 = f3 - f2 = f4 - - return f - -#------------------------------------------------------------------------------ - -def triverts(n, v, f, cols): - #TODO: Documentation! - - import numpy as np - - # Add third dimension to vertex data - tv = np.zeros([np.size(v)/2, 3]) - tv[:,0] = v[:,0] - tv[:,1] = v[:,1] - del v - - # Create the face array - if np.size(cols)==2: # hex grid - - vf = np.zeros([n*4,3], dtype=np.uint32) - k = 0 - for i in range(0,n): - - # triangle 1 - vf[k,0] = f[i,0] - vf[k,1] = f[i,1] - vf[k,2] = f[i,5] - k += 1 - - # triangle 2 - vf[k,0] = f[i,5] - vf[k,1] = f[i,1] - vf[k,2] = f[i,4] - k += 1 - - # triangle 3 - vf[k,0] = f[i,2] - vf[k,1] = f[i,4] - vf[k,2] = f[i,1] - k += 1 - - # triangle 4 - vf[k,0] = f[i,3] - vf[k,1] = f[i,4] - vf[k,2] = f[i,2] - k += 1 - - else: # square grid - - vf = np.zeros([n*2,3], dtype=np.uint32) - k = 0 - for i in range(0,n): - - # triangle 1 - vf[k,0] = f[i,1] - vf[k,1] = f[i,2] - vf[k,2] = f[i,0] - k += 1 - - # triangle 2 - vf[k,0] = f[i,2] - vf[k,1] = f[i,3] - vf[k,2] = f[i,0] - k += 1 - - return tv, vf - -#------------------------------------------------------------------------------ - - - - - - - - +# -*- coding: utf-8 -*- +""" +ovlib.ebsd: EBSD data analysis +=============================================================================== +""" + +class ebsd(object): + ''' + EBSD data object + + Parameters + ---------- + orientations : + + cryspy.xtal.orientation object + + x : numpy array of floats + + x locations of orientations + + y : numpy array of floats + + y locations of orientations + + phaseid : numpy array of ints + + phase identifiers + + Notes + ----- + - At minimum, an EBSD object is an orientation object with a phase and + x and y positions associated with each individual orientation. + + ''' + def __init__(self, orientations=[], x=[], y=[], phaseid=[]): + # initialize the minimum amount of information in order to have EBSD + # data. We need to have x and y coordinates and an orientation measured + # at those coordinates. + self.orientations = orientations + self.x = x + self.y = y + self.phaseid = phaseid + + #def __repr__(self): + # TODO: Add __repr__ method + # Needs to show number of phases, number of data points, etc. + + def calc_scanstepsize(self): + ''' adds step and scan size data to the ebsd object + + EBSD class method that uses x and y location information to add step + size and scan size data to the ebsd object. + ''' + import numpy as np + nr = np.size(self.x) + rows, cols = scansize(self.x, self.y) + xstp, ystp = scanstep(self.x, self.y, rows, cols) + idx = scanrowlims(self.x, self.y, rows, cols) + self.rows = rows + self.cols = cols + self.xstp = xstp + self.ystp = ystp + self.idx = idx + self.nr = nr + + def prepare_for_plotting(self): + ''' adds information to ebsd object required for plotting + + class method that adds information necessary for plotting to the object + ''' + import numpy as np + + if not hasattr(self, 'x') or not hasattr(self, 'y'): + print('x and y coordinates are needed for plotting ebsd data.') #TODO: Use warnings package? + + if not hasattr(self, 'rows') or not hasattr(self, 'cols'): + rows, cols = scansize(self.x, self.y) + self.rows = rows + self.cols = cols + del rows, cols + + if not hasattr(self, 'xstp') or not hasattr(self, 'ystp'): + xstp, ystp = scanstep(self.x, self.y, self.rows, self.cols) + self.xstp = xstp + self.ystp = ystp + del xstp, ystp + + if not hasattr(self, 'idx'): + idx = scanrowlims(self.x, self.y, self.rows, self.cols) + self.idx = idx + del idx + + if not hasattr(self, 'nr'): + nr = np.size(self.x) + self.nr = nr + del nr + + # Kernel vertices and faces are needed for plotting similar to matlab's + # patch function using matplotlib + v, p = kernverts(self.x, self.y, self.rows, self.cols, + self.xstp, self.ystp, self.idx) + f = kernfaces(v, p, self.idx, self.rows, self.cols, self.nr) + self.v = v + self.p = p + self.f = f + + # For plotting using opengl, we need a different format of vertices and + # faces + tv, vf = triverts(self.nr, self.v, self.f, self.cols) + self.tv = tv + self.vf = vf + +#------------------------------------------------------------------------------- + +def scansize(x, y): + """ + scansize returns the number of rows and columns in oimdata on either + a square or hexagonal grid. Two values for the # columns are + returned in the case of a hex grid: the first for odd and the + second for even. See hdrload for correct importing of the + data matrix. + + Notes + ----- + + This one isn't as straightforward as you might assume. + The data can come on either a hex grid or a square + grid. When on a hex grid, the data is staggered between + rows, so the number of data points differs between rows, + and the x-coordinate varies while the y-coordinate is + constant within each row. + + If there is no header for the data, or if the header doesn't match the + file contents, we can figure out this info from looking at the XY data. + + Ultimately, the old-school way I am using here to count + the number of vertical pixels below isn't efficient but + doesn't take very long. + + To get the oimdata input variable, see hdrload. + + If the input file is on a hex grid, columns returns first the odd + then the even number. + """ + import numpy as np + + if np.size(x) != np.size(y): + print('scansize: the lengths of x and y are not equal. this may result\ + in errors.') #TODO: use warnings package? + + # count the number of pixels in the horizontal direction + # i.e, the maximum number of columns + # (even numbered rows have fewer columns in the scan) and + # the first row (which is always ==0 in my TSL scans) is + # by definition an odd row. + cols1=np.sum(y==0) # cols in first row + + # if the first entry for the x-coordinate value in the first row + # doesn't match the first entry for the x-coordinate value in the + # second row, then it's hexagonal. This could maybe also be done + # using the 'unique' function. + if x[cols1] != x[0]: + rows = np.sum(x==0) + np.sum(x==x[cols1]) + cols = np.array([1,2], dtype=np.uint32) + cols[0]=cols1 + cols[1]=cols[0]-1 + else: + nr = np.size(x) + rows= np.uint32(nr / cols1) + cols= cols1 + + return rows, cols + +#------------------------------------------------------------------------------- + +def scanstep(x, y, nr, nc): + ''' finds the x and y step sizes of an ebsd scan + + Parameters + ---------- + x : array + x locations + y : array + y locations + nr : scalar + number of rows + nc : array + number of columns (can be 2 vector for hexagonal grid) + + Returns + ------- + xstep : scalar + step size in x direction + ystep : scalar + step size in y direction + ''' + import numpy as np + + if np.size(x) != np.size(y): + print('scanstep: the lengths of x and y are not equal. this may result\ + in errors.') # TODO: use warnings package? + + # Get the scan size + if np.size(nc)==2: # hexagonal scan + nc=nc[0]; + + # Get the hex data x step size + if nr<5: + # we need at least two points in the row to get the step size + # empirically (or we could read the header, but I think this method is + # generally better in case the header doesn't match the data). + # This means a hex scan size of at least 5 points is a reasonable + # minimum: 2 points in the first and third rows, 1 point in the middle + # row. + print('scanstep error: at least 5 data points required') # TODO: warnings? + else: + xstep = x[1]-x[0] + ystep = y[nc]-y[nc-1] + + return xstep, ystep + +#------------------------------------------------------------------------------- + +def scanrowlims(x, y, nrows, ncols): + ''' + + Notes + ----- + SCANROWLIMS finds the indices that correspond to the left and right + limits of each row. Assumes a regular hexagonal or square grid. + + The algorithm below is slow, but since it typically only needs to be run + once for a scan in a session, it seems ok for the time being. + ''' + import numpy as np + import righthand as rh + + if np.size(x) != np.size(y): + print('scanrowlims: the lengths of x and y are not equal. this may \ + result in errors.') # TODO: warnings? + + if np.size(ncols)==2: # then we have a hex grid + + # Find the indices corresponding to the left and right edges of each row + rowlims=np.zeros([nrows, 2], dtype=np.uint32) + + l = 0 + for i in range(1, nrows+1): + + b = ~rh.iseven(i) + if b: + r = l + ncols[0] - 1 + else: + r = l + ncols[0] - 2 + + rowlims[i-1, 0] = l + rowlims[i-1, 1] = r + + l = r + 1 + + elif np.size(ncols)==1: # then we have a square grid + + rowlims=np.zeros([nrows, 2], dtype=np.uint32) + + l = 0 + for i in range(1, nrows+1): + + r = l + ncols - 1 + + rowlims[i-1, 0] = l + rowlims[i-1, 1] = r + + l = r + 1 + + return rowlims + +#------------------------------------------------------------------------------- + +def kernverts(x, y, rows, cols, xstp, ystp, idx, clipverts=True): + ''' + KERNVERTS returns the grid of vertices corresponding to the boundaries of + the data point kernels in an ebsd scan, as well as the indices corresponding + to the edges of the rows in this grid. The results from the edges here + differ from scanrowlims: In scanrowlims, we return the limits of the rows + for the measurement grid. Here, we return the limits of the rows of the + vertices grid. + + inputs: x and y coordinates of scan + ''' + import numpy as np + import righthand as rh + + if np.size(cols)==2: + + # Make hex grid + xv1 = xstp / 2.0 + yv1 = ystp / np.sqrt(3.0) + yv2 = yv1 / 2.0 + + # first row, we need to cover the indices that are above the face nodes + v = np.zeros([(rows+1)*(2*cols[0]+1), 2], dtype=float) + p = np.zeros([2 * rows + 2, 2], dtype=np.uint32) + yy = y[[idx[0][0]]] + v1 = cols[0] + v2 = cols[0] + 1 + xvs1 = np.arange(1, v1+1) * xstp - xstp + xvs2 = np.arange(1, v2+1) * xstp - xstp + v[0:v1, 0] = xvs1 + v[0:v1, 1] = np.tile(yy - yv1, v1) + p[0, 0] = 0 + p[0, 1] = v1-1 + + v[v1:v1+v2, 0] = xvs2 - xv1 + v[v1:v1+v2, 1] = np.tile(yy - yv2, v2) + p[1, 0] = v1 + p[1, 1] = v1+v2-1 + + # All other nodes + nV=v1+v2 + j=2 + for i in range(1, rows+1): + yy = y[idx[i-1][0]] + v1 = cols[0] + v2 = cols[0] + 1 + xvs1 = np.arange(1, v1+1) * xstp - xstp + xvs2 = np.arange(1, v2+1) * xstp - xstp + + if ~rh.iseven(i): + + v[nV:nV+v2, 0] = xvs2-xv1 + v[nV:nV+v2, 1] = np.tile(yy+yv2, v2) + p[j, 0] = nV + p[j, 1] = nV+v2-1 + j += 1 + v[nV+v2:nV+v2+v1, 0] = xvs1 + v[nV+v2:nV+v2+v1, 1] = np.tile(yy+yv1, v1) + p[j, 0] = nV+v2 + p[j, 1] = nV+v2+v1-1 + j += 1 + nV=nV+v1+v2 + + else: + + v[nV:nV+v1, 0] = xvs1 + v[nV:nV+v1, 1] = np.tile(yy+yv2, v1) + p[j, 0] = nV + p[j, 1] = nV+v1-1 + j += 1 + v[nV+v1:nV+v1+v2, 0] = xvs2-xv1 + v[nV+v1:nV+v1+v2, 1] = np.tile(yy+yv1, v2) + p[j, 0] = nV+v1 + p[j, 1] = nV+v1+v2-1 + j += 1 + nV=nV+v1+v2 + + elif np.size(cols)==1: # square grid + + v = np.zeros([(cols+1)*(rows+1), 2], dtype=float) + p = np.zeros([rows+1, 2], dtype=np.uint32) + + v[:,0] = np.tile(xstp*np.arange(0,cols+1),rows+1)-xstp/2.0 + v[:,1] = np.reshape(np.tile(ystp*np.arange(0,rows+1), + (cols+1,1)).T-ystp/2.0, -1) + + p[:,0] = np.arange(1,(rows+1)*(cols+1),cols+1)-1 + p[:,1] = np.arange(1,(rows+1)*(cols+1),cols+1)-1+cols + + if clipverts==True and np.size(cols)==2: + # Fix the top row of points + v[p[0,0]:p[0,1]+1][:,1] = np.tile(y[idx[0,0]]-np.sqrt(np.spacing(1)), + (p[0,1]-p[0,0]+1)) + v[p[1,0]:p[1,1]+1][:,1] = np.tile(y[idx[0,0]], (p[1,1]-p[1,0]+1)) + + # Fix the bottom row of points + ploc = np.shape(p)[0]-1 + iloc = np.shape(idx)[0]-1 + v[p[ploc,0]:p[ploc,1]+1][:,1] = np.tile(y[idx[iloc,0]] + + np.sqrt(np.spacing(1)), + (p[ploc,1]-p[ploc,0]+1)) + v[p[ploc-1,0]:p[ploc-1,1]+1][:,1] = np.tile(y[idx[iloc,0]], + (p[ploc-1,1]-p[ploc-1,0]+1)) + + # Fix the left and right columns of points + for i in range(0,np.size(p[:,0])-1): + valmin = np.amin(x)-np.sqrt(np.spacing(1)) + valmax = np.amax(x)+np.sqrt(np.spacing(1)) + v[p[i,0]][0] = valmin + v[p[i,1]][0] = valmax + + + return v, p # v is the array of vertices, p is the array of edge indices + +#------------------------------------------------------------------------------ + +def kernfaces(v, p, idx, rows, cols, nr): + ''' + KERNFACES associates the faces of the kernels with their surrounding + vertices, for both hexagonal and square grid patterns. + ''' + import numpy as np + import righthand as rh + + # Associate faces with vertices + ndx=0; + + if np.size(cols)==2: # hex grid + + pb = rh.progbar(finalcount=nr, + message='ASSOCIATING FACES WITH VERTICES') + + f=np.zeros([nr,6], dtype=np.uint32) + for i in range(1,rows+1): + + j=(i-1)*2+1 + + if ~rh.iseven(i): # odd row + + tmp=np.array([ + np.arange( p[j-1,0] , p[j-1,1]+1), + np.arange( p[j ,0], p[j ,1] ), + np.arange( p[j ,0]+1, p[j ,1]+1), + np.arange( p[j+1,0], p[j+1,1] ), + np.arange( p[j+1,0]+1, p[j+1,1]+1), + np.arange( p[j+2,0], p[j+2,1]+1) ]).T + + else: # even row + + tmp=np.array([ + np.arange(p[j-1,0]+1, p[j-1,1] ), + np.arange(p[j ,0], p[j ,1] ), + np.arange(p[j ,0]+1, p[j ,1]+1), + np.arange(p[j+1,0], p[j+1,1] ), + np.arange(p[j+1,0]+1, p[j+1,1]+1), + np.arange(p[j+2,0]+1, p[j+2,1] ) ]).T + + + ntmp = np.size(tmp) / 6 + + for k in range(0, np.int(ntmp)): #XXX: Converting to int here could cause an issue if ntmp is not actually an int + f[ndx, 0:6] = [tmp[k,0], tmp[k,1], tmp[k,3], tmp[k,5], tmp[k,4], tmp[k,2]] + ndx += 1 + + pb.update(i) + + pb.update(-1) + + elif np.size(cols)==1: # square grid + + f = np.zeros([nr, 4], dtype=np.uint32) + f1 = np.arange(0, cols, dtype=np.uint32) + f2 = f1 + 1 + for i in range(0,rows): + f3 = np.arange(0, cols, dtype=np.uint32) + (cols + 1) * (i + 1) + f4 = f3 + 1 + f[idx[i,0]:idx[i,1]+1, 0] = f3 + f[idx[i,0]:idx[i,1]+1, 1] = f1 + f[idx[i,0]:idx[i,1]+1, 2] = f2 + f[idx[i,0]:idx[i,1]+1, 3] = f4 + f1 = f3 + f2 = f4 + + return f + +#------------------------------------------------------------------------------ + +def triverts(n, v, f, cols): + #TODO: Documentation! + + import numpy as np + + # Add third dimension to vertex data + tv = np.zeros([np.int(np.size(v)/2.0), 3]) # there are two columns, so size/2 will always be int + tv[:,0] = v[:,0] + tv[:,1] = v[:,1] + del v + + # Create the face array + if np.size(cols)==2: # hex grid + + vf = np.zeros([n*4,3], dtype=np.uint32) + k = 0 + for i in range(0,n): + + # triangle 1 + vf[k,0] = f[i,0] + vf[k,1] = f[i,1] + vf[k,2] = f[i,5] + k += 1 + + # triangle 2 + vf[k,0] = f[i,5] + vf[k,1] = f[i,1] + vf[k,2] = f[i,4] + k += 1 + + # triangle 3 + vf[k,0] = f[i,2] + vf[k,1] = f[i,4] + vf[k,2] = f[i,1] + k += 1 + + # triangle 4 + vf[k,0] = f[i,3] + vf[k,1] = f[i,4] + vf[k,2] = f[i,2] + k += 1 + + else: # square grid + + vf = np.zeros([n*2,3], dtype=np.uint32) + k = 0 + for i in range(0,n): + + # triangle 1 + vf[k,0] = f[i,1] + vf[k,1] = f[i,2] + vf[k,2] = f[i,0] + k += 1 + + # triangle 2 + vf[k,0] = f[i,2] + vf[k,1] = f[i,3] + vf[k,2] = f[i,0] + k += 1 + + return tv, vf + +#------------------------------------------------------------------------------ + + + + + + + + diff --git a/cryspy/orrl.py b/cryspy/orrl.py new file mode 100755 index 0000000..f3ad4c4 --- /dev/null +++ b/cryspy/orrl.py @@ -0,0 +1,3270 @@ +# -*- coding: utf-8 -*- +''' +ovlib.orrl: Orientation relationship characterization +=============================================================================== +''' + +def namedOR(name): + """ Returns ksi values for named orientation relationships + + Parameters + ---------- + name : {'ks', 'nw', 'bain'} + Orientation relationship name + + Returns + ------- + ksi_values : 1x3 numpy array of floats + ksi values in degrees + + Notes + ----- + TODO: Add plane parallel Greninger-Troiano, Kelly, etc. + TODO: Allow 'Kurdjumov-Sachs' as well as 'ks', etc. + """ + import numpy as np + + if isinstance(name, str): + + if name.lower() == 'ks': + + s6 = np.sqrt(6.0) + s3 = np.sqrt(3.0) + ksi1 = np.arccos((s6 + 1.0) / (2.0 * s3)) + ksi2 = np.arccos((s6 + 18.0) / (12.0 * s3)) + ksi3 = np.arccos((s6 + 12.0) / (6.0 * s6)) + ksi = np.array([ksi1, ksi2, ksi3]) + del s6, s3, ksi1, ksi2, ksi3 + + elif name.lower() == 'nw': + + s6 = np.sqrt(6) + s2 = np.sqrt(2) + ksi0 = np.arccos((s2 + 1.0) / s6) + ksi = np.array([0.0, ksi0, ksi0]) + + elif name.lower() == 'bain': + + ksi = np.array([0.0, 0.0, 0.0]) + + else: + + print('namedOR: Unrecognized named OR') # TODO: use warnings package + + else: + + print('namedOR requires a string input. Returning Bain.') # TODO: use warnings package + ksi = np.array([0.0, 0.0, 0.0]) + + return ksi * 180.0/np.pi + +def getRepresentativeVariant(ksi_values): + """ Returns quaternion of representative variant + + Parameters + ---------- + ksi_values : + + Returns + ------- + psiq : quat class + """ + import numpy as np + from cryspy.rot import quat + + if isinstance(ksi_values, str): + # Import named OR and convert to radians + orrl = namedOR(ksi_values) * np.pi / 180.0 + else: + # Convert OR specification into radians + orrl = ksi_values * np.pi / 180.0 + + csum = np.cos(orrl[0])+np.cos(orrl[1])+np.cos(orrl[2]) + x0 = 0.5 * np.sqrt(csum + 1.0) + x = 0.5 * np.sqrt(2. * np.cos(orrl) - csum + 1.0) + + ksiq = quat(x0, x[0], x[1], x[2]) + + # Calculate quaternion for normalized Bain correspondence. + + qw = 0.5 * np.sqrt(2.0 + np.sqrt(2.0)) + qz = (2.0 / np.sqrt(2.0)) / (4.0 * qw) + gammaq = quat(qw, 0.0, 0.0, qz); + + psiq = ksiq * gammaq + + return psiq + +# -------------------------------------------------------------------------- + +def generateVariants(ksi_values): + """ Generate variants from Kurdjumov-Sachs angles + + Returns matrices of an orientation relationship specified in Kurjumov-Sachs + angles. + + Parameters + ---------- + ksi_values : length 3 iterable OR {'KS', 'NW', 'Bain'} + + Returns + ------- + vv : rmat object + rotation matrices corresponding to variants + + """ + import numpy as np + import cryspy.rot as rot + import cryspy.util as util + import righthand as rh + import warnings + + if isinstance(ksi_values, str): + + ksi = namedOR(ksi_values) + + elif np.shape(ksi_values) == (3,): + + ksi = np.asanyarray(ksi_values) + + else: + + warnings.warn('ksi values must be a numeric iterable of length 3.') + + # convert ksi radians to rotation matrices + + ksi = ksi * np.pi / 180.0 + + mb = np.zeros([2, 9]) + + mb[0, 0] = np.cos(ksi[0]) + mb[0, 4] = np.cos(ksi[1]) + mb[0, 8] = np.cos(ksi[2]) + + costh = 0.5 * (np.sum(np.cos(ksi)) - 1.0) # sum(cos(ksi)) is the matrix trace + mosth = 1.0 - costh + sinth = np.sqrt(1.0 - costh**2.0) + + + r1 = np.sqrt((mb[0, 0] - costh) / mosth) + r2 = np.sqrt((mb[0, 4] - costh) / mosth) + r3 = np.sqrt((mb[0, 8] - costh) / mosth) + del costh + + r1r2 = r1 * r2 * mosth + r1r3 = r1 * r3 * mosth + r2r3 = r2 * r3 * mosth + r3st = r3 * sinth + r2st = r2 * sinth + r1st = r1 * sinth + del r1, r2, r3, mosth, sinth + + mb[0, 5] = r2r3 - r1st + mb[0, 7] = r2r3 + r1st + mb[1, :] = mb[0, :] + + mb[0, 1] = -r1r2 + r3st + mb[0, 2] = -r1r3 - r2st + mb[0, 3] = -r1r2 - r3st + mb[0, 6] = -r1r3 + r2st + del r1r2, r1r3, r2r3, r3st, r2st, r1st + + mb[1, 1] = -mb[0, 1] + mb[1, 2] = -mb[0, 2] + mb[1, 3] = -mb[0, 3] + mb[1, 6] = -mb[0, 6] + # mb[0] is the 'positive' solution; mb[1] is the 'negative' solution + + # create Bain correspondence matrices + bb = np.zeros([12, 9]) + bb[ 0, :] = [ 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0] + bb[ 1, :] = [ 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0] + bb[ 2, :] = [-1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] + bb[ 3, :] = [ 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 0.0] + bb[ 4, :] = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0] + bb[ 5, :] = [ 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0] + bb[ 6, :] = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0] + bb[ 7, :] = [-1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0] + bb[ 8, :] = [ 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 0.0] + bb[ 9, :] = [ 1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0] + bb[10, :] = [ 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 0.0] + bb[11, :] = [-1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, -1.0] + + # normalize correspondence matrices + bb = rot.rmat.from_array(bb / util.vecarraynorm(bb)) + mb = rot.rmat.from_array(mb) + + # produce variants + vv = np.zeros([24, 9]) + tmp = mb[0] * bb + vv[np.arange(0, 24, 2), :] = tmp.to_array() + tmp = mb[1] * bb + vv[np.arange(1, 24, 2), :] = tmp.to_array() + + # reduce redundancies, if they exist (as they do, for example, in NW) + vv, ia, ic = rh.uniquerows(rh.sigdec(vv, 7)) + del ia, ic + + return rot.rmat.from_array(vv) + +#------------------------------------------------------------------------------ + +def _bg_ksi1(bincenters): + """ returns ksi1 background + + Parameters + ---------- + bincenters : numpy array + centers of bins for ksi1 background + + Returns + ------- + bg : numpy array + background number fractions for ksi1 + + Notes + ----- + Background is determined by cubic spline interpolation from a large + numerical simulation containing 17156424 ksi values measured from + random orientations. + + """ + import numpy as np + import scipy.interpolate as interp + + d = np.array([ [ 0.00000000e+00, 0.00000000e+00], + [ 1.50000000e-01, 8.04400000e+03], + [ 4.50000000e-01, 2.45720000e+04], + [ 7.50000000e-01, 4.06200000e+04], + [ 1.05000000e+00, 5.61680000e+04], + [ 1.35000000e+00, 7.13330000e+04], + [ 1.65000000e+00, 8.80860000e+04], + [ 1.95000000e+00, 1.02050000e+05], + [ 2.25000000e+00, 1.17010000e+05], + [ 2.55000000e+00, 1.31760000e+05], + [ 2.85000000e+00, 1.47720000e+05], + [ 3.15000000e+00, 1.60180000e+05], + [ 3.45000000e+00, 1.75440000e+05], + [ 3.75000000e+00, 1.88830000e+05], + [ 4.05000000e+00, 2.03510000e+05], + [ 4.35000000e+00, 2.16520000e+05], + [ 4.65000000e+00, 2.29790000e+05], + [ 4.95000000e+00, 2.43770000e+05], + [ 5.25000000e+00, 2.56080000e+05], + [ 5.55000000e+00, 2.68910000e+05], + [ 5.85000000e+00, 2.81760000e+05], + [ 6.15000000e+00, 2.93690000e+05], + [ 6.45000000e+00, 3.06460000e+05], + [ 6.75000000e+00, 3.16310000e+05], + [ 7.05000000e+00, 3.27680000e+05], + [ 7.35000000e+00, 3.40790000e+05], + [ 7.65000000e+00, 3.50150000e+05], + [ 7.95000000e+00, 3.63730000e+05], + [ 8.25000000e+00, 3.74700000e+05], + [ 8.55000000e+00, 3.83250000e+05], + [ 8.85000000e+00, 3.93360000e+05], + [ 9.15000000e+00, 4.01330000e+05], + [ 9.45000000e+00, 4.08800000e+05], + [ 9.75000000e+00, 4.10170000e+05], + [ 1.00500000e+01, 4.16810000e+05], + [ 1.03500000e+01, 4.22100000e+05], + [ 1.06500000e+01, 4.24130000e+05], + [ 1.09500000e+01, 4.25640000e+05], + [ 1.12500000e+01, 4.26720000e+05], + [ 1.15500000e+01, 4.26860000e+05], + [ 1.18500000e+01, 4.25850000e+05], + [ 1.21500000e+01, 4.25010000e+05], + [ 1.24500000e+01, 4.19670000e+05], + [ 1.27500000e+01, 4.14030000e+05], + [ 1.30500000e+01, 4.06510000e+05], + [ 1.33500000e+01, 3.97900000e+05], + [ 1.36500000e+01, 3.85480000e+05], + [ 1.39500000e+01, 3.72070000e+05], + [ 1.42500000e+01, 3.55970000e+05], + [ 1.45500000e+01, 3.36740000e+05], + [ 1.48500000e+01, 3.11690000e+05], + [ 1.51500000e+01, 2.79820000e+05], + [ 1.54500000e+01, 2.57130000e+05], + [ 1.57500000e+01, 2.35370000e+05], + [ 1.60500000e+01, 2.18230000e+05], + [ 1.63500000e+01, 2.01140000e+05], + [ 1.66500000e+01, 1.84260000e+05], + [ 1.69500000e+01, 1.68320000e+05], + [ 1.72500000e+01, 1.54590000e+05], + [ 1.75500000e+01, 1.39760000e+05], + [ 1.78500000e+01, 1.27400000e+05], + [ 1.81500000e+01, 1.14020000e+05], + [ 1.84500000e+01, 1.03180000e+05], + [ 1.87500000e+01, 9.14750000e+04], + [ 1.90500000e+01, 8.00980000e+04], + [ 1.93500000e+01, 7.07470000e+04], + [ 1.96500000e+01, 6.10580000e+04], + [ 1.99500000e+01, 5.08750000e+04], + [ 2.02500000e+01, 4.25510000e+04], + [ 2.05500000e+01, 3.39610000e+04], + [ 2.08500000e+01, 2.52270000e+04], + [ 2.11500000e+01, 1.73040000e+04], + [ 2.14500000e+01, 1.12930000e+04], + [ 2.17500000e+01, 7.17200000e+03], + [ 2.20500000e+01, 3.76000000e+03], + [ 2.23500000e+01, 1.55200000e+03], + [ 2.26500000e+01, 3.94000000e+02], + [ 2.29500000e+01, 8.00000000e+00], + [ 2.32500000e+01, 0.00000000e+00], + [ 2.35500000e+01, 0.00000000e+00], + [ 2.38500000e+01, 0.00000000e+00], + [ 2.41500000e+01, 0.00000000e+00], + [ 2.44500000e+01, 0.00000000e+00], + [ 2.47500000e+01, 0.00000000e+00], + [ 2.50500000e+01, 0.00000000e+00], + [ 2.53500000e+01, 0.00000000e+00], + [ 2.56500000e+01, 0.00000000e+00], + [ 2.59500000e+01, 0.00000000e+00], + [ 2.62500000e+01, 0.00000000e+00], + [ 2.65500000e+01, 0.00000000e+00], + [ 2.68500000e+01, 0.00000000e+00], + [ 2.71500000e+01, 0.00000000e+00], + [ 2.74500000e+01, 0.00000000e+00], + [ 2.77500000e+01, 0.00000000e+00], + [ 2.80500000e+01, 0.00000000e+00], + [ 2.83500000e+01, 0.00000000e+00], + [ 2.86500000e+01, 0.00000000e+00], + [ 2.89500000e+01, 0.00000000e+00], + [ 2.92500000e+01, 0.00000000e+00], + [ 2.95500000e+01, 0.00000000e+00], + [ 2.98500000e+01, 0.00000000e+00], + [ 3.01500000e+01, 0.00000000e+00], + [ 3.04500000e+01, 0.00000000e+00], + [ 3.07500000e+01, 0.00000000e+00], + [ 3.10500000e+01, 0.00000000e+00], + [ 3.13500000e+01, 0.00000000e+00], + [ 3.16500000e+01, 0.00000000e+00], + [ 3.19500000e+01, 0.00000000e+00], + [ 3.22500000e+01, 0.00000000e+00], + [ 3.25500000e+01, 0.00000000e+00], + [ 3.28500000e+01, 0.00000000e+00], + [ 3.31500000e+01, 0.00000000e+00], + [ 3.34500000e+01, 0.00000000e+00], + [ 3.37500000e+01, 0.00000000e+00], + [ 3.40500000e+01, 0.00000000e+00], + [ 3.43500000e+01, 0.00000000e+00], + [ 3.46500000e+01, 0.00000000e+00]]) + + minval = np.sqrt(np.spacing(np.float32(1))) + cj = interp.UnivariateSpline(d[:,0], + d[:,1]+minval, + k=1, s=0.5) + + # Interpolate the spline for our bin array into a background for our histogram + bg = cj(bincenters) + bg[bg < minval] = minval # do not allow division by zero + + return bg + +def _bg_ksi2(bincenters): + """ returns ksi1 background + + Parameters + ---------- + bincenters : numpy array + centers of bins for ksi1 background + + Returns + ------- + bg : numpy array + background number fractions for ksi1 + + Notes + ----- + Background is determined by cubic spline interpolation from a large + numerical simulation containing 17156424 ksi values measured from + random orientations. The bin spacing in this histogram is 0.3 degrees. + + """ + import numpy as np + import scipy.interpolate as interp + + d = np.array([ [ 0.00000000e+00, 0.00000000e+00], + [ 1.50000000e-01, 3.80000000e+01], + [ 4.50000000e-01, 2.53000000e+02], + [ 7.50000000e-01, 6.37000000e+02], + [ 1.05000000e+00, 1.30400000e+03], + [ 1.35000000e+00, 2.14800000e+03], + [ 1.65000000e+00, 3.15500000e+03], + [ 1.95000000e+00, 4.51300000e+03], + [ 2.25000000e+00, 6.03800000e+03], + [ 2.55000000e+00, 7.55500000e+03], + [ 2.85000000e+00, 9.49000000e+03], + [ 3.15000000e+00, 1.15950000e+04], + [ 3.45000000e+00, 1.37350000e+04], + [ 3.75000000e+00, 1.64950000e+04], + [ 4.05000000e+00, 1.88700000e+04], + [ 4.35000000e+00, 2.18850000e+04], + [ 4.65000000e+00, 2.54230000e+04], + [ 4.95000000e+00, 2.88190000e+04], + [ 5.25000000e+00, 3.21320000e+04], + [ 5.55000000e+00, 3.60380000e+04], + [ 5.85000000e+00, 3.99840000e+04], + [ 6.15000000e+00, 4.36950000e+04], + [ 6.45000000e+00, 4.81920000e+04], + [ 6.75000000e+00, 5.32350000e+04], + [ 7.05000000e+00, 5.78030000e+04], + [ 7.35000000e+00, 6.26690000e+04], + [ 7.65000000e+00, 6.77040000e+04], + [ 7.95000000e+00, 7.24380000e+04], + [ 8.25000000e+00, 7.90320000e+04], + [ 8.55000000e+00, 8.46900000e+04], + [ 8.85000000e+00, 9.05220000e+04], + [ 9.15000000e+00, 9.71920000e+04], + [ 9.45000000e+00, 1.03820000e+05], + [ 9.75000000e+00, 1.10760000e+05], + [ 1.00500000e+01, 1.17170000e+05], + [ 1.03500000e+01, 1.23890000e+05], + [ 1.06500000e+01, 1.31260000e+05], + [ 1.09500000e+01, 1.38800000e+05], + [ 1.12500000e+01, 1.47670000e+05], + [ 1.15500000e+01, 1.54370000e+05], + [ 1.18500000e+01, 1.62540000e+05], + [ 1.21500000e+01, 1.71450000e+05], + [ 1.24500000e+01, 1.80180000e+05], + [ 1.27500000e+01, 1.88610000e+05], + [ 1.30500000e+01, 1.96360000e+05], + [ 1.33500000e+01, 2.06070000e+05], + [ 1.36500000e+01, 2.15760000e+05], + [ 1.39500000e+01, 2.25380000e+05], + [ 1.42500000e+01, 2.34630000e+05], + [ 1.45500000e+01, 2.45320000e+05], + [ 1.48500000e+01, 2.56620000e+05], + [ 1.51500000e+01, 2.62000000e+05], + [ 1.54500000e+01, 2.64870000e+05], + [ 1.57500000e+01, 2.69010000e+05], + [ 1.60500000e+01, 2.71560000e+05], + [ 1.63500000e+01, 2.74630000e+05], + [ 1.66500000e+01, 2.78040000e+05], + [ 1.69500000e+01, 2.80570000e+05], + [ 1.72500000e+01, 2.82790000e+05], + [ 1.75500000e+01, 2.85960000e+05], + [ 1.78500000e+01, 2.88360000e+05], + [ 1.81500000e+01, 2.89220000e+05], + [ 1.84500000e+01, 2.92980000e+05], + [ 1.87500000e+01, 2.93430000e+05], + [ 1.90500000e+01, 2.96900000e+05], + [ 1.93500000e+01, 2.97160000e+05], + [ 1.96500000e+01, 2.98050000e+05], + [ 1.99500000e+01, 3.00280000e+05], + [ 2.02500000e+01, 3.02770000e+05], + [ 2.05500000e+01, 3.01800000e+05], + [ 2.08500000e+01, 3.04190000e+05], + [ 2.11500000e+01, 3.05480000e+05], + [ 2.14500000e+01, 3.02770000e+05], + [ 2.17500000e+01, 3.01660000e+05], + [ 2.20500000e+01, 2.96840000e+05], + [ 2.23500000e+01, 2.91940000e+05], + [ 2.26500000e+01, 2.87030000e+05], + [ 2.29500000e+01, 2.79490000e+05], + [ 2.32500000e+01, 2.74410000e+05], + [ 2.35500000e+01, 2.68910000e+05], + [ 2.38500000e+01, 2.60700000e+05], + [ 2.41500000e+01, 2.54640000e+05], + [ 2.44500000e+01, 2.47580000e+05], + [ 2.47500000e+01, 2.42160000e+05], + [ 2.50500000e+01, 2.36140000e+05], + [ 2.53500000e+01, 2.29720000e+05], + [ 2.56500000e+01, 2.26630000e+05], + [ 2.59500000e+01, 2.17650000e+05], + [ 2.62500000e+01, 2.12590000e+05], + [ 2.65500000e+01, 2.06500000e+05], + [ 2.68500000e+01, 2.01920000e+05], + [ 2.71500000e+01, 1.95560000e+05], + [ 2.74500000e+01, 1.88290000e+05], + [ 2.77500000e+01, 1.84200000e+05], + [ 2.80500000e+01, 1.78390000e+05], + [ 2.83500000e+01, 1.72670000e+05], + [ 2.86500000e+01, 1.66430000e+05], + [ 2.89500000e+01, 1.61550000e+05], + [ 2.92500000e+01, 1.55230000e+05], + [ 2.95500000e+01, 1.50620000e+05], + [ 2.98500000e+01, 1.44690000e+05], + [ 3.01500000e+01, 1.00740000e+05], + [ 3.04500000e+01, 6.55290000e+04], + [ 3.07500000e+01, 4.09580000e+04], + [ 3.10500000e+01, 2.05860000e+04], + [ 3.13500000e+01, 3.76200000e+03], + [ 3.16500000e+01, 0.00000000e+00], + [ 3.19500000e+01, 0.00000000e+00], + [ 3.22500000e+01, 0.00000000e+00], + [ 3.25500000e+01, 0.00000000e+00], + [ 3.28500000e+01, 0.00000000e+00], + [ 3.31500000e+01, 0.00000000e+00], + [ 3.34500000e+01, 0.00000000e+00], + [ 3.37500000e+01, 0.00000000e+00], + [ 3.40500000e+01, 0.00000000e+00], + [ 3.43500000e+01, 0.00000000e+00], + [ 3.46500000e+01, 0.00000000e+00]]) + + minval = np.sqrt(np.spacing(np.float32(1))) + cj = interp.UnivariateSpline(d[:,0], + d[:,1]+minval, + k=1, s=0.5) + + # Interpolate the spline for our bin array into a background for our histogram + bg = cj(bincenters) + bg[bg < minval] = minval # do not allow division by zero + + return bg + +def _bg_ksi3(bincenters): + """ returns ksi1 background + + Parameters + ---------- + bincenters : numpy array + centers of bins for ksi1 background + + Returns + ------- + bg : numpy array + background number fractions for ksi1 + + Notes + ----- + Background is determined by cubic spline interpolation from a large + numerical simulation containing 17156424 ksi values measured from + random orientations. The bin spacing in this histogram is 0.3 degrees. + + """ + import numpy as np + import scipy.interpolate as interp + + d = np.array([ [ 0.00000000e+00, 0.00000000e+00], + [ 1.50000000e-01, 2.02400000e+03], + [ 4.50000000e-01, 6.40000000e+03], + [ 7.50000000e-01, 1.04980000e+04], + [ 1.05000000e+00, 1.44530000e+04], + [ 1.35000000e+00, 1.87920000e+04], + [ 1.65000000e+00, 2.27030000e+04], + [ 1.95000000e+00, 2.67350000e+04], + [ 2.25000000e+00, 3.07190000e+04], + [ 2.55000000e+00, 3.50120000e+04], + [ 2.85000000e+00, 3.91140000e+04], + [ 3.15000000e+00, 4.28890000e+04], + [ 3.45000000e+00, 4.71630000e+04], + [ 3.75000000e+00, 5.13210000e+04], + [ 4.05000000e+00, 5.43970000e+04], + [ 4.35000000e+00, 5.88480000e+04], + [ 4.65000000e+00, 6.29620000e+04], + [ 4.95000000e+00, 6.75770000e+04], + [ 5.25000000e+00, 7.14270000e+04], + [ 5.55000000e+00, 7.46270000e+04], + [ 5.85000000e+00, 7.91680000e+04], + [ 6.15000000e+00, 8.24090000e+04], + [ 6.45000000e+00, 8.65320000e+04], + [ 6.75000000e+00, 9.06090000e+04], + [ 7.05000000e+00, 9.47120000e+04], + [ 7.35000000e+00, 9.79920000e+04], + [ 7.65000000e+00, 1.01590000e+05], + [ 7.95000000e+00, 1.05900000e+05], + [ 8.25000000e+00, 1.09020000e+05], + [ 8.55000000e+00, 1.12940000e+05], + [ 8.85000000e+00, 1.17490000e+05], + [ 9.15000000e+00, 1.20630000e+05], + [ 9.45000000e+00, 1.23850000e+05], + [ 9.75000000e+00, 1.27110000e+05], + [ 1.00500000e+01, 1.33170000e+05], + [ 1.03500000e+01, 1.35750000e+05], + [ 1.06500000e+01, 1.38000000e+05], + [ 1.09500000e+01, 1.42120000e+05], + [ 1.12500000e+01, 1.45870000e+05], + [ 1.15500000e+01, 1.49250000e+05], + [ 1.18500000e+01, 1.52450000e+05], + [ 1.21500000e+01, 1.56420000e+05], + [ 1.24500000e+01, 1.59460000e+05], + [ 1.27500000e+01, 1.63300000e+05], + [ 1.30500000e+01, 1.65540000e+05], + [ 1.33500000e+01, 1.68700000e+05], + [ 1.36500000e+01, 1.71120000e+05], + [ 1.39500000e+01, 1.75060000e+05], + [ 1.42500000e+01, 1.78070000e+05], + [ 1.45500000e+01, 1.80540000e+05], + [ 1.48500000e+01, 1.83390000e+05], + [ 1.51500000e+01, 1.85630000e+05], + [ 1.54500000e+01, 1.89110000e+05], + [ 1.57500000e+01, 1.91660000e+05], + [ 1.60500000e+01, 1.95150000e+05], + [ 1.63500000e+01, 1.97860000e+05], + [ 1.66500000e+01, 1.99910000e+05], + [ 1.69500000e+01, 2.03460000e+05], + [ 1.72500000e+01, 2.05680000e+05], + [ 1.75500000e+01, 2.08830000e+05], + [ 1.78500000e+01, 2.10760000e+05], + [ 1.81500000e+01, 2.12770000e+05], + [ 1.84500000e+01, 2.14450000e+05], + [ 1.87500000e+01, 2.17540000e+05], + [ 1.90500000e+01, 2.19990000e+05], + [ 1.93500000e+01, 2.22920000e+05], + [ 1.96500000e+01, 2.24490000e+05], + [ 1.99500000e+01, 2.25390000e+05], + [ 2.02500000e+01, 2.27330000e+05], + [ 2.05500000e+01, 2.29780000e+05], + [ 2.08500000e+01, 2.31760000e+05], + [ 2.11500000e+01, 2.34130000e+05], + [ 2.14500000e+01, 2.34930000e+05], + [ 2.17500000e+01, 2.36170000e+05], + [ 2.20500000e+01, 2.39060000e+05], + [ 2.23500000e+01, 2.39200000e+05], + [ 2.26500000e+01, 2.40280000e+05], + [ 2.29500000e+01, 2.41970000e+05], + [ 2.32500000e+01, 2.42410000e+05], + [ 2.35500000e+01, 2.44470000e+05], + [ 2.38500000e+01, 2.44630000e+05], + [ 2.41500000e+01, 2.45470000e+05], + [ 2.44500000e+01, 2.46940000e+05], + [ 2.47500000e+01, 2.46660000e+05], + [ 2.50500000e+01, 2.48020000e+05], + [ 2.53500000e+01, 2.49590000e+05], + [ 2.56500000e+01, 2.50060000e+05], + [ 2.59500000e+01, 2.49730000e+05], + [ 2.62500000e+01, 2.49300000e+05], + [ 2.65500000e+01, 2.49770000e+05], + [ 2.68500000e+01, 2.51230000e+05], + [ 2.71500000e+01, 2.50670000e+05], + [ 2.74500000e+01, 2.50110000e+05], + [ 2.77500000e+01, 2.53140000e+05], + [ 2.80500000e+01, 2.50100000e+05], + [ 2.83500000e+01, 2.49790000e+05], + [ 2.86500000e+01, 2.49210000e+05], + [ 2.89500000e+01, 2.48700000e+05], + [ 2.92500000e+01, 2.47540000e+05], + [ 2.95500000e+01, 2.47380000e+05], + [ 2.98500000e+01, 2.47910000e+05], + [ 3.01500000e+01, 1.88290000e+05], + [ 3.04500000e+01, 1.38640000e+05], + [ 3.07500000e+01, 1.07030000e+05], + [ 3.10500000e+01, 8.20110000e+04], + [ 3.13500000e+01, 5.92320000e+04], + [ 3.16500000e+01, 4.13020000e+04], + [ 3.19500000e+01, 2.75660000e+04], + [ 3.22500000e+01, 1.75590000e+04], + [ 3.25500000e+01, 9.84800000e+03], + [ 3.28500000e+01, 4.43200000e+03], + [ 3.31500000e+01, 1.59800000e+03], + [ 3.34500000e+01, 1.18000000e+02], + [ 3.37500000e+01, 0.00000000e+00], + [ 3.40500000e+01, 0.00000000e+00], + [ 3.43500000e+01, 0.00000000e+00], + [ 3.46500000e+01, 0.00000000e+00]]) + + minval = np.sqrt(np.spacing(np.float32(1))) + cj = interp.UnivariateSpline(d[:,0], + d[:,1]+minval, + k=1, s=0.5) + + # Interpolate the spline for our bin array into a background for our histogram + bg = cj(bincenters) + bg[bg < minval] = minval # do not allow division by zero + + return bg + +def ksi1hist(ksi1values, binwidth=0.5, return_everything=False): + """ histogram ksi1 data + + Parameters + ---------- + ksi1values : numpy array of floats + ksi1values, in degrees + + binwidth : numpy float (optional) + width of bins, in degrees + + return_everything : bool (optional) + flag for whether + + Returns + ------- + fcorr : numpy array + background-corrected ksi1 number fractions + + bm : numpy array + mean values for the histogram bins + + ba : (optional, with return_everything flag) numpy array + histogram bin edges + + fraw : (optional, with return_everything flag) numpy array + ksi1 number fractions before background correction + """ + import numpy as np + + # Histogram ksi data + ba = np.r_[0:35:binwidth] + nb = ba.size + bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) + fraw, tmp = np.histogram(ksi1values, ba, density=True) + fraw = fraw / np.sum(fraw) + + bg = _bg_ksi1(bm) + + # Correct the histogram for measurement + fcorr = fraw / bg + fcorr = fcorr / np.sum(fcorr) + + if return_everything == False: + return fcorr, bm + else: + return fcorr, bm, ba, fraw + +def ksi2hist(ksi2values, binwidth=0.5, return_everything=False): + """ histogram ksi2 data + + Parameters + ---------- + ksi1values : numpy array of floats + ksi1values, in degrees + + binwidth : numpy float (optional) + width of bins, in degrees + + return_everything : bool (optional) + flag for whether + + Returns + ------- + fcorr : numpy array + background-corrected ksi1 number fractions + + bm : numpy array + mean values for the histogram bins + + ba : (optional, with return_everything flag) numpy array + histogram bin edges + + fraw : (optional, with return_everything flag) numpy array + ksi1 number fractions before background correction + """ + import numpy as np + + # Histogram ksi data + ba = np.r_[0:35:binwidth] + nb = ba.size + bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) + fraw, tmp = np.histogram(ksi2values, ba, density=True) + fraw = fraw / np.sum(fraw) + + bg = _bg_ksi2(bm) + + # Correct the histogram for measurement + fcorr = fraw / bg + fcorr = fcorr / np.sum(fcorr) + + if return_everything == False: + return fcorr, bm + else: + return fcorr, bm, ba, fraw + +def ksi3hist(ksi3values, binwidth=0.5, return_everything=False): + """ histogram ksi3 data + + Parameters + ---------- + ksi3values : numpy array of floats + ksi1values, in degrees + + binwidth : numpy float (optional) + width of bins, in degrees + + return_everything : bool (optional) + flag for whether + + Returns + ------- + fcorr : numpy array + background-corrected ksi1 number fractions + + bm : numpy array + mean values for the histogram bins + + ba : (optional, with return_everything flag) numpy array + histogram bin edges + + fraw : (optional, with return_everything flag) numpy array + ksi1 number fractions before background correction + """ + import numpy as np + + # Histogram ksi data + ba = np.r_[0:35:binwidth] + nb = ba.size + bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) + fraw, tmp = np.histogram(ksi3values, ba, density=True) + fraw = fraw / np.sum(fraw) + + bg = _bg_ksi3(bm) + + # Correct the histogram for measurement + fcorr = fraw / bg + fcorr = fcorr / np.sum(fcorr) + + if return_everything == False: + return fcorr, bm + else: + return fcorr, bm, ba, fraw + +def fit_foldnorm(f, bm, return_info=False, accuracy=None, iterations=5000, + method = 'slsqp', mub=None, sgb=None): + """ fit ksi histogram to folded normal distribution + + Parameters + ---------- + fcorr : numpy float array + histogram bin counts or frequencies + + bm : numpy float array + middle values of bins + + return_info : bool {false} + return info from fitting procedure + + accuracy : float or None {None} + numerical accuracy of fitting procedure, default is floating point + + iterations : int + maximum number of iterations in fitting minimization + + method : {'slsqp', 'lsq'} + method for constrained function minimization, either sequential + least squares programming (default) or standard least squares. + + mub : float iterable of size 2 + lower bound search value for folded normal mu at location 0; + upper bound at location 1 + + sgb : float iterable of size 2 + lower bound search value for folded normal mu at location 0; + upper bound at location 1 + + Returns + ------- + mu : float + folded normal fit location parameter + sig : float + folded normal fit shape parameter + rss : float + final residual sum of squares difference between the fit and + the histogram + info : tuple + (1) the number of iterations required for fitting + (2) the exit mode from the optimizer (see notes) + (3) message describing exit mode + + Notes + ----- + - For very narrow distributions, it can be necessary to adjust the upper + bound for mu. When the distribution is narrow and well-behaved, np.inf + works fine (i.e., use mub=[np.spacing(np.float(1.0)), np.inf]). + - Minimization is performed by default with scipy.optimize.fmin_slsqp. + Refer to the documentation for this function for further details on exit + mode returns. + """ + import numpy as np + from scipy.special import erf + import scipy.optimize as spop + + if accuracy == None: # default to floating point accuracy + accuracy = np.spacing(np.float32(0)) + + # Estimate the distribution fitting parameters and then minimize the RMS + # Note that RMS error is linearly related to log likelihood, so we should get + # comparable results in both minimizations + m1 = np.dot(bm, f) # first moment + m2 = np.dot(bm**2.0, f) # second moment + m4 = np.dot(bm**4.0, f) # fourth moment + s = np.dot((bm - m1)**2.0, f) # standard deviation + x = np.arange(0.0, + np.amax(bm) + np.amax(bm) / 1000.0, + np.amax(bm) / 1000.0) + + # Elandt finds that the 1st and 2nd moments give a better approximation + # when m1/s is greater than about 1.35, and the 2nd and 4th moment + # method is better otherwise. + if m1/s > 1.35: # estimate parameters by 1st and 2nd moments + a = m1**2.0 / m2 # ratio of moments + def i0(x): # Eq 2 [2] + return 1.0 - 0.5 * (1.0 + erf(x / np.sqrt(2.0))) + def g(th, i0, a): + return (np.sqrt(2.0/np.pi) * \ + np.exp(-0.5 * th**2.0) - \ + th * \ + (1.0 - 2.0 * i0(-th)))**2.0 / (a * (1.0 + th**2.0)) - 1.0 + + # Find the value of G closest to zero, limiting iterations + # (fminbnd is not sufficiently robust in our case because G + # sometimes is close to zero but does not necessarily intersect the + # x axis in the limit of the half normal distribution). Assuming + # the function will only come close to zero once, then we will just + # search for the the solution to the equation iteratively + lb = 0.0 + ub = np.amax(x) + res = np.inf + nit = 0 + j = 0 + while (res > accuracy and + nit < iterations and + (ub - lb) / 1000.0 > accuracy): + + # set a search range + qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) + + tmp = np.absolute(g(qtm, i0, a)) + resn = np.amin(tmp) # get value closest to zero in range + j = np.argmin(tmp) + + ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound + lb = qtm[np.amax([j - 1, 0])] # get new lower bound + nit += 1 # increment number of iterations counter + th=qtm[j] + del qtm, j, ub, lb, res, g, a, i0, nit, tmp + + else: # Estimate Parameters by second and fourth moments + + b = m4 / m2**2.0 # ratio of moments + + def h(q, b): # Eq. 22 [2] + return b * (1.0 + q**2.0)**2.0 - (3.0 + 6.0 * q**2.0 + q**4.0) + + # Find the value of h closest to zero, limiting iterations. + # Same method as above. + lb = 0.0 + ub = np.amax(x) + res = np.inf + nit = 0 + j = 0 + while (res > accuracy and + nit < iterations and + np.absolute(ub-lb) / 1000.0 > accuracy): + + # set a search range + qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) + + tmp = np.absolute(h(qtm, b)) + resn = np.amin(tmp) # get value closest to zero in range + j = np.argmin(tmp) + if np.isreal(resn): + res = resn # in case min cannot be found + ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound + lb = qtm[np.amax([j - 1, 0])] # get new lower bound + nit += 1 # increment number of iterations counter + + th = qtm[j] + del qtm, j, ub, lb, res, h, b, nit, tmp + del m2, m4 + + # Compute initial guess values + sgi = np.sqrt((s**2.0 + m1**2.0) / (1.0 + th**2.0)) + mui = th * sgi + + # Define minimization function as a least squares problem + def ff(params): + import numpy as np + import scipy.stats as stats + fx = stats.foldnorm.pdf(bm, params[0], loc=0, scale=params[1]) + fx = fx/(np.sum(fx)+np.spacing(1.0)) + return np.sum((f-fx)**2.0) + + # Set upper and lower bounds + if mub==None: + # np.inf would be a nicer upper bound here but realistically + # we need to hold it under 15 for distributions that + # are not well-behaved. + mub = (np.sqrt(accuracy), 15.0) + + if sgb==None: + # np.inf would be a nicer upper bound here but realistically + # we need to hold it under 5 for distributions that + # are not well-behaved + # Zero would be a nicer lower bound but realistically it + # we shouldn't allow it to be less than the bin width. + sgb = (np.mean(np.diff(bm)), 5.0) + + if method=='slsqp': + # ----- Begin perform constrained minimization, option #1 ----------------- + out, rss, its, imode, smode = spop.fmin_slsqp(ff, x0=[mui, sgi], + bounds=[mub, sgb], + full_output=True, + iter=iterations, + acc=accuracy, + iprint=1) + mu = out[0] + sig = out[1] + # ----- End perform constrained minimization, option #1 ----------------- + else: + # ----- Begin perform constrained minimization, option #2 ----------------- + out = spop.least_squares(ff, x0=[mui, sgi], + bounds=([mub[0], sgb[0]], + [mub[1], sgb[1]]), + verbose=1, + max_nfev=iterations, + gtol=accuracy, + xtol=accuracy, + ftol=accuracy) + mu = out.x[0] + sig = out.x[1] + rss = out.cost + imode = out.status + smode = out.message + its = out.nfev + # ----- End perform constrained minimization, option #2 ----------------- + + if return_info==False: + return mu, sig + else: + return mu, sig, rss, (its, imode, smode) + +def foldnorm_mode(mu, sig, accuracy=None): + """ mode/peak of the folded normal distribution + + Parameters + ---------- + mu : float + location parameter + sig : float + shape parameter + + Returns + ------- + expected_value : float + E(Y) + + Notes + ----- + Equation comes from the documentation for the folded normal distribution in + the R statistics package. + http://hosho.ees.hokudai.ac.jp/~kubo/Rdoc/library/VGAM/html/fnormal1.html + """ + import scipy.stats as stats + import numpy as np + import scipy.optimize as spop + + if accuracy == None: # default to floating point accuracy + accuracy = np.spacing(np.float32(1.0)) + + fn = stats.foldnorm(mu, loc=0, scale=sig) + def chk(x): + val = -fn.pdf(x) + return val + + res = spop.minimize(chk, 0.0, method='nelder-mead', + options={'xtol': 1e-8, 'disp': False}) + + return res.x[0] + + +def fit_ksivals(ksi1vals, ksi2vals, ksi3vals, binwidth=0.5, makeplot=True, \ + accuracy=None, plotdpi=96, fmax=15.0, bmax=15.0, + method = 'slsqp', mub=None, sgb=None, plotlegcontains='all'): + """ + Parameters + ---------- + + ksi1vals : numpy float array + + ksi2vals : numpy float array + + ksi3vals : numpy float array + + makeplot : bool (optional, default=True) + return a plot of the ksi value distributions + + binwidth : float (optional, default=0.5) + width of bins on plot (if makeplot=True) + + fmax : float (optional, default=15.0) + maximum value of y axis of plots (if makeplot=True) + + bmax : float (optional, default=15.0) + maximum value of y axis of plots (if makeplot=True) + + plotdpi : int (optional, default=96) + dpi for plot output. Use for screen resolution. + + accuracy : float (optional) + defined accuracy of least squares optimization on folded normal fit. + Default value is floating point accuracy. + + method : {'slsqp', 'lsq'} + method for constrained function minimization, either sequential + least squares programming (default) or standard least squares. + + mub : float iterable of size 2 + lower bound search value for folded normal mu at location 0; + upper bound at location 1 + + sgb : float iterable of size 2 + lower bound search value for folded normal mu at location 0; + upper bound at location 1 + + plotlegcontains : list of strings + {'all', 'mo', 'sig', 'mu', 'ksibar', 'std', 'rss'} + defines which parameters to include in the plot legend + + Returns + ------- + result : numpy float array + np.array([[mo1, mean1, mu1, sig1, rss1], + [mo2, mean2, mu2, sig2, rss2], + [mo3, mean3, mu3, sig3, rss3]]); i.e., + - result[0, 0] : modal value of folded normal fit of ksi1 + - result[0, 1] : mean value of ksi1 histogram + - result[0, 2] : mu value for folded normal fit of ksi1 + - result[0, 3] : sigma value for folded normal fit of ksi1 + - result[0, 4] : rss for folded normal fit of ksi1 + - result[1, 0] : modal value of folded normal fit of ksi2 + - result[1, 1] : mean value of ksi2 histogram + - result[1, 2] : mu value for folded normal fit of ksi2 + - result[1, 3] : sigma value for folded normal fit of ksi2 + - result[1, 4] : rss for folded normal fit of ksi2 + - result[2, 0] : modal value of folded normal fit of ksi3 + - result[2, 1] : mean value of ksi3 histogram + - result[2, 2] : mu value for folded normal fit of ksi3 + - result[2, 3] : sigma value for folded normal fit of ksi3 + - result[2, 4] : rss for folded normal fit of ksi3 + + fig : (optional, returned if makeplot=True) + figure matplotlib handle + + ax1 : (optional, returned if makeplot=True) + ksi1 matplotlib axis handle + + ax2 : (optional, returned if makeplot=True) + ksi2 matplotlib axis handle + + ax3 : (optional, returned if makeplot=True) + ksi2 matplotlib axis handle + + Notes + ----- + - If plot is output, the 'observation probability corrected' histogram is + shown in white in the foreground. The darker gray background histogram is + the uncorrected ksi values, and the lighter gray is the observation + probability distribution. + + """ + + import numpy as np + import scipy.stats as stats + import matplotlib.pyplot as plt + + if plotlegcontains=='all': + plotlegcontains = ['mo', 'sig', 'mu', 'ksibar', 'std', 'rss'] + + f1c, bm, ba, f1r = ksi1hist(ksi1vals, + binwidth, return_everything=True) + + f2c, bm, ba, f2r = ksi2hist(ksi2vals, + binwidth, return_everything=True) + + f3c, bm, ba, f3r = ksi3hist(ksi3vals, + binwidth, return_everything=True) + + mu1, sig1, rss1, dat1 = fit_foldnorm(f1c, bm, return_info=True, + accuracy=accuracy, + method=method, + mub=mub, + sgb=sgb) + + mu2, sig2, rss2, dat2 = fit_foldnorm(f2c, bm, return_info=True, + accuracy=accuracy, + method=method, + mub=mub, + sgb=sgb) + + mu3, sig3, rss3, dat3 = fit_foldnorm(f3c, bm, return_info=True, + accuracy=accuracy, + method=method, + mub=mub, + sgb=sgb) + + mo1 = foldnorm_mode(mu1, sig1) + mo2 = foldnorm_mode(mu2, sig2) + mo3 = foldnorm_mode(mu3, sig3) + + mean1 = np.dot(bm, f1c) + mean2 = np.dot(bm, f2c) + mean3 = np.dot(bm, f3c) + + returnarray = np.array([[mo1, mean1, mu1, sig1, rss1], + [mo2, mean2, mu2, sig2, rss2], + [mo3, mean3, mu3, sig3, rss3]]) + + if makeplot==False: + + return returnarray + + else: + + # parameters we might want to adjust + xlimv = [0.0, bmax] + ylimv = [0.0, fmax] + xtickstep = 3 + ytickstep = 3 + bgdistclr = [0.85, 0.85, 0.85] + uncorrclr = [0.5, 0.5, 0.5] + ksi1faceclr = [1.0, 1.0, 1.0] + ksi2faceclr = [1.0, 1.0, 1.0] + ksi3faceclr = [1.0, 1.0, 1.0] + fontsz=12 + + xv = np.arange(0.0, np.amax(bm) + np.amax(bm) / 1000.0, + np.amax(bm) / 1000.0) + + # Create an awesome plot + fig = plt.figure(figsize=(15, 4), dpi=plotdpi) + fig.patch.set_facecolor('w') + + # ksi1 subplot + ax1 = fig.add_subplot(131) + f = stats.foldnorm.pdf(xv, mu1, scale=sig1) + f = xv.size*f/np.sum(f) + ax1.plot(xv, f, '-k', linewidth=1) + ax1.bar(bm, f1r*bm.size, width=binwidth, + facecolor=uncorrclr, edgecolor=uncorrclr) + ax1.bar(bm, f1c*bm.size, width=binwidth, + facecolor=ksi1faceclr, edgecolor=[0.0, 0.0, 0.0]) + bg = _bg_ksi1(xv) + bg = bg/np.sum(bg) + ax1.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) + legstring = [] + if any('mo' in s for s in plotlegcontains): + legstring.append( + r'$Mo_{\xi1}$' + + r' = {0:5.2f}'.format(mo1) + + r'$^\circ$' +'\n') + if any('mu' in s for s in plotlegcontains): + legstring.append( + r'$\mu_{\xi1}$' + r' = {0:5.2f}'.format(mu1) + + r'$^\circ$' +'\n') + if any('sig' in s for s in plotlegcontains): + legstring.append( + r'$\sigma_{\xi1}$' + r' = {0:5.2f}'.format(sig1) + + r'$^\circ$' + '\n') + if any('ksibar' in s for s in plotlegcontains): + legstring.append( + r'$<\xi_1>$' + r' = {0:5.2f}'.format(np.mean(ksi1vals)) + + r'$^\circ$' +'\n') + if any('std' in s for s in plotlegcontains): + legstring.append( + r'$St.Dev.(\xi_1)$' + r' = {0:5.2f}'.format(np.std(ksi1vals)) + + r'$^\circ$' + '\n') + if any('rss' in s for s in plotlegcontains): + legstring.append( + r'$RSS$' + r' = {0:5.2e}'.format(rss1)) + ax1.text(0.97, 0.97, + ''.join(legstring), + fontsize=fontsz, + bbox=dict(edgecolor='k', facecolor='w'), + transform = ax1.transAxes, + horizontalalignment='right', + verticalalignment='top') + ax1.set_xlim(xlimv) + ax1.set_ylim(ylimv) + ax1.set_xticks(np.arange(xlimv[0], xlimv[1]+xtickstep, xtickstep)) + ax1.set_yticks(np.arange(ylimv[0], ylimv[1]+ytickstep, ytickstep)) + + # ksi2 subplot + ax2 = fig.add_subplot(132) + f = stats.foldnorm.pdf(xv, mu2, scale=sig2) + f = xv.size*f/np.sum(f) + ax2.plot(xv, f, '-k', linewidth=1) + ax2.bar(bm, f2r*bm.size, width=binwidth, + facecolor=uncorrclr, edgecolor=uncorrclr) + ax2.bar(bm, f2c*bm.size, width=binwidth, + facecolor=ksi2faceclr, edgecolor=[0.0, 0.0, 0.0]) + bg = _bg_ksi2(xv) + bg = bg/np.sum(bg) + ax2.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) + legstring = [] + if any('mo' in s for s in plotlegcontains): + legstring.append( + r'$Mo_{\xi2}$' + + r' = {0:5.2f}'.format(mo2) + + r'$^\circ$' +'\n') + if any('mu' in s for s in plotlegcontains): + legstring.append( + r'$\mu_{\xi2}$' + r' = {0:5.2f}'.format(mu2) + + r'$^\circ$' +'\n') + if any('sig' in s for s in plotlegcontains): + legstring.append( + r'$\sigma_{\xi_2}$' + r' = {0:5.2f}'.format(sig2) + + r'$^\circ$' + '\n') + if any('ksibar' in s for s in plotlegcontains): + legstring.append( + r'$<\xi_2>$' + r' = {0:5.2f}'.format(np.mean(ksi2vals)) + + r'$^\circ$' +'\n') + if any('std' in s for s in plotlegcontains): + legstring.append( + r'$St.Dev.(\xi2)$' + r' = {0:5.2f}'.format(np.std(ksi2vals)) + + r'$^\circ$' + '\n') + if any('rss' in s for s in plotlegcontains): + legstring.append( + r'$RSS$' + r' = {0:5.2e}'.format(rss2)) + ax2.text(0.03, 0.97, + ''.join(legstring), + fontsize=fontsz, + bbox=dict(edgecolor='k', facecolor='w'), + transform = ax2.transAxes, + horizontalalignment='left', + verticalalignment='top') + ax2.set_xlim(xlimv) + ax2.set_ylim(ylimv) + ax2.set_xticks(np.arange(xlimv[0], xlimv[1]+xtickstep, xtickstep)) + ax2.set_yticks([]) + + + # ksi2 subplot + ax3 = fig.add_subplot(133) + f = stats.foldnorm.pdf(xv, mu3, scale=sig3) + f = xv.size*f/np.sum(f) + ax3.plot(xv, f, '-k', linewidth=1) + ax3.bar(bm, f3r*bm.size, width=binwidth, + facecolor=uncorrclr, edgecolor=uncorrclr) + ax3.bar(bm, f3c*bm.size, width=binwidth, + facecolor=ksi3faceclr, edgecolor=[0.0, 0.0, 0.0]) + bg = _bg_ksi3(xv) + bg = bg/np.sum(bg) + ax3.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) + legstring = [] + if any('mo' in s for s in plotlegcontains): + legstring.append( + r'$Mo_{\xi3}$' + + r' = {0:5.2f}'.format(mo3) + + r'$^\circ$' +'\n') + if any('mu' in s for s in plotlegcontains): + legstring.append( + r'$\mu_{\xi3}$' + r' = {0:5.2f}'.format(mu3) + + r'$^\circ$' +'\n') + if any('sig' in s for s in plotlegcontains): + legstring.append( + r'$\sigma_{\xi3}$' + r' = {0:5.2f}'.format(sig3) + + r'$^\circ$' + '\n') + if any('ksibar' in s for s in plotlegcontains): + legstring.append( + r'$<\xi_3>$' + r' = {0:5.2f}'.format(np.mean(ksi3vals)) + + r'$^\circ$' +'\n') + if any('std' in s for s in plotlegcontains): + legstring.append( + r'$St.Dev.(\xi_3)$' + r' = {0:5.2f}'.format(np.std(ksi3vals)) + + r'$^\circ$' + '\n') + if any('rss' in s for s in plotlegcontains): + legstring.append( + r'$RSS$' + r' = {0:5.2e}'.format(rss3)) + ax3.text(0.03, 0.97, + ''.join(legstring), + fontsize=fontsz, + bbox=dict(edgecolor='k', facecolor='w'), + transform = ax3.transAxes, + horizontalalignment='left', + verticalalignment='top') + ax3.set_xlim(xlimv) + ax3.set_ylim(ylimv) + ax3.set_xticks(np.arange(xlimv[0], xlimv[1]+xtickstep, xtickstep)) + ax3.set_yticks([]) + + ax1.set_ylabel(r'Relative Number Fraction', fontsize=fontsz*1.2) + ax1.set_xlabel(r'$\xi_1$ ($^\circ$)', fontsize=fontsz*1.2) + ax2.set_xlabel(r'$\xi_2$ ($^\circ$)', fontsize=fontsz*1.2) + ax3.set_xlabel(r'$\xi_3$ ($^\circ$)', fontsize=fontsz*1.2) + + for label in (ax1.get_yticklabels() + ax1.get_xticklabels() + + ax2.get_xticklabels() + ax3.get_xticklabels()): + label.set_fontsize(fontsz) + + fig.subplots_adjust(bottom=0.15) + plt.tight_layout() + plt.draw() + + return returnarray, \ + fig, ax1, ax2, ax3 + +# ---------------------------------------------------------------------------- + +def CSLcrit(name='Brandon', sigmas=None): + ''' calculate CSL criteria + + **CSLcrit** calculates the angular deviation from the ideal misorientation + relationship allowed by different *named* coincident site lattice (CSL) + criteria of the form: + + ..math :: + \theta_c=\theta_0 / sigma^eta + + Parameters + ---------- + name : {'Brandon', 'Pumphrey', 'Ishida-McLean', 'Palumbo-Aust'} + + CSL criteria name. Defaults to Brandon. Also accepts 'B' for Brandon, + 'P' for Pumphrey, 'IM' for Ishida-McLean, and 'PA' for Palumbo-Aust. + String argument is case insensitive. Dash can be replaced by '&' or + 'and' with or without spaces around it. + + sigmas : None + + Sigma values of interest. If None, returns all values from 1 to 51. + + Returns + ------- + thc : numpy float array + + Notes + ----- + This is a Python translation of a Matlab function of the same name written + by EJP 2011-05-02 while at Ruhr-Universitaet Bochum. This function was used + for the analysis in references [1]_ and [2]_. + + For general reference on Coincident Site Lattice theory, see + reference [3]_. The named CSL criteria used here from the following: + Brandon [4]_, Pumphrey [5]_, Palumbo & Aust [6]_, and Ishida & McLean [7]_. + + References + ---------- + .. [1] Otto et al, J Mater Sci 47 (2012) p2915-2927. + .. [2] Otto et al, Acta Mater 60 (2012) p2982-2998. + .. [3] Warrington & Boon, Acta Metall 23 (1975) p599 + .. [4] Brandon, Acta Metall 14 (1966) p1479 + .. [5] Pumphrey, in Grain Boundary Structure & Properties, London: Academic Press (1976) p139 + .. [6] Palumbo & Aust, Acta Metall 38 (1990) p2343 + .. [7] Ishida & McLean, Philos Mag 27 (1973) p1125 + ''' + import numpy as np + import warnings + + # Remove + name = name.replace('&', '').replace('and', '').replace('-','') + name = name.replace(' ', '').lower() + + if sigmas == None: + + sig = np.arange(1, 53, 2, dtype=np.float) + + else: + + sig = np.asanyarray(sigmas, dtype=np.float) + + if np.any(sig % 2): + + warnings.warn('Only odd values of sigma are valid. ' + \ + 'Odd values have been removed.') + + if (name == 'brandon') or (name == 'b'): + + th0 = 15.0 + eta = 1.0 / 2.0 + + elif (name == 'pumphrey') or (name =='p'): + + th0 = 15.0 + eta = 2.0 / 3.0 + + elif (name == 'palumboaust') or (name == 'pa'): + + th0 = 15.0 + eta = 5.0 / 6.0 + + elif (name == 'ishidamclean') or (name == 'im'): + + th0 = 8.0 + eta = 1.0 + + else: + + warnings.warn('CSLcrit: unrecognized named criterion. ' + \ + 'Proceeding with Brandon criterion.') + + th0 = 15.0 + eta = 1.0 / 2.0 + + # Convert theta to radians + th0 = th0 * np.pi / 180.0 + + # Calculate criteria + thc = th0 / sig**eta + + return thc + +# ---------------------------------------------------------------------------- + +#def genCSLs(sigmas=None): +#function [CSL,SQ,SV,SN]=GenCSL(varargin) +#% GenCSL generates the exact misorientations of the coincident site lattice +#% theory as rotation matrices and as quaternions. +#% +#% INPUTS: varargin can be a list of CSL values (as in, for example, +#% [3, 9, 27]). If left blank, it generates the CSLs up to Sigma49. +#% +#% OUTPUTS: the CSL in a structural format (CSL), or as lists of +#% quaternions (SQ), their corresponding sigma values (SV), and +#% their standard names (SN). +#% +#% REFERENCES: +#% [1] H. Grimmer, W. Bollman, D. H. Warrington. "Coincident-Site Lattices +#% and Complete Pattern-Shift Lattices in Cubic Crystals." Acta Cryst. A30 +#% (1974) p197. +#% [2] D. H. Warrington, P. Bufalini. "The Coincident Site Lattice and Grain +#% Boundaries." Scripta Metall. 5 (1971) p771. +#% +#% NOTES: Function exactly reproduces Table 1 from Grimmer et al, except for +#% Sigma31b, which appears to be in error in the paper. +#% +#% ------------------------------------------------------------------------- +#% 2011-05-01 | Eric Payton, Ruhr-Universitaet Bochum (payton.28[at]osu.edu) +#% ------------------------------------------------------------------------- +#% This program is provided without any guarantee of correctness. +#% If you modify it and/or improve it, please kindly share with me your new +#% and improved version to the email address above. Thanks! +#%---------------------------------------------------------------------------- + + + +# % Remove symmetric redundancies by putting into fundamental region +# O=RMat2Quat(SM);RR=zeros(length(SM),4); +# for ind=1:length(SM) +# q=QuatProd(repmat(O(ind,:),size(SYM,1),1),SYM); +# [an,ax]=Quat2AngAx(q); +# ax=sort(abs(ax(an==min(an),:)),2,'descend');an=min(an);ax=ax(1,:); +# RR(ind,:)=[an,ax]; +# end +# [~,b]=unique(sigdec(RR,5),'rows');RR=RR(b,:); +# +# % Sort the results by increasing angle in axis/angle description, such +# % that, in the event of multiple results, th.a1 +# for ind=1:size(RR,1) +# tmp=Quat2RMat(RR(ind,:)); +# eval(['CSL.S' num2str(S) char(96+ind) ... +# '=tmp{1};']); +# SN{ee}=['\Sigma' num2str(S) char(96+ind)];ee=ee+1; +# end +# else +# tmp=Quat2RMat(RR); +# eval(['CSL.S' num2str(S) '=tmp{1};']); +# SN{ee}=['\Sigma' num2str(S)];ee=ee+1; +# end +# +# % Store results as a list of quaternions corresponding Sigma values +# SQ=vertcat(SQ,RR); +# SV=vertcat(SV,repmat(S,size(RR,1),1)); +# +# clear SM O an ax RR b ind % clean up +#end % loop over all desired N +#clear Ndex S SYM TT X h1 k1 l1 h2 k2 l2 hkl1 hkl2 hkl3 q N ee % clean up +# +#% This is it. + +# ---------------------------------------------------------------------------- + +def genKitahara_KS(): + ''' KS OR matrices + + **genKitahara_KS** returns the rotation matrices for the 24 variants of the + Kurdjumov-Sachs orientation relationship in the order presented in Table 2 + of Kitahara [1]_. + + Parameters + ---------- + None + + Returns + ------- + V : 24 x 1 list of 3 x 3 numpy matrices + + Notes + ----- + Rounding the results of Kitahara_KS and removing redundancies gives the + list of Bain correspondence matrices. + + See also + -------- + genKitahara_NW, genBainCorrMatrices + + References + ---------- + .. [1] Kitahara et al, Acta Mater 54 (2006) p1279. + ''' + import numpy as np + + Pg = [0] * 24 + Pa = [0] * 24 + Dg = [0] * 24 + Da = [0] * 24 + + i = 0 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 1 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 2 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 3 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 4 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, -1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 5 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, -1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 6 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 7 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 8 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 9 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 10 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 11 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 12 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 13 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 14 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 15 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 16 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 17 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 18 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 19 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 20 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 21 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 22 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 23 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + V = [] + + for i in range(0, 24): + + a = Dg[i] / np.linalg.norm(Dg[i]) + b = np.cross(Dg[i], Pg[i]) / np.linalg.norm(np.cross(Dg[i], Pg[i])) + c = Pg[i] / np.linalg.norm(Pg[i]) + M = np.vstack([a, b, c]) + + a = Da[i] / np.linalg.norm(Da[i]) + b = np.cross(Da[i], Pa[i]) / np.linalg.norm(np.cross(Da[i], Pa[i])) + c = Pa[i] / np.linalg.norm(Pa[i]) + A = np.vstack([a, b, c]) + + V.append(np.linalg.solve(A, M)) + + return V + +# --------------------------------------------------------------------------- + +def genKitahara_NW(): + ''' NW OR matrices + + **genKitahara_NW** returns the rotation matrices for the 12 variants of the + Nishiyama-Wasserman orientation relationship in the order presented in + Table 2 of Kitahara [1]_. + + Parameters + ---------- + None + + Returns + ------- + V : 21 x 1 list of 3 x 3 numpy matrices + + Notes + ----- + Table in Reference [1]_ has an error in the gamma direction for + variant #10! For table 10 to agree with Table 6, the gamma direction must + be [ 2 -1 1] and not [-1 2 1]. + + See also + -------- + genKitahara_KS, genBainCorrMatrices + + References + ---------- + .. [1] Kitahara et al, Mater Charact (2006) p378. + ''' + import numpy as np + + Pg = [0] * 12 + Pa = [0] * 12 + Dg = [0] * 12 + Da = [0] * 12 + + i = 0 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 2.0, -1.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 1 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 2.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 2 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, 2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + + i = 3 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-2.0, -1.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 4 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 2.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 5 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, -1.0, 2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 6 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 2.0, 1.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 7 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -2.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 8 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 1.0, 2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 9 + Pg[i] = [-1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 2.0, -1.0, 1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 10 + Pg[i] = [-1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 2.0, 1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 11 + Pg[i] = [-1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, -2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + V = [] + + for i in range(0, 12): + + a = Dg[i] / np.linalg.norm(Dg[i]) + b = np.cross(Dg[i], Pg[i]) / np.linalg.norm(np.cross(Dg[i], Pg[i])) + c = Pg[i] / np.linalg.norm(Pg[i]) + M = np.vstack([a, b, c]) + + a = Da[i] / np.linalg.norm(Da[i]) + b = np.cross(Da[i], Pa[i]) / np.linalg.norm(np.cross(Da[i], Pa[i])) + c = Pa[i] / np.linalg.norm(Pa[i]) + A = np.vstack([a, b, c]) + + V.append(np.linalg.solve(A, M)) + + return V + +# --------------------------------------------------------------------------- + +def genZirconia(): + + from cryspy import rot + import numpy as np + #%% Lattice correspondence A + + Abca1 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, + g21=1.0, g22=0.0, g23=0.0, + g31=0.0, g32=1.0, g33=0.0) + + Abca2 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, + g21=-1.0, g22=0.0, g23=0.0, + g31=0.0, g32=-1.0, g33=0.0) + + Acba1 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, + g21=0.0, g22=1.0, g23=0.0, + g31=-1.0, g32=0.0, g33=0.0) + + Acba2 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, + g21=0.0, g22=-1.0, g23=0.0, + g31=1.0, g32=0.0, g33=0.0) + + Abca3 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, + g21=-1.0, g22=0.0, g23=0.0, + g31=0.0, g32=1.0, g33=0.0) + + Abca4 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, + g21=1.0, g22=0.0, g23=0.0, + g31=0.0, g32=-1.0, g33=0.0) + + Acba3 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, + g21=0.0, g22=-1.0, g23=0.0, + g31=-1.0, g32=0.0, g33=0.0) + + Acba4 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, + g21=0.0, g22=1.0, g23=0.0, + g31=1.0, g32=0.0, g33=0.0) + + corrA = [Abca1, Abca2, Acba1, Acba2, Abca3, Abca4, Acba3, Acba4] + corrtA = ['Abca1', 'Abca2', 'Acba1', 'Acba2', 'Abca3', 'Abca4', 'Acba3', 'Acba4'] + + + Bcab1 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, + g21=0.0, g22=0.0, g23=1.0, + g31=1.0, g32=0.0, g33=0.0) + + Bcab2 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, + g21=0.0, g22=0.0, g23=1.0, + g31=-1.0, g32=0.0, g33=0.0) + + Bacb1 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, + g21=0.0, g22=0.0, g23=1.0, + g31=0.0, g32=1.0, g33=0.0) + + Bacb2 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, + g21=0.0, g22=0.0, g23=1.0, + g31=0.0, g32=-1.0, g33=0.0) + + Bcab3 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, + g21=0.0, g22=0.0, g23=-1.0, + g31=-1.0, g32=0.0, g33=0.0) + + Bcab4 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, + g21=0.0, g22=0.0, g23=1.0, + g31=1.0, g32=0.0, g33=0.0) + + Bacb3 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, + g21=0.0, g22=0.0, g23=-1.0, + g31=0.0, g32=-1.0, g33=0.0) + + Bacb4 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, + g21=0.0, g22=0.0, g23=-1.0, + g31=0.0, g32=1.0, g33=0.0) + + corrB = [Bcab1, Bcab2, Bacb1, Bacb2, Bcab3, Bcab4, Bacb3, Bacb4] + corrtB = ['Bcab1', 'Bcab2', 'Bacb1', 'Bacb2', 'Bcab3', 'Bcab4', 'Bacb3', 'Bacb4'] + + Cabc1 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, + g21=0.0, g22=1.0, g23=0.0, + g31=0.0, g32=0.0, g33=1.0) + + Cabc2 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, + g21=0.0, g22=-1.0, g23=0.0, + g31=0.0, g32=0.0, g33=1.0) + + Cbac1 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, + g21=-1.0, g22=0.0, g23=0.0, + g31=0.0, g32=0.0, g33=1.0) + + Cbac2 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, + g21=1.0, g22=0.0, g23=0.0, + g31=0.0, g32=0.0, g33=1.0) + + Cabc3 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, + g21=0.0, g22=1.0, g23=0.0, + g31=0.0, g32=0.0, g33=-1.0) + + Cabc4 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, + g21=0.0, g22=-1.0, g23=0.0, + g31=0.0, g32=0.0, g33=-1.0) + + Cbac3 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, + g21=-1.0, g22=0.0, g23=0.0, + g31=0.0, g32=0.0, g33=-1.0) + + Cbac4 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, + g21=1.0, g22=0.0, g23=0.0, + g31=0.0, g32=0.0, g33=-1.0) + + corrC = [Cabc1, Cabc2, Cbac1, Cbac2, Cabc3, Cabc4, Cbac3, Cbac4] + corrtC = ['Cabc1', 'Cabc2', 'Cbac1', 'Cbac2', 'Cabc3', 'Cabc4', 'Cbac3', 'Cbac4'] + + return np.hstack([corrA, corrB, corrC]), np.hstack([corrtA, corrtB, corrtC]) + +# --------------------------------------------------------------------------- + +def lattParamAus_Onink(at_pct_carbon, temperature_degC): + ''' estimate austenite lattice parameter at temperature + + **latticeParamAus_Onink** estimates the lattice parameter of the + face-centered cubic austenite phase at high temperature as a function of + carbon content based on the best fit equations of Onink et al [1]_. + Lattice parameter is returned in angstroms. + + Parameters + ---------- + at_pct_carbon : float + atomic percent of carbon + + temperature_degC : float + temperature in degrees celsius + + Returns + ------- + a0 : float + lattice parameter of austenite (gamma) phase in angstroms + + Notes + ----- + Verified validity range varies with temperature and carbon content + (Ref [1]_, Table 2): + + ======= ====================== + at % C Temperature Range in K + ======= ====================== + 0.05 1080-1250 + 1.30 1080-1250 + 1.75 1060-1250 + 2.60 1030-1250 + 3.79 1000-1250 + ======= ====================== + + References + ---------- + .. [1] M Onink, C. M. Brakman, F. D. Tichelaar, E. J. Mittemeijer, + S. van der Zwaag. "The lattice parameters of austenite and ferrite + in Fe-C alloys as functions of carbon concentration and + temperature." Scripta Metall Mater 29 (1993) p1011-1016. + + Examples + -------- + >>> # Reproduce Figure 2 from Onink et al [1]_. The x-axis will be similar + >>> # but not *exactly* the same because we are plotting atomic percent + >>> # instead of number C atoms / 100 Fe atoms. + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import constants + >>> temperature_degC = constants.K2C(np.arange(1000.0, 1275.0, 25.0)) + >>> for at_pct_carbon in [0.0, 1.3, 1.75, 2.6, 3.65]: + >>> print(at_pct_carbon) + >>> a0 = latticeParamAus_Onink(at_pct_carbon, temperature_degC) + >>> # For consistency in cryspy, our inputs are in degC + >>> # and output is in angstroms. + >>> # Converting for comparison to the published plot... + >>> plt.plot(constants.C2K(temperature_degC), a0/10.0) + >>> plt.legend(['Fe', 'Fe-0.3C', 'Fe-0.4C', 'Fe-0.6C', 'Fe-0.8C']) + >>> plt.ylim([0.3630, 0.3690]) + >>> plt.xlim([950, 1350]) + >>> plt.xlabel('Temperature (K)') + >>> plt.ylabel('Lattice parameter of austenite (nm)') + >>> plt.show() + ''' + from scipy import constants + tempK = constants.C2K(temperature_degC) + a0 = (0.363067 + 0.000783 * at_pct_carbon) * \ + (1.0 + (24.92 - 0.51 * at_pct_carbon) * 1.0E-6 * (tempK - 1000.0)) + return a0 * 10.0 + +# --------------------------------------------------------------------------- + +def lattParamMart_Kurdjumov(c_wt_pct, a0_angstroms): + ''' RTP Fe alpha prime parameters as function of wt%C + + **lattParams_Kurdjumov** calculates room temperature lattice parameters + for austenite and martensite from carbon content. + + Parameters + ---------- + c_wt_pct : float + weight percent carbon + a0 : float + austenite lattice parameter in angstroms + + Returns + ------- + a : float + martensite a axis lattice parameter in angstroms + c : float + martensite c axis lattice parameter in angstroms + + Notes + ----- + Equations come from Ref. [1]_ with reference to Ref. [2]_ and [3]_ for + discovery, [4]_ and [5]_ for validation, and [6]_ and [7]_ for showing that + the same equations are acceptable for alloy steels. + + References + ---------- + .. [1] Z. Fan et al. Phys Rev B 52(14) (1995) p9979. + .. [2] G. V. Kurdjumow and E. Kaminsky, Nature 122 (1928) p425. + .. [3] G. V. Kurdjumov and E. Kaminsky, Z Phys 53 (1929) p696. + .. [4] G. V. Kurdjumov, J ISIL 195 (1960) p26. + .. [5] C. S. Roberts, Trans AIME 197 (1953) p203 + .. [6] Z. Nishiyama and M. Doi, J. JIM 8 (1944) p305 + .. [7] P. G. Winchell and M. Cohen, Trans AIME 55 (1962) p347. + ''' + # c in atomic percent + + a = a0_angstroms - 0.013 * c_wt_pct + c = a0_angstroms + 0.116 * c_wt_pct + + return a, c + +# ---------------------------------------------------------------------------- + +def lattParams_RobertsCohen(c_at_pct): + ''' RTP Fe alpha prime & gamma parameters as function of at%C + + **lattParams_RobertsCohen** calculates room temperature lattice parameters + for austenite and martensite from carbon content. + + Parameters + ---------- + c_at_pct : float + atomic percent carbon + + Returns + ------- + a0 : float + austenite lattice parameter in angstroms + a : float + martensite a axis lattice parameter in angstroms + c : float + martensite c axis lattice parameter in angstroms + + Notes + ----- + Atomic pct equations come from Ref. [1]_ using the data obtained in + Ref. [2]_. + + References + ---------- + .. [1] M. Cohen, "The Strengthening of Steel." Transactions of the + Metallurgical Society of AIME 224 (1962) p637-657. + .. [2] C.S. Roberts, "Technical Note: Effect of Carbon on the Volume + Fractions and Lattice Parameters Of Retained Austenite and + Martensite." Journal of Metals, AIME Transactions (Institute of + Metals Division) 197 (1953). p203-204. + ''' + # c in atomic percent + + a0 = 3.548 + 0.0094 * c_at_pct # units of angstroms + a = 2.861 - 0.0028 * c_at_pct + c = 2.861 + 0.0247 * c_at_pct + + return a0, a, c + +# ---------------------------------------------------------------------------- + +def lattParams_Lee(c_at_pct, temp_degC): + ''' Temperature dependent alpha & gamma prime parameters as function of %C + + Parameters + ---------- + c_at_pct : float + atomic percent carbon in austenite + temp_degC : float + temperature in degrees C + + Returns + ------- + a0 : float + austenite lattice parameter in angstroms + a : float + martensite a axis lattice parameter in angstroms + c : float + martensite c axis lattice parameter in angstroms + + Notes + ----- + We assume in this function that the carbon content in austenite and + martensite has to be the same, since the martensite forms from the + austenite. + + References + ---------- + .. [1] S-J Lee et al. Acta Mater 55 (2007) p875 + ''' + + tt = temp_degC + 273.15 + + betaG = (24.9 - 0.5 * c_at_pct) * 1.0E-6 + a0 = (0.36306 + 7.83E-4 * c_at_pct) * (1.0 + betaG * (tt - 1000.0)) + + betaM = (14.9 - 1.9 * c_at_pct) * 1.0E-6 + c = (0.28610 + 0.0025855 * c_at_pct) * (1.0 + betaM * (tt - 273.0)) + a = (0.28610 - 0.0002898 * c_at_pct) * (1.0 + betaM * (tt - 273.0)) + + return a0, a, c + +# --------------------------------------------------------------------------- + +def martstart_YangBhadeshiaAdjustment(G, Ms0=363.5, f=0.01, m=0.05, a=1, b=0.2689): + ''' + Predicts reduction in martensite start temperature with reduction in + austenite grain size according to geometric model. + + Parameters + ---------- + Ms0 : float + Martensite start temperature at limit of infinitely large grains, which + is a function of composition. + f : float + Detectable martensite fraction + m : float + Martensite plate aspect ratio + a : float + fitting constant, units 1/mm^3 + b : float + fitting constant, unitless + G : float + ASTM grain size number + + Returns + ------- + Ms : float + Martensite start temperature, in degC + + Notes + ----- + All input values default to those from [1]_. The user should note that Ms0 + will vary with composition. + + + References + ---------- + ..[1] Yang & Bhadeshia, Scripta Mater 60 (2009) p493 + ..[2] p90-91 of Underwood, Quantitative Stereology. Reading, Mass: Addison- + Wesley Publishing Co., 1970. + ''' + import numpy as np + + # Convert the ASTM grain size to the mean grain section area in sq microns + A_gamma = 100.0 * 25.4**2 / 2.0**(G - 1.0) + + # Convert to sq millimeters + A_gamma = A_gamma / 1000.0**2 + + # For lack of a better option at this time, we will assume spherical grains + # and use the equivalent spherical volume for the mean cross sectional + # area. The mean sectional area of a sphere is 2/3*pi*r^2. If we set this + # equal to unity, then r=sqrt(3/2/pi). The volume of a sphere with this + # radius is 4*pi/3*(3/2/pi)^3/2 + V_gamma = A_gamma * 4.0 * np.pi / 3.0 * (3.0 / 2.0 / np.pi)**(3.0 / 2.0) + + Ms = Ms0 - (1.0 / b) * np.log((1.0 / (a * V_gamma)) * \ + (np.exp(-np.log(1.0 - f) / m) - 1.0) + 1.0) + + return Ms + +# --------------------------------------------------------------------------- + +def martstart_vanBohemen2017(G, C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, + Mo=0.0, Si=0.0, K_C=370.0, K_HP=350.0, + K_1=1015.0, D_C=15.0): + ''' predicts the martensite start temperature from composition and austenite grain size + + Parameters + ---------- + G : float + Austenite ASTM grain size number + C : float + wt% C + Mn : float + wt% Mn + Cr : float + wt% Cr + Ni : float + wt% Ni + Mo : float + wt% Mo + Si : float + wt% Si + K_C : float + J/mol + K_HP : float + J*(microns^0.5)/mol + D_C : float + Critical austenite equivalent diameter grain size for single packet + microstructures, with units of microns + K_1 : float {1015.0} + A constant depending on defect size, strain, and interfacial energyies, + with units of J/mol + + + Returns + ------- + Ms : float + martensite start temperature in degC + + Notes + ----- + The model does not account for the compositional effects of Nb, Ti, V, Al, + Cu,W and Co, since limited data is available for a thorough validation to + evaluate the coefficients Km for these elements [1]_. + + + References + ---------- + .. [1] van Bohemen & Morsdorf, Acta Mater 125 (2017) p401. + ''' + import numpy as np + + # The following are the fitting parameters and constants + # used by van Bohemen & Morsdorf + + # Convert ASTM GS to "diameter" in microns. + # Note that this is not really a diameter, but the square root of area. + D_gamma = np.sqrt(100.0 * 25.4**2 / 2.0**(G - 1.0)) + + # Non-chemical free energy term from the reduction in martensite lath + # aspect ratio that occurs as grain size is reduced + W_C = K_C * np.exp(-6.0 * D_gamma / D_C) + + # Non-chemical free energy term associated with Hall-Petch hardening + # of the austenite + W_HP = K_HP / np.sqrt(D_gamma) + + # Compositional dependence of athermal frictional work + # (as fit to experimental data) + W_mu = 670.0 * np.sqrt(C) + \ + np.sqrt((195.0 * np.sqrt(Mn))**2 + \ + (140.0 * np.sqrt(Si))**2 + \ + (170.0 * np.sqrt(Cr))**2 + \ + ( 5.0 * np.sqrt(Ni))**2 + \ + (205.0 * np.sqrt(Mo))**2) + + # Estimated change in Gibbs energy due to defect content, athermal + # frictional work, Hall-Petch hardening, and martensite lath aspect ratio + delGc = K_1 + W_mu + W_HP + W_C + + # van Bohemen's best fit to FactSage prediction of martensite start + # temperatures + T1 = 718.3 - 291.0*C - 24.0*Mn - 1.8*Si - 5.6*Cr - 18.4*Ni + 3.5*Mo + + # + ms = T1 - delGc / 7.22 + + return ms + +# --------------------------------------------------------------------------- + +def martstart_vanBohemen2012(C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, + Mo=0.0, Si=0.0): + ''' predicts the martensite start temperature from composition + + Parameters + ---------- + C : float + wt% C + Mn : float + wt% Mn + Cr : float + wt% Cr + Ni : float + wt% Ni + Mo : float + wt% Mo + Si : float + wt% Si + + Returns + ------- + Ms : float + martensite start temperature in degC + + Notes + ----- + Non-linear fit may be better for high C than Steven-Haynes. + + + References + ---------- + .. [1] van Bohemen, Mater Sci Tech 28 (2012) p487. + ''' + import numpy as np + sumKixi = 31.*Mn + 13.*Si + 10.*Cr + 18.*Ni + 12.*Mo + ms = 565.0 - sumKixi - 600.0*(1.0-np.exp(-0.96*C)) + return ms + +# ---------------------------------------------------------------------------- + +def martstart_StevenHaynesMod(C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, + Mo=0.0, Co=0.0, Si=0.0): + ''' predicts the martensite start temperature from composition + + Parameters + ---------- + C : float + wt% C + Mn : float + wt% Mn + Cr : float + wt% Cr + Ni : float + wt% Ni + Mo : float + wt% Mo + Co : float + wt% Co + Si : float + wt% Si + + Returns + ------- + Ms : float + martensite start temperature in degC + + Notes + ----- + - Implements modification from Ref. _[2] to equation originally from + Ref. _[1]. + - The equation should work reasonably well under the limits of + 0.6 wt%C, 4.9 wt% Mn, 12.2 wt% Cr, 12.5 wt% Ni, and 5.4 wt% Mo, 3.2 wt% Si, + and 9 wt% Co. + - Underestimates start temperature at high Mo contents + + References + ---------- + .. [1] W. Steven & A.G. Haynes. JISI 183 (1956) p349. + .. [2] C.Y. Kung & J.J. Rayment, MMTA 13A (1982) p328. + .. [3] G. Krauss, Steels: Processing, Structure, and Performance. + Materials Park, OH: ASM International (2008). (Ch 5, p64) + ''' + Ms = 561.0 - 474.0 * C - 33.0 * Mn - 17.0 * Cr - 17.0 * Ni - 21.0 * Mo +\ + (10.0 * Co - 7.5 * Si) + return Ms + + +def martstart_AndrewsLinearMod(C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, + Mo=0.0, Co=0.0, Si=0.0): + ''' predicts the martensite start temperature from composition + + Parameters + ---------- + C : float + wt% C + Mn : float + wt% Mn + Cr : float + wt% Cr + Ni : float + wt% Ni + Mo : float + wt% Mo + Co : float + wt% Co + Si : float + wt% Si + + Returns + ------- + Ms : float + martensite start temperature in degC + + Notes + ----- + - Implements modification from Ref. _[2] to equation originally from + Ref. _[1]. + - The equation should work reasonably well under the limits of + 0.6 wt%C, 4.9 wt% Mn, 12.2 wt% Cr, 12.5 wt% Ni, and 5.4 wt% Mo, 3.2 wt% Si, + and 9 wt% Co. + - Overestimates martensite start temperature for high chromium alloys + + References + ---------- + .. [1] K.W. Andrews, JISI 203 (1965) p721. + .. [2] C.Y. Kung & J.J. Rayment, MMTA 13A (1982) p328. + .. [3] G. Krauss, Steels: Processing, Structure, and Performance. + Materials Park, OH: ASM International (2008). (Ch 5, p64) + ''' + Ms = 539.0 - 423.0 * C - 30.4 * Mn - 12.1 * Cr - 17.7 * Ni - 7.5 * Mo +\ + (10.0 * Co - 7.5 * Si) + return Ms + +def martfract_HarrisCohen(deltaT): + ''' returns transformed fraction martensite as a function of undercooling + + Parameters + ---------- + deltaT : float + Amount of undercooling in degC + + Notes + ----- + - Developed for steels containing 1.1% C + + References + ---------- + .. [1] G. Krauss, Steels: Processing, Structure, and Performance. + Materials Park, OH: ASM International (2008). (Ch 5, p65) + .. [2] W.H. Harris & M. Cohen. Trans AIME 180 (1949) p447. + + See Also + -------- + cryspy.orrl.martstart_AndrewsLinearMod + cryspy.orrl.martstart_StephenHaynesMod + ''' + f = 1.0 - 6.96 * 1.0E-15 * (455.0 - deltaT)**5.32 + + return f + +# ---------------------------------------------------------------------------- + +def martfract_KoistenenMarburger(deltaT): + ''' returns transformed fraction martensite as a function of undercooling + + Parameters + ---------- + deltaT : float + Amount of undercooling in degC + + Notes + ----- + - Developed for steels containing 0.37-1.1 wt% C + + References + ---------- + .. [1] G. Krauss, Steels: Processing Structure and Performance. + Materials Park, OH: ASM International (2008). (Ch 5, p65) + .. [2] D.P. Koistenen & R.E. Marburger. Acta Metall 7 (1959) p59. + + See Also + -------- + cryspy.orrl.martstart_AndrewsLinearMod + cryspy.orrl.martstart_StephenHaynesMod + ''' + import numpy as np + + f = 1.0 - np.exp(-1.1E-2 * deltaT) + + return f + +# ---------------------------------------------------------------------------- + +def genBainCorrMatrices(): + ''' Bain corr matrices + + **genBainCorrMatrices** returns the rotation matrices for the 12 Bain + correspondence matrices, numbered according to parallelism of the c-axis + of the B phase. + + Parameters + ---------- + None + + Returns + ------- + V : 12 x 1 list of 3 x 3 numpy matrices + + Notes + ----- + Rounding the results of Kitahara_KS and removing redundancies gives the + list of Bain correspondence matrices. + + See also + -------- + genKitahara_NW, genBainCorrMatrices + + References + ---------- + .. [1] Kitahara et al, Acta Mater 54 (2006) p1279. + ''' + import numpy as np + + # Numbered according to parallelism of c-axis of B phase + c = 12 * [0] + + c[ 0] = np.mat([[ 1.0, -1.0, 0.0], + [ 1.0, 1.0, 0.0], + [ 0.0, 0.0, 1.0]]) + + c[ 1] = np.mat([[-1.0, -1.0, 0.0], + [ 1.0, -1.0, 0.0], + [ 0.0, 0.0, 1.0]]) + + c[ 2] = np.mat([[ 1.0, 1.0, 0.0], + [-1.0, 1.0, 0.0], + [ 0.0, 0.0, 1.0]]) + + c[ 3] = np.mat([[-1.0, 1.0, 0.0], + [ 1.0, 1.0, 0.0], + [ 0.0, 0.0, -1.0]]) + + c[ 4] = np.mat([[-1.0, 0.0, 1.0], + [ 1.0, 0.0, 1.0], + [ 0.0, 1.0, 0.0]]) + + c[ 5] = np.mat([[ 1.0, 0.0, -1.0], + [ 1.0, 0.0, 1.0], + [ 0.0, -1.0, 0.0]]) + + c[ 6] = np.mat([[-1.0, 0.0, -1.0], + [-1.0, 0.0, 1.0], + [ 0.0, 1.0, 0.0]]) + + c[ 7] = np.mat([[ 1.0, 0.0, 1.0], + [ 1.0, 0.0, -1.0], + [ 0.0, 1.0, 0.0]]) + + c[ 8] = np.mat([[ 0.0, 1.0, -1.0], + [ 0.0, 1.0, 1.0], + [ 1.0, 0.0, 0.0]]) + + c[ 9] = np.mat([[ 0.0, 1.0, 1.0], + [ 0.0, -1.0, 1.0], + [ 1.0, 0.0, 0.0]]) + + c[10] = np.mat([[ 0.0, -1.0, 1.0], + [ 0.0, 1.0, 1.0], + [-1.0, 0.0, 0.0]]) + + c[11] = np.mat([[ 0.0, -1.0, -1.0], + [ 0.0, 1.0, -1.0], + [ 1.0, 0.0, 0.0]]) + + return c + +# ---------------------------------------------------------------------------- + +def calcPTMT(a0, a, c, delta=1.0): + ''' perform PTMT calculations + + **calcPTMT** performs calculations of the phenomenological theory of + martensitic transformations using inputs of the lattice parameters of the + face-centered cubic (gamma) and body-centered-tetragonal (alpha prime) + phases. + + Parameters + ---------- + a0 : float + lattice parameter of the FCC austenite phase in angstroms. + a : float + lattice parameter for the a and b axes of the BC(C/T) martensite phase + in angstroms. + c : float + lattice parameter for the c axis of the BC(C/T) martensite phase in + angstroms. + + Returns + ------- + ksivals : n x 3 numpy float array + ksi angles for the orientation relationship + + detailtuple : tuple of floats + detailed results of the ptmt calculation. In order, the returns are: + (0) invariantPlaneNormal, (1) displacementVector, + (2) shapeDeformationMatrix, (3) shapeDeformationMagnitude, + (4) complimentaryShearDirection, (5) complimentaryShearMagnitude, + (6) complimentaryShearAngleRadians + + References + ---------- + .. [1] Z. Nishiyama, Martensitic Transformation. New York: Academic Press (1978). + .. [2] J. S. Bowles and J. K. Mackenzie. "The crystallography of martensite + transformations I." Acta Metall 2 (1954). + .. [3] J. K. Mackenzie and J. S. Bowles. "The crystallography of martensite + transformations II." Acta Metall 2 (1954). + .. [4] H. K. D. H. Bhadeshia, Worked Examples in the Geometry of Crystals, + 2nd Ed. Brookfield, VT: The Institute of Metals, 1987. + .. [5] C. M. Wayman, Introduction to the Crystallography of Martensitic + Transformations. New York: Macmillan Company (1964). + + Notes + ----- + Provided for ease of copy-and-paste, due to the unweildy outputs + + #from cryspy import orrl + #ksivals, resultsTuple = orrl.calcPTMT(a0, a, c) + #invariantPlaneNormal = resultsTuple[0] + #displacementVector = resultsTuple[1] + #shapeDeformationMatrix = resultsTuple[2] + #shapeDeformationMagnitude = resultsTuple[3] + #complimentaryShearDirection = resultsTuple[4] + #complimentaryShearMagnitude = resultsTuple[5] + #complimentaryShearAngleRadians = resultsTuple[6] + + #from cryspy import orrl + #import numpy as np + #a0, a, c = orrl.lattParams_RobertsCohen(np.arange(0, 5, 0.5)) + #ksivals = [] + #invariantPlaneNormal = [] + #displacementVector = [] + #shapeDeformationMatrix = [] + #shapeDeformationMagnitude = [] + #complimentaryShearDirection = [] + #complimentaryShearMagnitude = [] + #complimentaryShearAngleRadians = [] + #for i in np.arange(0, np.shape(a0)[0]): + # tmp, resultsTuple = orrl.calcPTMT(a0[i], a[i], c[i]) + # ksivals.append(tmp) + # invariantPlaneNormal.append(resultsTuple[0]) + # displacementVector.append(resultsTuple[1]) + # shapeDeformationMatrix.append(resultsTuple[2]) + # shapeDeformationMagnitude.append(resultsTuple[3]) + # complimentaryShearDirection.append(resultsTuple[4]) + # complimentaryShearMagnitude.append(resultsTuple[5]) + # complimentaryShearAngleRadians.append(resultsTuple[6]) + + Examples + ------- + >>> # Reproduce example from Nishiyama (with lattice parameters from Wayman) + >>> a0 = 3.591 # angstroms, gamma + >>> a = 2.875 # angstroms, alpha prime + >>> c = 2.875 # angstroms, alpha prime + >>> ksivals, resultsTuple = calcPTMT(a0, a, c) + >>> invariantPlaneNormal = resultsTuple[0] + >>> displacementVector = resultsTuple[1] + >>> shapeDeformationMatrix = resultsTuple[2] + >>> shapeDeformationMagnitude = resultsTuple[3] + >>> complimentaryShearDirection = resultsTuple[4] + >>> complimentaryShearMagnitude = resultsTuple[5] + >>> complimentaryShearAngleRadians = resultsTuple[6] + + >>> from cryspy import orrl + >>> import numpy as np + >>> a0, a, c = orrl.lattParams_RobertsCohen(np.arange(0, 5, 0.5)) + >>> ksivals = [] + >>> invariantPlaneNormal = [] + >>> displacementVector = [] + >>> shapeDeformationMatrix = [] + >>> shapeDeformationMagnitude = [] + >>> complimentaryShearDirection = [] + >>> complimentaryShearMagnitude = [] + >>> complimentaryShearAngleRadians = [] + >>> for i in np.arange(0, np.shape(a0)[0]): + >>> tmp, resultsTuple = orrl.calcPTMT(a0, a, c) + >>> ksivals.append(tmp) + >>> invariantPlaneNormal.append(resultsTuple[0]) + >>> displacementVector.append(resultsTuple[1]) + >>> shapeDeformationMatrix.append(resultsTuple[2]) + >>> shapeDeformationMagnitude.append(resultsTuple[3]) + >>> complimentaryShearDirection.append(resultsTuple[4]) + >>> complimentaryShearMagnitude.append(resultsTuple[5]) + >>> complimentaryShearAngleRadians.append(resultsTuple[6]) + ''' + + import numpy as np + from scipy.linalg import lstsq as sp_lstsq + from warnings import warn + + # set up dilatation parameters from inputs + # I added the anisotropic dilatation later, so it is implemented differently. + # The anisotropic case (del1 and del2) follows Otte, Acta Cryst 16 (1963) p8. + # The isotropic case follows the implementation description in Wayman's text. + if np.size(delta) == 1: + del1 = delta + del2 = delta + elif np.size(delta) == 2: + del1 = delta[0] + del2 = delta[1] + +# # In Python 3.6, I'm getting kernel death failures for bad parameter inputs. +# # Putting forth these warnings. +# if a0 <= 0: +# warn('Input parameters outside of acceptable ranges.\n'+ +# '(a0 <= 0)') +# +# if a0 / a <= c / a: +# warn('Input parameters outside of acceptable ranges.\n'+ +# '(a0/a <= c/a)') +# +# tmpval = 4.0 * (a**2 / a0**2)**2 + \ +# (4.0 * (c**2 / a0**2) - 8.0) * (a**2 / a0**2) + \ +# 2.0 - (c**2 / a0**2)**2 +# if tmpval > 0: +# warn('Input parameters outside of acceptable ranges.\n'+ +# '4x^2 + (4y-8)x + 2-y^2) > 0, where x=a^2/a0^2 and y=c^2/a0^2') +# +# if a / a0 < -0.27 * c / a + 1.086: +# warn('Input parameters outside of acceptable ranges.\n'+ +# '(a/a0 < -0.27 * ca + 1.086)') + + ''' + During PTMT calculations, several steps impose limits on the ranges of the + ratios a/a0, c/a, and c/a0. For example, to generate the Bain distortion + matrix, a0 must be greater than zero; in order to be able to calculate an + invariant line, a0/a must be less than or equal to c/a and + 4x^2 + (4y-8)x + 2-y^2) must be less than or equal to zero, where x is a^2/a0^2 + and y is c^2/a0^2; for calculation of the invariant plane normal, both c/a0 and + a/a0 must be greater than zero, and so on. The border between the valid and + invalid ranges is found to follow a/a0 < -0.27 * c/a + 1.086 with an r-squared + value in excess of 0.9999. + ''' + + # Using a0=3.591 and a=c=2.875 does not agree exactly with top of page 358 in + # Nishiyama (values from Wayman). Better with a=c=2.8747428847860585? + # Should we change both a and a0? + # a0 = 3.591 # angstroms, gamma + # a = 2.875 # angstroms, alpha prime + # c = 2.875 # angstroms, alpha prime + + slipPlaneFamilyBCT = [1, 1, 2] + slipDirectionFamilyBCT = [1, 1, -1] + + #%% Prepare inputs + cc = genBainCorrMatrices() + + slipPlaneFamilyBCT = np.mat(slipPlaneFamilyBCT / np.linalg.norm(slipPlaneFamilyBCT)) + slipDirectionFamilyBCT = np.mat(slipDirectionFamilyBCT / np.linalg.norm(slipDirectionFamilyBCT)) + + ## Below not used, but could probably be done... + ## Loop through Bain matrices + # O = [] # empty list to contain variant rotations + # for baindex in range(0, np.shape(cc)[0]): + cm = cc[0]#cc[baindex] + + #%% Create the Bain distortion matrix, bb + # an anisotropic dilatation would be added here, a la Otte, Acta Cryst 1963 + # Wayman discusses this possibility on p122-123. + eta1 = np.sqrt(2.0) * a / a0 + eta3 = c / a0 + + # Override for checking against Bhadeshia's example calculation + #eta1 = 1.136071 + #eta3 = 0.803324 + + # In agreement with p123 Wayman; p358 Nishiyama + + bb = np.mat([[eta1, 0.0, 0.0], + [0.0, eta1, 0.0], + [0.0, 0.0, eta3]]) + # this is bold B in Nishiyama; (fBf) in Wayman + + #%% Calculation of invariant lines. + # The equations below are hand-solved from the system of equations shown + # as Eq. 6 on page 358 of Nishiyama. + # Resulting values agree with Equations 6' in Nishiyama + x1 = -np.sqrt((1.0 - eta1**2) / (eta3**2 - eta1**2)) + x2a = -np.sqrt(1.0 - 2.0 * x1**2) + x2b = -x2a + x3 = -x1 + + xin = [np.mat([x1, x2a, x3]), np.mat([x1, x2b, x3])] # two possibilities + # results are in agreement with x1 and x2 in Wayman; xi1 and xi2 in Nishiyama + + #%% Calculation of invariant normal + # Resulting values agree with Equations 7' in Nishiyama + eta1i = 1.0 / eta1**2 + eta3i = 1.0 / eta3**2 + n1 = np.sqrt((1.0 - eta1i) / (eta3i - eta1i)) + n2a = np.sqrt(1.0 - 2.0 * n1**2) + n2b = -n2a + n3 = n1 + + nin = [np.mat([n1, n2a, n3]), np.mat([n1, n2b, n3])] # two possibilities + # results in agreement with ni1' and ni2' in Nishiyama; n1' and n2' in Wayman + # and the solutions on page 62 of Bhadeshia (with different values of the etas) + + #%% Lines after Bain distortion + # There are four possible combinations of x and n (top of page 359). + # Nishiyama only looks at one as an example in his textbook. + # Resulting value for xui agrees with equation 8, p359 + # Resulting value for pp2 agrees with equation 9, p359 + + # One could choose one of the four combinations this way + # for xindex in np.arange(0,2): + # for nindex in np.arange(0,2): + # I'm choosing not to do this here because we can get the ksi angles from + # just the first index, then populate the set of variants from the ksi angles. + xindex = 0 + nindex = 0 + + xi = xin[xindex] + ni = nin[nindex] + + # Convert BCT slip plane and family into FCC coordinates + slipPlaneFCC = slipPlaneFamilyBCT * cm + slipDirectionFCC = np.linalg.solve(cm, slipDirectionFamilyBCT.T) + + p2 = slipPlaneFCC / np.linalg.norm(slipPlaneFCC) + d2 = slipDirectionFCC / np.linalg.norm(slipPlaneFCC) + xui = xi * bb # x underscore sub i + pp2 = p2 * bb.I + pp2 = pp2 / np.linalg.norm(pp2) # p prime underscore sub 2 + + #%% Invariant line strain + # The numerical absolute values of u and v agree with bottom of page 359. + # The middle value of u is negative in the text but positive here. + # It appears that this is probably an error in the text. + u = np.cross(xi, p2) + v = np.cross(xui, pp2) + + # R1 and R2 seem to be transposed in Nishiyama vs. Wayman + rr1 = np.mat(np.vstack([xi, p2, u]).T) + rr2 = np.mat(np.vstack([xui, pp2, v]).T) + + # iS_0i, Eq 13 in Nishiyama. Agrees with values in Wayman, p126. + yy = rr2.T * bb * rr1 + + # Nishiyama Eq. 15. His result is obtained with ni[0] and xi[0]. + # Agrees with result in Wayman, p126. + zz = np.dot(ni, rr1).A1 + + # Now, to produce the solution, we can use the associative properties of + # the matrices to multiply (n'_i;i) by the matrix containing the rotation + # of beta about x_i (see Nishiyama Eqs 15 and 14, respectively.) + # When we multiply through and collect the necessary terms, we find: + tmp1 = np.array([[zz[1] * yy[1,0] + zz[2] * yy[2,0], + zz[2] * yy[1,0] - zz[1] * yy[2,0]], + [zz[1] * yy[1,1] + zz[2] * yy[2,1], + zz[2] * yy[1,1] - zz[1] * yy[2,1]], + [zz[1] * yy[1,2] + zz[2] * yy[2,2], + zz[2] * yy[1,2] - zz[1] * yy[2,2]]]) + + tmp2 = np.array([[zz[0] - zz[0] * yy[0, 0]], + [zz[1] - zz[0] * yy[0, 1]], # Note that the problem is + [zz[2] - zz[0] * yy[0, 2]]]) # now overspecified! + + + # Now we can solve the (overspecified) system for cosB and sinB + # If our system was not overspecified, we could use + # np.linalg.solve(tmp1, tmp2) + # (as we would in Matlab.) + + # It appears that matlab must convert to a least squares method + # for an overspecified system. + + # The numpy least squares function at the time of this writing has + # a bug that results in a segfault, so we are using the scipy version. + + # Numpy segfault issue documented here: + # https://github.com/numpy/numpy/issues/9891 + if (~np.any(np.isnan(tmp1)) or ~np.any(np.isnan(tmp2))): + tmp3 = sp_lstsq(tmp1, tmp2)[0] + cosB = tmp3[0] + sinB = tmp3[1] + else: + cosB = 1.0 + sinB = 0.0 + # This result is in agreement with Wayman as well. + + betamx = np.mat([[1.0, 0.0, 0.0], [0.0, cosB, -sinB], [0.0, sinB, cosB]]) + + # Create a dilatation parameter matrix + # See page 122-123 of Wayman text. Here we allow a tetragonal anisotropic + # dilation. + deltamx = np.mat(np.eye(3)) + deltamx[0,0] = del1 + deltamx[1,1] = del1 + deltamx[2,2] = del2 + #deltamx = np.mat([[1.2*dil, 0., 0.], [0., 1.2*dil, 0.], [0., 0., 0.9*dil]]) + + # The results for ss here agree with Eq 17. + # Also sgrees with (fSf) in Wayman p126. + # And agrees with (F S F) in Bhadeshia p63 (with different etas) + ss = deltamx * rr1 * betamx * yy * rr1.T + ## Check results. Should be <= round off error, approx 1E-15 + #print(np.mat(zz) * np.mat(betamx) * np.mat(yy) - np.mat(zz)) + + # Note that ss is also given by (fJf) * bb, where (fJf) is given by + # J = rr1 * betamx * rr2.T + + #%% Calculate the useful stuff + + # Calculate the invariant plane normal + p1 = p2 * np.linalg.inv(ss) - p2 + p1p = p1 / np.linalg.norm(p1) # agrees with Eq. 18 in Nishiyama and p127 Wayman + + # Calculate the displacement vector + d1 = (ss * d2 - d2) / (p1p * d2) # agrees with Eq. 19 Nishiyama and p127 Wayman + + # Magnitude of the shape deformation + m1 = np.linalg.norm(d1) # agrees with Eq. 20 + + # Direction of complimentary shear + y = np.mat(np.cross(p1p, np.mat([1.0, 0.0, 0.0]))).T # [1 0 0] in text p361 + d2 = (y - np.linalg.solve(ss, y)) / (p2 * y) + + # Magnitude of complimentary shear + m2 = np.linalg.norm(d2) + + # Angle of complimentary shear + alpha = np.arctan(0.5 * m2) + + # Shape deformation matrix (Bhadeshia) + delta = m1 * p1p * d1 + s = np.sqrt(m1**2 - delta**2) # FIXME: Refer back to Bhadeshia, where is this variable to be used? + pp = np.eye(3, 3) + m1 * d1 * p1p + + #%% Get orientation relationship between martensite and austenite + # Note that this gives the CORRECT orientation relationship for the c/a and + # a/a0 ratios for K-S. + + # Victoria said: "KSI is similarity-transformed version of J (i.e., + # expressed in martensite basis)" + # This would then be solved as follows: + # gamma = cm.A / np.tile(np.linalg.norm(cm.A, axis=1), [3, 1]).T + # vx = gamma * (a / a0) * aJg * gamma.T # some variant of the OR + # I don't find that this returns the expected result. + + + # Bhadeshia writes the following, but the determinant isnt unity + aJg = cm * ss.I + # However, if we multiply by a/a0, the expected det=1 matrix is returned + # Matrices compare well to the T matrices in Kitahara + + vx = (a / a0) * aJg # some variant of the orientation relationship + #print(np.linalg.det(vx)) # check that determinant is unity + + + # This works for only certain variants. For example, it gets the right + # ksi angles for Kitahara T4 and T24 in the KS paper; however, it does not + # get the expected result for Kitahara T18 in the KS paper. + # Returns expected results for Kitahara T1 and T11 and in NW paper. + #vx = np.matrix([[0.742, -0.667, -0.075], + # [0.650, 0.742, -0.167], # check using Kitahara matrix + # [0.167, 0.075, 0.983]]) # for KS (#T24) + #vx = np.matrix([[0.667, -0.742, 0.075], + # [0.742, 0.650, -0.167], # check using Kitahara matrix + # [0.075, 0.167, 0.983]]) # for KS (#T4) + #vs = np.matrix([[-0.075, -0.742, 0.667], + # [0.167, 0.650, 0.742], # check using Kitahara matrix + # [-0.983, 0.167, 0.075]])# for KS (#T18) + #vx = np.matrix([[0.000, 0.707, -0.707], + # [-0.169, 0.697, 0.697], # check using a Kitahara matrix + # [0.986, 0.120, 0.120]]) # for NW + #vx = np.matrix([[-0.707, 0.000, -0.707], + # [-0.120, -0.986, 0.120], # check using a Kitahara matrix + # [-0.697, 0.169, 0.697]]) # for NW + + # Now, to get the ksi angles, we first need the Bain correspondence for + # the representative variant + bt = np.round(vx) + + # The orientation of the variant matrix matters. NOT transposing gives the + # expected result for the Kitahara KS and NW matrices, so it appears that + # I am using the correct orientation (aus -> mart). + # bt = bt.T + # vx = vx.T + # print(vx) + # FIXME: Eventually this would be much more robust if we were to orient + # all variants against a standard variant and then get the ksi + # angles. + + g1 = vx.ravel()[0, 0:3] + b1 = bt.ravel()[0, 0:3] + g2 = vx.ravel()[0, 3:6] + b2 = bt.ravel()[0, 3:6] + g3 = vx.ravel()[0, 6:9] + b3 = bt.ravel()[0, 6:9] + + # individual ksi angles + tmpksi1 = np.real(np.arccos(np.dot(g1 / np.linalg.norm(g1), + b1.T / np.linalg.norm(b1)))) + tmpksi2 = np.real(np.arccos(np.dot(g2 / np.linalg.norm(g2), + b2.T / np.linalg.norm(b2)))) + ksi1 = np.amin([tmpksi1, tmpksi2]) * 180. / np.pi + ksi2 = np.amax([tmpksi1, tmpksi2]) * 180. / np.pi + ksi3 = np.real(np.arccos(np.dot(g3 / np.linalg.norm(g3), + b3.T / np.linalg.norm(b3)))) * 180. / np.pi + + #print(ksi1, ksi2, ksi3) + # TODO: Generate full set of variant matrices as an output + + #%% Compile results together + ksivals = np.array([ksi1, ksi2, ksi3]) + invariantPlaneNormal = p1p + displacementVector = d1 + shapeDeformationMatrix = pp + shapeDeformationMagnitude = m1 + complimentaryShearDirection = d2 + complimentaryShearMagnitude = m2 + complimentaryShearAngleRadians = alpha + + return ksivals, \ + (invariantPlaneNormal, displacementVector, \ + shapeDeformationMatrix, shapeDeformationMagnitude, \ + complimentaryShearDirection, complimentaryShearMagnitude, \ + complimentaryShearAngleRadians) + +# ---------------------------------------------------------------------------- + +def solvePTMTparams(ksi1, ksi2, ksi3, accuracy='default', + initial_guess=(0.8035, 1.047), + max_iter=1000): + """ + **solvePTMTparams** solves the PTMT using a least squares approach to + return the apparent a/a0 and c/a ratios for a given set of ksi parameters. + + Parameters + ---------- + ksi1 : float + ksi1 value + + ksi2 : float + ksi2 value + + ksi3 : float + ksi3 value + + accuracy : float (optional) + desired accuracy of the result. + + initial_guess : 2-tuple of floats (optional) + Initial guess values. initial_guess[0] should be the a/a0 ratio and + initial_guess[1] should be the c/a ratio. Defaults to (0.8035, 1.047). + + max_iter : int (optional) {1000} + Maximum number of search iterations. + + Returns + ------- + res : object + results of the optimization routine. See scipy.optimize.least_squares + for more outputs. + - res.x = optimized values of [a/a0, c/a] + - res.fun = residual sum of squares of solution + - res.nfev = number of function evalutions performed + - res.success = True if convergence criteria satisfied + - res.status = reason code for algorithm termination + - res.message = string reason for algorithm termination + """ + import numpy as np + import scipy.optimize as spop + + if accuracy=='default': + accuracy = np.spacing(np.float64(1.0)) + + # Initial guesses for a/a0 and c/a ratios + aa0i = initial_guess[0] + cai = initial_guess[1] + + # Set up objective function for minimization + def rmsPTMT(x): + + a0 = 3.6 # this value doesn't actually matter when there is no dilation + a = a0 * x[0] + c = x[1] * a + + try: + tmp, resultsTuple = calcPTMT(a0, a, c) + except: + tmp = 999999999999999999999999. * np.ones(3) + + return np.sum(np.sqrt((ksi1 - tmp[0])**2 + + (ksi2 - tmp[1])**2 + + (ksi3 - tmp[2])**2)) + + res = spop.minimize(rmsPTMT, x0=[aa0i, cai], + method='Nelder-Mead', + options={'maxiter': max_iter, + 'xatol': accuracy, + 'fatol': accuracy}) + + # Also tried the following minimizations, and neither worked as reliably... + + #res = spop.least_squares(rmsPTMT, x0=[aa0i, cai], bounds=[aa0b, cab], + # verbose=1, max_nfev=max_iter, gtol=accuracy, + # xtol=accuracy, ftol=accuracy) + + #out, rss, its, imode, smode = spop.fmin_slsqp(rmsPTMT, x0=[aa0i, cai], + # bounds=[aa0b, cab], + # full_output=True, + # iter=iterations, + # acc=accuracy, + # #epsilon=1.E-12, + # iprint=1) + + return res + +# ---------------------------------------------------------------------------- + +def solvePTMTparams_dil(ksi1, ksi2, ksi3, a0, accuracy='default', + initial_guess=(0.8035, 1.047, 0.996, 1.052), + max_iter=1000): + """ + **solvePTMTparams** solves the PTMT using a least squares approach to + return the apparent a/a0 and c/a ratios for a given set of ksi parameters. + + Parameters + ---------- + ksi1 : float + ksi1 value + + ksi2 : float + ksi2 value + + ksi3 : float + ksi3 value + + accuracy : float (optional) + desired accuracy of the result. + + initial_guess : 2-tuple of floats (optional) + Initial guess values. initial_guess[0] should be the a/a0 ratio and + initial_guess[1] should be the c/a ratio. Defaults to (0.8035, 1.047). + + max_iter : int (optional) {1000} + Maximum number of search iterations. + + Returns + ------- + res : object + results of the optimization routine. See scipy.optimize.least_squares + for more outputs. + - res.x = optimized values of [a/a0, c/a] + - res.fun = residual sum of squares of solution + - res.nfev = number of function evalutions performed + - res.success = True if convergence criteria satisfied + - res.status = reason code for algorithm termination + - res.message = string reason for algorithm termination + """ + import numpy as np + import scipy.optimize as spop + + if accuracy=='default': + accuracy = np.spacing(np.float64(1.0)) + + # Initial guesses for a/a0 and c/a ratios + aa0i = initial_guess[0] + cai = initial_guess[1] + del1i = initial_guess[2] + del2i = initial_guess[3] + + # Set up objective function for minimization + def rmsPTMT(x): + + a = a0 * x[0] + c = x[1] * a + del1 = x[2] + del2 = x[3] + + try: + tmp, resultsTuple = calcPTMT(a0, a, c, delta=[del1, del2]) + except: + tmp = 999999999999999999999999. * np.ones(3) + + # I am applying a simple weighting here because + # experience is suggesting that ksi1 is harder to fit. + return np.sum(np.sqrt(3.0*(ksi1 - tmp[0])**2 + + 2.0*(ksi2 - tmp[1])**2 + + 1.0*(ksi3 - tmp[2])**2)) + + res = spop.minimize(rmsPTMT, x0=[aa0i, cai, del1i, del2i], + method='Nelder-Mead', + options={'maxiter': max_iter, + 'xatol': accuracy, + 'fatol': accuracy}) + + return res diff --git a/rot.py b/cryspy/rot.py old mode 100644 new mode 100755 similarity index 85% rename from rot.py rename to cryspy/rot.py index ccd8804..6538f71 --- a/rot.py +++ b/cryspy/rot.py @@ -5,11 +5,11 @@ ''' class angax(object): - """ + """ Angle/axis rotation representation. - + ... - + Attributes ---------- th : list, tuple, or numpy array of floats @@ -20,24 +20,24 @@ class angax(object): the axis y coordinate z : list, tuple, or numpy array of floats the axis z coordinate - + Notes ----- Axes are normalized during initialization """ - - def __init__(self,th=1,x=0,y=0,z=0): + + def __init__(self, th=0, x=1, y=0, z=0): import numpy as np # note that in the above initialization, the default values will be # be given for the remaining parameters if insufficient values are # passed to the function. The best way to avoid this problem is to # always type a=..., b=... etc. - + # check that the shapes of all are the same - if np.shape(th)==np.shape(x)==np.shape(y)==np.shape(z): - + if np.shape(th)==np.shape(x)==np.shape(y)==np.shape(z): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... th = np.asarray(th) x = np.asarray(x) y = np.asarray(y) @@ -51,33 +51,33 @@ def __init__(self,th=1,x=0,y=0,z=0): self.y = y * nrm self.z = z * nrm self.size = np.size(self.x) - + else: - print "angax construction error: check that the lengths of th, x,"\ - " y, and z are all the same." + print("angax construction error: check that the lengths of th, x,"\ + " y, and z are all the same.") #TODO: check into using warnings package return None #------------------------------------------------------------------------------- def numel(self): """ returns angax size - + Gives the number of elements in the angle/axis rotation object - + Parameters ---------- None - + Returns ------- n : int the number of elements in the rotation matrix object - + Examples -------- >>> import numpy as np >>> import cryspy.rot as rot - >>> my_angax = rot.angax(th=[np.pi/7.0, np.pi], + >>> my_angax = rot.angax(th=[np.pi/7.0, np.pi], ... x=[1, 2], y=[1, 1], z=[1, 5]) >>> numel = my_angax.numel() """ @@ -87,15 +87,15 @@ def numel(self): def to_array(self): ''' converts object to array - + Converts angax object to an n x 4 numpy array where the first column corresponds to the angle and the final three columns correspond to the axis. - + Parameters ---------- None - + Returns ------- a : angax object @@ -106,20 +106,20 @@ def to_array(self): >>> import cryspy.rot as rot >>> my_angax = rot.angax(th=0.785, x=1.0, y=1.0, z=0.0) >>> my_array = my_angax.to_array() - + See Also -------- np.array : numpy array ''' import numpy as np return np.vstack([self.x, self.y, self.z]).T - + #------------------------------------------------------------------------------- - + @classmethod def from_array(cls, arg): ''' create angax object from numpy array - + Converts an n x 4 numpy array where the first column corresponds to angles (in radians) and the latter three columns correspond to the axes into an angax object. @@ -128,19 +128,19 @@ def from_array(cls, arg): ---------- arg : n x 4 numpy array array to be converted into angax object - + Returns ------- a : angax object object containing the angle/axis representation of the rotation - + Examples -------- >>> import numpy as np >>> import cryspy.rot as rot >>> my_array = [[np.pi, 1.0, 0.0, 0.0], [np.pi/4.0, 0.0, 1.0, 3.0]] - >>> my_angax = rot.angax.from_array( my_array ) - + >>> my_angax = rot.angax.from_array( my_array ) + See Also -------- np.array : numpy array @@ -154,25 +154,25 @@ def from_array(cls, arg): arg = arg.T return cls(arg[:,0], arg[:,1], arg[:,2], arg[:,3]) else: - print 'input is not an n x 4 numpy array' + print('input is not an n x 4 numpy array') # TODO: check into using warnings #------------------------------------------------------------------------------- - + @classmethod def from_bunge(cls, arg): ''' convert bunge to angax - + Converts Bunge Euler angle object to angle/axis representation. - + Parameters ---------- arg : Bunge object containing Euler angles - + Returns ------- a : angax object object containing the angle/axis representation of the rotation - + Examples -------- >>> import cryspy.rot as rot @@ -181,7 +181,7 @@ def from_bunge(cls, arg): ... PHI =[np.pi/2.0, np.pi/8.0], ... phi2=[np.pi/4.0, np.pi/6.0]) >>> my_angax = rot.angax.from_bunge( my_euler ) - + See Also -------- bunge : Bunge Euler angle rotation object @@ -189,17 +189,17 @@ def from_bunge(cls, arg): return angax.from_quat(quat.from_bunge(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_quat(cls, arg): ''' convert quat to angax - + Converts quaternions to angle/axis representation. - + Parameters ---------- arg : quaternion object - + Returns ------- a : angax object @@ -214,61 +214,61 @@ def from_quat(cls, arg): ... c=[0.0, 1.0], ... d=[0.0, 0.0]) >>> my_angax = rot.angax.from_quat( my_quat ) - + See Also -------- rodri : Rodrigues vector object ''' import numpy as np - + aa=arg.a aa[aa>1.0]=1.0 - + an = 2.0 * np.arccos(aa) - - # angle/axis is not well-defined when angle approaches zero - loc = an >= np.sqrt(np.spacing(1)) - sqi = np.zeros(np.size(an)) + + # angle/axis is not well-defined when angle approaches zero + loc = an >= np.sqrt(np.spacing(1)) + sqi = np.zeros(np.size(an)) sqi[loc]= 1.0 / np.sin(0.5 * an[loc]) - + # assign axes xx = arg.b * sqi yy = arg.c * sqi zz = arg.d * sqi - + # do not allow rotations greater than pi loc = an > np.pi an[loc] = 2.0 * np.pi - an[loc] xx[loc] = -xx[loc] yy[loc] = -yy[loc] zz[loc] = -zz[loc] - + # when the angle is near zero, force the axis to be 1 0 0 fix = an < np.sqrt(np.spacing(1)) an[fix] = 0.0 xx[fix] = 1.0 yy[fix] = 0.0 zz[fix] = 0.0 - + # when the angle is very close to pi, make it pi fix = np.absolute(np.pi - an) < np.sqrt(np.spacing(1)) an[fix] = np.pi - + return cls(th=an,x=xx,y=yy,z=zz) #------------------------------------------------------------------------------- - + @classmethod def from_rodri(cls, arg): ''' convert rodri to angax - + Converts Rodrigues vectors to angle/axis representation - + Parameters ---------- arg : rodri object object containing Rodrigues vectors, see :rodri: - + Returns ------- a : angax object @@ -278,8 +278,8 @@ def from_rodri(cls, arg): -------- >>> import cryspy.rot as rot >>> my_rodri = rot.rodri() - >>> my_angax = rot.angax.from_rodri( my_rodri ) - + >>> my_angax = rot.angax.from_rodri( my_rodri ) + See Also -------- rodri : Rodrigues vector object @@ -287,69 +287,69 @@ def from_rodri(cls, arg): import numpy as np nrm=np.array([arg.r0, arg.r1, arg.r2, arg.r3]) nrm=1.0 / np.sqrt(np.sum(nrm**2.0, axis=0)) - - an=2.0 * np.arctan(nrm) + + an=2.0 * np.arctan(nrm) xx=arg.r1 * nrm yy=arg.r2 * nrm zz=arg.r3 * nrm - + return cls(th=an, x=xx, y=yy, z=zz) #------------------------------------------------------------------------------- - + @classmethod def from_rmat(cls, arg): ''' converts rotation matrices to angle/axis representation - + Parameters ---------- arg : rmat object rotation matrix - + Returns ------- angax : angax object - angle/axis rotation representation - + angle/axis rotation representation + See Also -------- rmat - + Notes ----- - * assumes active rotation matrix - + * assumes active rotation matrix + * adapted from Peter Kovesi's matlab code, which itself follows the - implementation suggested by Hartley & Zissermann. - + implementation suggested by Hartley & Zissermann. + Original header from Kovesi's code follows:: MATRIX2ANGLEAXIS - Homogeneous matrix to angle-axis description - + Usage: t = matrix2angleaxis(T) - + Argument: T - 4x4 Homogeneous transformation matrix Returns: t - 3-vector giving rotation axis with magnitude equal to the rotation angle in radians. - + See also: ANGLEAXIS2MATRIX, ANGLEAXIS2MATRIX2, ANGLEAXISROTATE, NEWANGLEAXIS, NORMALISEANGLEAXIS - + Copyright (c) 2008 Peter Kovesi School of Computer Science & Software Engineering The University of Western Australia pk at csse uwa edu au http://www.csse.uwa.edu.au/ - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without + files (the "Software"), to deal in the Software without restriction, subject to the following conditions: - - The above copyright notice and this permission notice shall be + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + The Software is provided "as is", without warranty of any kind. ''' # Following the implementation suggested by Hartley and Zisserman: @@ -359,25 +359,25 @@ def from_rmat(cls, arg): import numpy.linalg as npla n = np.size(arg.g11) v = np.complex128(np.zeros([n,9])) - d = np.zeros([n,3]) - + d = np.zeros([n,3]) + for i in range(0, n): dtmp,vtmp = npla.eig(np.array( [[arg.g11[i]-1.0, arg.g12[i], arg.g13[i]], [arg.g21[i], arg.g22[i]-1.0, arg.g23[i]], [arg.g31[i], arg.g32[i], arg.g33[i]-1.0]] )) - + v[i] = np.array([vtmp[0,0], vtmp[0,1], vtmp[0,2], vtmp[1,0], vtmp[1,1], vtmp[1,2], vtmp[2,0], vtmp[2,1], vtmp[2,2]]) - + d[i] = np.absolute(np.array([dtmp[0], dtmp[1], dtmp[2]])) - - + + # find the index of the smallest eigenvalue ind=np.argsort(d.T,axis=0).T - + # extract the appropriate eigenvectors xx=np.zeros(n) yy=np.zeros(n) @@ -389,41 +389,42 @@ def from_rmat(cls, arg): yy[i]=v[i,loc+3] zz[i]=v[i,loc+6] chk[i]=d[i,loc] # record the min eigenvalue - + # DEBUG + # TODO: check into using warnings package if any(chk > 0.001): - print 'At least one rotation matrix is dubious.\n' - print 'Location(s):\n' - print np.where(chk > 0.001) - + print('At least one rotation matrix is dubious.\n') + print('Location(s):\n') + print(np.where(chk > 0.001)) + chk=(xx**2.0 + yy**2.0 + zz**2.0)**0.5 - 1.0 if any(chk > 0.0001): - print 'At least one rotation matrix results in a non-unit axis\n' - print 'Location(s)\n' - print np.where(chk > 0.0001) - + print('At least one rotation matrix results in a non-unit axis\n') + print('Location(s)\n') + print(np.where(chk > 0.0001)) + # determine rotation angle tcq = arg.g11 + arg.g22 + arg.g33 - 1.0 tsq = xx * (arg.g32 - arg.g23) + yy * (arg.g13 - arg.g31) + zz * (arg.g21 - arg.g12) an = np.arctan2(tsq, tcq) - + # fix negative thetas rev = an < 0.0 an[rev] = -an[rev] xx[rev] = -xx[rev] yy[rev] = -yy[rev] zz[rev] = -zz[rev] - + # put in the range of 0 to pi loc=np.absolute(an)>np.pi an[loc] = 2.0 * np.pi - an[loc] xx[loc] = -xx[loc] yy[loc] = -yy[loc] zz[loc] = -zz[loc] - - return angax(th=an, x=xx, y=yy, z=zz) -#------------------------------------------------------------------------------- + return angax(th=an, x=xx, y=yy, z=zz) + +#------------------------------------------------------------------------------- def __repr__(self): import numpy as np @@ -432,45 +433,49 @@ def __repr__(self): '{0: >8s} {1:^6s} {2:^6s} {3:^6s} \n'.format('theta', 'x', 'y', 'z') fmtstr = '{0: >8.3f}\xb0 @ [{1: >6.3f} {2: >6.3f} {3: >6.3f}]\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.th[i] * 180.0/np.pi, - self.x[i], - self.y[i], + repstr += fmtstr.format(self.th[i] * 180.0/np.pi, + self.x[i], + self.y[i], self.z[i]) for i in np.arange(0, 3): repstr += ' {0: ^7s} {0: ^6s} {0: ^6s} {0: ^6s} \n'.format( '.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.th[i] * 180.0/np.pi, - self.x[i], - self.y[i], + repstr += fmtstr.format(self.th[i] * 180.0 / np.pi, + self.x[i], + self.y[i], self.z[i]) else: - + + th = np.atleast_1d(self.th.squeeze()) + x = np.atleast_1d(self.x.squeeze()) + y = np.atleast_1d(self.y.squeeze()) + z = np.atleast_1d(self.z.squeeze()) for i in np.arange(0, self.size): - repstr += fmtstr.format(self.th[i] * 180.0/np.pi, - self.x[i], - self.y[i], - self.z[i]) - + repstr += fmtstr.format(th[i] * 180.0 / np.pi, + x[i], + y[i], + z[i]) + repstr += '\n' return repstr #------------------------------------------------------------------------------- def __getitem__(self,index): - + an = self.th[index] xx = self.x[index] yy = self.y[index] zz = self.z[index] - + return angax(th=an, x=xx, y=yy, z=zz) #------------------------------------------------------------------------------- @@ -478,9 +483,9 @@ def __getitem__(self,index): class bunge(object): ''' Bunge Euler angles rotation representation. - + ... - + Attributes ---------- phi1 : list, tuple, or numpy array of floats @@ -501,22 +506,22 @@ def __init__(self, phi1=0,PHI=0,phi2=0): # be given for the remaining parameters if insufficient values are # passed to the function. The best way to avoid this problem is to # always type phi1=..., PHI=... etc. - + # check that the shapes of all are the same - if np.shape(phi1)==np.shape(PHI)==np.shape(phi2): - + if np.shape(phi1)==np.shape(PHI)==np.shape(phi2): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... self.phi1 = util.vecarrayconvert(phi1) self.PHI = util.vecarrayconvert(PHI) self.phi2 = util.vecarrayconvert(phi2) self.size = np.size(self.phi1) - + else: - print "bunge construction error: check that the lengths of phi1, "\ - "PHI, and phi2 are all the same." + print("bunge construction error: check that the lengths of phi1, "\ + "PHI, and phi2 are all the same.") # TODO: check into using warnings package return None - + #------------------------------------------------------------------------------- def numel(self): @@ -532,16 +537,16 @@ def to_array(self): def to_nfft(self): return matthies.from_bunge(self).to_nfft() - + #------------------------------------------------------------------------------- - + @classmethod def from_array(cls, arg): ''' create bunge object from numpy array ''' return cls(arg[:,0], arg[:,1], arg[:,2]) - + #------------------------------------------------------------------------------- @classmethod @@ -549,32 +554,32 @@ def from_angax(cls, arg): return bunge.from_rmat(rmat.from_angax(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_quat(cls, arg): return bunge.from_rmat(rmat.from_quat(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_rodri(cls, arg): return bunge.from_angax(angax.from_rodri(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_matthies(cls, arg): - import cryspy.util as util + import righthand as rh import numpy as np - - ind = ~util.isnegligible(arg.beta) - - phi1 = arg.alpha + + ind = ~rh.isnegligible(arg.beta) + + phi1 = arg.alpha phi1[ind] = phi1[ind] + np.pi / 2.0 - + phi2 = arg.gamma phi2[ind] = phi2[ind] + 3.0 * np.pi / 2.0 - + return bunge(phi1, arg.beta, phi2) #------------------------------------------------------------------------------- @@ -582,22 +587,22 @@ def from_matthies(cls, arg): @classmethod def from_rmat(cls, arg): import numpy as np - + # Make sure the g33 values are in range c2 = np.amax( \ np.vstack([np.amin( \ - np.vstack([arg.g33, + np.vstack([arg.g33, np.tile(1.0, arg.g33.shape[0])]).T,\ - axis=1), + axis=1), np.tile(-1.0, arg.g33.shape[0])]).T, axis=1) - p = np.absolute(c2) > 1.0 - np.sqrt(np.spacing(1)) - - s2i = 1.0 / np.sqrt(1.0 - c2**2.0) + p = np.absolute(c2) > 1.0 - np.sqrt(np.spacing(1.0)) + + s2i = 1.0 / (np.sqrt(1.0 - c2**2.0) + np.spacing(1.0)) c1 = -arg.g32 * s2i s1 = arg.g31 * s2i c3 = arg.g23 * s2i s3 = arg.g13 * s2i - + # fix points near poles c1[p] = arg.g11[p] s1[p] = arg.g12[p] @@ -608,20 +613,20 @@ def from_rmat(cls, arg): p1 = np.arctan2(s1, c1) P0 = np.arccos(c2) p2 = np.arctan2(s3, c3) - + # positive convention p1[p1<0] = p1[p1<0] + 2.0 * np.pi P0[P0<0] = P0[P0<0] + 2.0 * np.pi p2[p2<0] = p2[p2<0] + 2.0 * np.pi - - return cls(phi1=p1, PHI=P0, phi2=p2) + + return cls(phi1=p1, PHI=P0, phi2=p2) #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np - + repstr = '{0: ^29s}\n'.format('Bunge Euler angles (\xb0)') + \ '{0:-^29s}\n'.format('-') + \ @@ -632,25 +637,25 @@ def __repr__(self): n = self.size - 1 if n > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, - self.PHI[i] * 180.0/np.pi, + repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, + self.PHI[i] * 180.0/np.pi, self.phi2[i] * 180.0/np.pi) for i in np.arange(0, 3): repstr += ' {0:^7s}{0:^7s}{0:^7s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, - self.PHI[i] * 180.0/np.pi, + repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, + self.PHI[i] * 180.0/np.pi, self.phi2[i] * 180.0/np.pi) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, - self.PHI[i] * 180.0/np.pi, + repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, + self.PHI[i] * 180.0/np.pi, self.phi2[i] * 180.0/np.pi) - + repstr += '\n' return repstr @@ -667,9 +672,9 @@ class quat(object): ''' quaternion class Quaternion rotation representation. - + ... - + Attributes ---------- a : scalar, list, tuple, or numpy array of floats @@ -680,53 +685,53 @@ class quat(object): the second value of the vector part of the quaternion d : scalar, list, tuple, or numpy array of floats the third value of the vector part of the quaternion - + Notes ----- - The quaternion parameters (a, b, c, d) are normalized during initialization to produce a unit quaternion. - + - Quaternions cannot be used to represent improper rotations. - - - The convention is employed that convention that the scalar part of the - quaternion cannot take on negative values. The vector part is + + - The convention is employed that convention that the scalar part of the + quaternion cannot take on negative values. The vector part is automatically adjusted accordingly during initialization. - - - Coded with some inspiration from the robotics toolbox quaternion class by + + - Coded with some inspiration from the robotics toolbox quaternion class by Luis Fernando Lara Tobar and Peter Corke (governed by the Mozilla Public - License 1.1) and the mtex toolbox by R. Hielscher and H. Schaeben + License 1.1) and the mtex toolbox by R. Hielscher and H. Schaeben (governed by GPL2), and the matlab and octave functions for computer vision and image processing from Peter Kovesi. - + Examples -------- >>> q = quat() - + >>> q = quat(0.6070,-0.7043,0.0634,-0.3627) - + >>> a = 0.6070,0.9688 >>> b = -0.7043,0.1176 >>> c = 0.0634,0.0263 >>> d = -0.3627,0.2166 >>> q = quat(a, b, c, d) - + >>> a = [0.6070,0.9688] >>> b = [-0.7043,0.1176] >>> c = [0.0634,0.0263] >>> d = [-0.3627,0.2166] >>> q = quat(a, b, c, d) - + >>> import numpy as np >>> a = np.array([0.6070,0.9688]) >>> b = np.array([-0.7043,0.1176]) >>> c = np.array([0.0634,0.0263]) >>> d = np.array([-0.3627,0.2166]) >>> q = quat(a, b, c, d) - + >>> b = bunge(np.pi, np.pi/2, np.pi/7) >>> q = quat.from_bunge(b) ''' - + def __init__(self, a=1, b=0, c=0, d=0): # note that in the above initialization, the default values will be # be given for the remaining parameters if insufficient values are @@ -734,12 +739,12 @@ def __init__(self, a=1, b=0, c=0, d=0): # always type a=..., b=... etc. import numpy as np import cryspy.util as util - + # check that the shapes of all are the same - if np.shape(a)==np.shape(b)==np.shape(c)==np.shape(d): - + if np.shape(a)==np.shape(b)==np.shape(c)==np.shape(d): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... a = util.vecarrayconvert(a) b = util.vecarrayconvert(b) c = util.vecarrayconvert(c) @@ -751,13 +756,13 @@ def __init__(self, a=1, b=0, c=0, d=0): self.a = a * nrm self.b = b * nrm self.c = c * nrm - self.d = d * nrm + self.d = d * nrm self.size = np.size(self.a) - + else: - print "quat construction error: check that the lengths of a, b, "\ - "c, and d are all the same." - return None + print("quat construction error: check that the lengths of a, b, "\ + "c, and d are all the same.") # TODO: check into using warnings package + return None #------------------------------------------------------------------------------- @@ -775,25 +780,31 @@ def to_array(self): return np.vstack([self.a, self.b, self.c, self.d]).T #------------------------------------------------------------------------------- - + @classmethod def from_array(cls,arg): ''' create quaternion object from numpy array ''' - return quat(arg[:,0], arg[:,1], arg[:,2], arg[:,3]) - + import numpy as np + arg = np.atleast_2d(arg) + return quat(a=arg[:, 0], b=arg[:, 1], c=arg[:, 2], d=arg[:, 3]) + #------------------------------------------------------------------------------- - + @classmethod def from_angax(cls,arg): ''' convert angax to quat - + Conversion of angle/axis rotation representation to quaternion rotation representation - + + Parameters + ---------- + angax : angax class object + Notes - ----- - Follows algorithm from Peter Kovesi (pk at csse uwa edu au), School of + ----- + Follows algorithm from Peter Kovesi (pk at csse uwa edu au), School of Computer Science & Software Engineering, The University of Western Australia, http://www.csse.uwa.edu.au/ ''' @@ -803,44 +814,44 @@ def from_angax(cls,arg): y = -arg.y * an z = -arg.z * an hth = 0.5 * arg.th - sth = np.sin(hth) - - return cls(a=np.cos(0.5 * hth), b=x*sth, c=y*sth, d=z*sth) + sth = np.sin(hth) + + return cls(a=np.cos(hth), b=x*sth, c=y*sth, d=z*sth) #------------------------------------------------------------------------------- - + @classmethod def from_bunge(cls,arg): ''' convert bunge to quat - + Conversion of Bunge Euler angle rotation representation to quaternion rotation representation. - + Notes ----- - - follows an algorithm similar to the one used in mtex; however, + - follows an algorithm similar to the one used in mtex; however, gamma in mtex is phi2-2*pi/2. Here we use phi2+pi/2, which agrees with the Kocks chapter in Texture and Anisotropy. ''' import numpy as np - + ha = 0.5 * (arg.phi1 - np.pi/2) # half alpha hb = 0.5 * arg.PHI # half beta hg = 0.5 * (arg.phi2 + np.pi/2) # half gamma zz = np.zeros(np.size(ha)) # zeros array - + qa = quat(a=np.cos(ha), b=zz, c=zz, d=np.sin(ha)) qb = quat(a=np.cos(hb), b=zz, c=np.sin(hb), d=zz ) qc = quat(a=np.cos(hg), b=zz, c=zz, d=np.sin(hg)) - + return quat.conj(qa * qb * qc) #------------------------------------------------------------------------------- - + @classmethod def from_rodri(cls,arg): ''' convert rodri to quat - + Conversion of Rodrigues vector rotation representation to quaternion rotation representation. ''' @@ -851,9 +862,9 @@ def from_rodri(cls,arg): @classmethod def from_rmat(cls,arg): ''' convert rmat to quat - + Conversion of rotation matrices to quaternion rotation representation. - + Notes ----- - What happens when the rotation matrix describes an improper rotation? @@ -865,125 +876,125 @@ def from_rmat(cls,arg): uu = arg.g11 vv = arg.g22 ww = arg.g33 - + dd = np.zeros([np.size(uu), 4]) dd[:, 0] = np.sqrt(1.0 + uu + vv + ww) dd[:, 1] = np.sqrt(1.0 - uu - vv + ww) dd[:, 2] = np.sqrt(1.0 + uu - vv - ww) dd[:, 3] = np.sqrt(1.0 - uu + vv - ww) - + # maximum values and locations in dd mh = np.amax(dd, axis=1) j = np.argmax(dd, axis=1) - + mh = 0.50 * mh # convenience variable mi = 0.25 / mh # convenience variable - + n = np.size(arg.g11) - + a = np.zeros(n) b = np.copy(a) c = np.copy(a) d = np.copy(a) - + if n>1: - + x = j==0 a[x] = -mh[x] b[x] = ( arg.g23[x] - arg.g32[x]) * mi[x] c[x] = ( arg.g31[x] - arg.g13[x]) * mi[x] d[x] = ( arg.g12[x] - arg.g21[x]) * mi[x] - + x = j==1 d[x] = mh[x] c[x] = ( arg.g23[x] + arg.g32[x]) * mi[x] b[x] = ( arg.g31[x] + arg.g13[x]) * mi[x] a[x] =-( arg.g12[x] - arg.g21[x]) * mi[x] - + x = j==2 b[x] = mh[x] a[x] = (-arg.g23[x] + arg.g32[x]) * mi[x] d[x] = ( arg.g31[x] + arg.g13[x]) * mi[x] c[x] = ( arg.g12[x] + arg.g21[x]) * mi[x] - + x = j==3 c[x] = mh[x] d[x] = ( arg.g23[x] + arg.g32[x]) * mi[x] a[x] = (-arg.g31[x] + arg.g13[x]) * mi[x] b[x] = ( arg.g12[x] + arg.g21[x]) * mi[x] - + loc = a<0.0 a[loc] = -a[loc] b[loc] = -b[loc] c[loc] = -c[loc] d[loc] = -d[loc] - + else: - + if j==0: a = -mh b = ( arg.g23 - arg.g32) * mi c = ( arg.g31 - arg.g13) * mi d = ( arg.g12 - arg.g21) * mi - + if j==1: d = mh c = ( arg.g23 + arg.g32) * mi b = ( arg.g31 + arg.g13) * mi a =-( arg.g12 - arg.g21) * mi - + if j==2: b = mh a = (-arg.g23 + arg.g32) * mi d = ( arg.g31 + arg.g13) * mi c = ( arg.g12 + arg.g21) * mi - + if j==3: c = mh d = ( arg.g23 + arg.g32) * mi a = (-arg.g31 + arg.g13) * mi b = ( arg.g12 + arg.g21) * mi - + if a<0.0: a = -a b = -b c = -c d = -d - + a = np.squeeze(a) b = np.squeeze(b) c = np.squeeze(c) d = np.squeeze(d) - + return quat(a, b, c, d) #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np repstr = '{0: ^31s}\n'.format('quaternions') + \ '{0:-^31s}\n'.format('-') + \ ' {0:^6s} {1:^6s} {2:^6s} {3:^6s} \n'.format('a', 'b', 'c', 'd') fmtstr = '<{0: >6.3f} {1: >6.3f} {2: >6.3f} {3: >6.3f} >\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.a[i], - self.b[i], - self.c[i], + repstr += fmtstr.format(self.a[i], + self.b[i], + self.c[i], self.d[i]) for i in np.arange(0, 3): repstr += ' {0:^6s} {0:^6s} {0:^6s} {0:^6s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.a[i], - self.b[i], - self.c[i], - self.d[i]) + repstr += fmtstr.format(self.a[i], + self.b[i], + self.c[i], + self.d[i]) else: a = np.atleast_1d(self.a.squeeze()) @@ -992,7 +1003,7 @@ def __repr__(self): d = np.atleast_1d(self.d.squeeze()) for i in np.arange(0, self.size): repstr += fmtstr.format(a[i], b[i], c[i], d[i]) - + repstr += '\n' return repstr @@ -1000,38 +1011,38 @@ def __repr__(self): def __mul__(self, q2): ''' product - + Multiplication of quaternions - + Notes ----- q1 and q2 must be the same size or one must be single. ''' if isinstance(q2, quat): - # the following works if q1 and q2 are the same size or one of them + # the following works if q1 and q2 are the same size or one of them # is a single quaternion. It rightfully returns an error otherwise. a = self.a * q2.a - self.b * q2.b - self.c * q2.c - self.d * q2.d b = self.a * q2.b + self.b * q2.a + self.c * q2.d - self.d * q2.c c = self.a * q2.c + self.c * q2.a + self.d * q2.b - self.b * q2.d d = self.a * q2.d + self.d * q2.a + self.b * q2.c - self.c * q2.b - + loc = a < 0.0 a[loc] = -a[loc] b[loc] = -b[loc] c[loc] = -c[loc] d[loc] = -d[loc] - + return quat(a, b, c, d) -#------------------------------------------------------------------------------ - +#------------------------------------------------------------------------------ + def __truediv__(self, q): '''Returns quaternion misorientation.''' - + if isinstance(q, quat): qr = quat() - qr = self * q.inv() + qr = self * q.inv() return qr #------------------------------------------------------------------------------ @@ -1041,50 +1052,50 @@ def inv(self): '''Returns the quaternion inverse.''' qi = copy.copy(self) qi.a = -qi.a; - + return qi - + #------------------------------------------------------------------------------ def conj(self): ''' quaternion conjugate - + Returns the conjugate rotation for the quaternion - + ''' import copy qi = copy.copy(self) qi.b = -qi.b qi.c = -qi.c qi.d = -qi.d - + return qi - + #------------------------------------------------------------------------------ - + def __getitem__(self,index): - + aa = self.a[index] bb = self.b[index] cc = self.c[index] dd = self.d[index] - + return quat(a=aa, b=bb, c=cc, d=dd) #------------------------------------------------------------------------------ def symmetrize(self, s): ''' returns symmetrically equivalent rotations - + Parameters ---------- s : cryspy.xtal.symm object or cryspy.rot.quat object - + Returns ------- list : in which each entry is a symmetrically equivalent version - + Notes ----- Quaternions can only handle proper rotations. The symmetrize method @@ -1105,30 +1116,29 @@ def to_fundzone(self, s, qref=None): symmetry s ''' import numpy as np - - if qref==None: - qref=quat() - - s = s.rotations * qref - - # get the number of quaternions and the number of symmetries - m = s.size - n = self.size - + + if qref == None: + qref = quat() + + o = qref.inv() * self + s = s.rotations + + # get the number of quaternions and the number of symmetries + #m = s.size + #n = self.size + # compute all distances to fundamental regions - w = np.tile(s.a, [n, 1]).T * np.tile(self.a, [m, 1]) - x = np.tile(s.b, [n, 1]).T * np.tile(self.b, [m, 1]) - y = np.tile(s.c, [n, 1]).T * np.tile(self.c, [m, 1]) - z = np.tile(s.d, [n, 1]).T * np.tile(self.d, [m, 1]) - d = np.absolute(w + x + y + z) - + + + d = np.absolute(np.dot(s.to_array(), o.to_array().T)) + # locate and extract the fundamental region for each quaternion loc = np.argmax(d, axis=0) - q2f = s[[loc]].conj() - + q2i = s.inv() + # project to the fundamental region - qf = q2f * self - + qf = o * q2i[loc] + return qf #------------------------------------------------------------------------------ @@ -1136,26 +1146,26 @@ def to_fundzone(self, s, qref=None): @classmethod def rand(cls, n, kappa=0, mu=None): """ random quaternion(s) generated from von Mises-Fisher distribution - + Parameters ---------- n : int number of random quaternions - + kappa : float > 0 clustering parameter - + mu : quat instance location parameter (quaternion central to the distribution) - + Returns ------- qr : quat instance random quaternions - + See Also -------- - util.rand_vonMisesFisherM + util.rand_vonMisesFisherM """ import cryspy.util as util if mu == None: @@ -1163,10 +1173,10 @@ def rand(cls, n, kappa=0, mu=None): elif isinstance(mu, quat): mu = mu.to_array() else: - print 'quat.rand: mu must be an instance of the quat class' - - return quat.from_array(util.rand_vonMisesFisherM(n=n, - kappa=kappa, + print('quat.rand: mu must be an instance of the quat class') # TODO: check into using warnings package + + return quat.from_array(util.rand_vonMisesFisherM(n=n, + kappa=kappa, mu=mu)) #------------------------------------------------------------------------------ @@ -1174,12 +1184,12 @@ def rand(cls, n, kappa=0, mu=None): class rodri(object): """ Rodrigues vector class - + Created on Fri Apr 13 16:20:21 2012 - + @author: epayton """ - + def __init__(self, r1=0, r2=0, r3=0): # note that in the above initialization, the default values will be # be given for the remaining parameters if insufficient values are @@ -1187,26 +1197,26 @@ def __init__(self, r1=0, r2=0, r3=0): # always type r1=..., r2=... etc. import numpy as np import cryspy.util as util - + # check that the shapes of all are the same - if np.shape(r1)==np.shape(r2)==np.shape(r3): - + if np.shape(r1)==np.shape(r2)==np.shape(r3): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... r1 = util.vecarrayconvert(r1) r2 = util.vecarrayconvert(r2) r3 = util.vecarrayconvert(r3) self.r1 = r1 self.r2 = r2 - self.r3 = r3 - + self.r3 = r3 + self.size = np.size(r1) - + else: - print "rodri construction error: check that the lengths of r1, r2,"\ - " and r3 are all the same." - return None + print("rodri construction error: check that the lengths of r1, r2,"\ + " and r3 are all the same.") # TODO: check into using warnings package + return None #------------------------------------------------------------------------------- @@ -1218,9 +1228,9 @@ def numel(self): def to_array(self): import numpy as np return np.vstack([self.r1, self.r2, self.r3]).T - + #------------------------------------------------------------------------------- - + @classmethod def from_angax(cls, arg): import numpy as np @@ -1248,42 +1258,42 @@ def from_rmat(cls, arg): #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np repstr = '{0:^24s}\n'.format('Rodrigues vectors') + \ '{0:-^24s}\n'.format('-') + \ ' {0: ^6s} {1: ^6s} {2: ^6s} \n'.format('r1', 'r2', 'r3') fmtstr = '< {0: >6.3f} {1: >6.3f} {2: >6.3f} >\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.r1[i], - self.r2[i], + repstr += fmtstr.format(self.r1[i], + self.r2[i], self.r3[i]) for i in np.arange(0, 3): repstr += ' {0:^6s} {0:^6s} {0:^6s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.r1[i], - self.r2[i], - self.r3[i]) + repstr += fmtstr.format(self.r1[i], + self.r2[i], + self.r3[i]) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.r1[i], - self.r2[i], + repstr += fmtstr.format(self.r1[i], + self.r2[i], self.r3[i]) - + repstr += '\n' return repstr #------------------------------------------------------------------------------- -class rmat(object): +class rmat(object): ''' rotation matrix class - + object for containing and manipulating rotation matrices ''' @@ -1294,14 +1304,14 @@ def __init__(self, g11=1, g12=0, g13=0, g21=0, g22=1, g23=0, g31=0, g32=0, g33=1 # always type g11=..., g2=... etc. import numpy as np import cryspy.util as util - + # check that the shapes of all are the same if np.shape(g11)==np.shape(g12)==np.shape(g13)==\ np.shape(g21)==np.shape(g22)==np.shape(g23)==\ - np.shape(g31)==np.shape(g32)==np.shape(g33): - + np.shape(g31)==np.shape(g32)==np.shape(g33): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... self.g11 = util.vecarrayconvert(g11) self.g12 = util.vecarrayconvert(g12) self.g13 = util.vecarrayconvert(g13) @@ -1311,30 +1321,30 @@ def __init__(self, g11=1, g12=0, g13=0, g21=0, g22=1, g23=0, g31=0, g32=0, g33=1 self.g31 = util.vecarrayconvert(g31) self.g32 = util.vecarrayconvert(g32) self.g33 = util.vecarrayconvert(g33) - + self.size = np.size(g11) - + else: - print "rmat construction error: check that the lengths of g11, g12,"\ - " ... g33 are all the same." - return None + print("rmat construction error: check that the lengths of g11, g12,"\ + " ... g33 are all the same.") # TODO: check into using warnings package + return None #------------------------------------------------------------------------------ def numel(self): """ returns rmat size - + Gives the number of elements in the rotation matrix object - + Parameters ---------- None - + Returns ------- n : int the number of elements in the rotation matrix object - + Examples -------- >>> import cryspy.rot as rot @@ -1349,9 +1359,9 @@ def numel(self): def to_array(self): """ convert rmat to array - + Converts a rotation matrix object to an n x 9 numpy array. - + Examples -------- >>> import cryspy.rot as rot @@ -1359,25 +1369,25 @@ def to_array(self): >>> my_array = my_rmat.to_array() """ import numpy as np - return np.vstack([self.g11, self.g12, self.g13, + return np.vstack([self.g11, self.g12, self.g13, self.g21, self.g22, self.g23, self.g31, self.g32, self.g33]).T - + #------------------------------------------------------------------------------ @classmethod def from_array(cls,arg): """ convert array to rmat - + Class method for converting an n x 9 numpy array into an object of the rotation matrix class. - + Parameters ---------- arg : n x 9 numpy array of floats the numpy array from which to create the rmat - + Examples -------- >>> import numpy as np @@ -1390,8 +1400,8 @@ def from_array(cls,arg): s = np.shape(arg) if np.all(np.asarray(s) != 9): - print 'error: rotation object requires at least one dimension = 9' - + print('error: rotation object requires at least one dimension = 9') # TODO: check into using warnings package + if np.size(s) == 1: return cls(g11=arg[0], g12=arg[1], g13=arg[2], \ g21=arg[3], g22=arg[4], g23=arg[5], \ @@ -1399,26 +1409,26 @@ def from_array(cls,arg): else: return cls(g11=arg[:,0], g12=arg[:,1], g13=arg[:,2], \ g21=arg[:,3], g22=arg[:,4], g23=arg[:,5], \ - g31=arg[:,6], g32=arg[:,7], g33=arg[:,8]) - + g31=arg[:,6], g32=arg[:,7], g33=arg[:,8]) + #------------------------------------------------------------------------------ @classmethod def from_quat(cls,arg): """ convert quat to rmat - + Convert quaternion object to rotation matrix object - + Examples -------- >>> import cryspy.rot as rot >>> my_quat_obj = rot.quat() >>> my_rmat = rot.rmat.from_quat( my_quat_obj ) """ - + # user wants to convert quaternions to rotation matrices - + w = arg.a x = arg.b y = arg.c @@ -1428,14 +1438,14 @@ def from_quat(cls,arg): x2 = x**2.0 y2 = y**2.0 z2 = z**2.0 - + xy = x*y xz = x*z yz = y*z wx = w*x wy = w*y wz = w*z - + return cls( g11 = w2 + x2 - y2 - z2, g12 = 2.0 * (xy - wz), @@ -1448,20 +1458,20 @@ def from_quat(cls,arg): g33 = w2 - x2 - y2 + z2) #------------------------------------------------------------------------------ - + @classmethod - def from_bunge(cls,arg): + def from_bunge(cls,arg): # user wants to convert Bunge Euler angles to rotation matrices # algorithm from wikipedia - import numpy as np - + import numpy as np + c1 = np.cos(arg.phi1) c2 = np.cos(arg.PHI) c3 = np.cos(arg.phi2) s1 = np.sin(arg.phi1) s2 = np.sin(arg.PHI) s3 = np.sin(arg.phi2) - + # ZXZ combination return cls( g11 = c1 * c3 - s1 * c2 * s3, @@ -1469,32 +1479,32 @@ def from_bunge(cls,arg): g13 = s2 * s1, g22 = -s1 * s3 + c1 * c2 * c3, g21 = c3 * s1 + s3 * c2 * c1, - g23 = -s2 * c1, + g23 = -s2 * c1, g31 = s3 * s2, g32 = c3 * s2, g33 = c2) #------------------------------------------------------------------------------ - + @classmethod def from_angax(cls,arg): """ Create rmat from angax - + Converts an angle/axis to a passive rotation matrix - + Parameters ---------- arg : angax object object containin angle/axis pair - + Returns ------- rmat : rmat object rotation matrix corresponding to angle/axis input - + Notes ----- - This converts an angle/axis rotation to a passive rotation matrix. + This converts an angle/axis rotation to a passive rotation matrix. """ import numpy as np r1 = arg.x @@ -1509,7 +1519,7 @@ def from_angax(cls,arg): c = np.cos(arg.th) s = np.sin(arg.th) cm = 1 - c - + return cls( g11 = r1s * cm + c, g12 = r12 * cm - r3 * s, @@ -1522,35 +1532,35 @@ def from_angax(cls,arg): g33 = r3s * cm + c) #------------------------------------------------------------------------------ - + @classmethod def from_rodri(cls,arg): - return cls.from_angax(angax.from_rodri(arg)) + return cls.from_angax(angax.from_rodri(arg)) #------------------------------------------------------------------------------ def __repr__(self): - + import numpy as np repstr = '{0:^72s}\n'.format('rotation matrices') + \ '{0:-^72s}\n'.format('-') + \ ' {0:^6s} {1:^6s} {2:^6s} '.format('g11', 'g12', 'g13') + \ ' {0:^6s} {1:^6s} {2:^6s} '.format('g21', 'g22', 'g23') + \ ' {0:^6s} {1:^6s} {2:^6s} \n'.format('g31', 'g32', 'g33') - + fmtstr = '[{0: >6.3f}, {1: >6.3f}, {2: >6.3f};' + \ ' {3: >6.3f}, {4: >6.3f}, {5: >6.3f};' + \ ' {6: >6.3f}, {7: >6.3f}, {8: >6.3f}]\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.g11[i], - self.g12[i], - self.g13[i], - self.g21[i], + repstr += fmtstr.format(self.g11[i], + self.g12[i], + self.g13[i], + self.g21[i], self.g22[i], self.g23[i], self.g31[i], @@ -1561,34 +1571,34 @@ def __repr__(self): ' {0:^6s} {0:^6s} {0:^6s} '.format('.') + \ ' {0:^6s} {0:^6s} {0:^6s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.g11[i], - self.g12[i], - self.g13[i], - self.g21[i], + repstr += fmtstr.format(self.g11[i], + self.g12[i], + self.g13[i], + self.g21[i], self.g22[i], self.g23[i], self.g31[i], self.g32[i], - self.g33[i]) + self.g33[i]) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.g11[i], - self.g12[i], - self.g13[i], - self.g21[i], + repstr += fmtstr.format(self.g11[i], + self.g12[i], + self.g13[i], + self.g21[i], self.g22[i], self.g23[i], self.g31[i], self.g32[i], self.g33[i]) - + repstr += '\n' return repstr #------------------------------------------------------------------------------ - + def __getitem__(self, index): gg11 = self.g11[index] gg12 = self.g12[index] @@ -1608,16 +1618,16 @@ def __getitem__(self, index): def __mul__(self, b): """ multiplies rotation matrices - + uses Laderman method (23 steps rather than 27) - + Reference --------- Laderman, JD. "A Noncommutative Algorithm for Multiplying 3×3 Matrices - Using 23 Multiplications." Bull Amer Math Soc 82 (1976) p126. + Using 23 Multiplications." Bull Amer Math Soc 82 (1976) p126. """ if isinstance(b, rmat): - + m1 = ( self.g11 + self.g12 + self.g13 - self.g21 - \ self.g22 - self.g32 - self.g33 ) * b.g22 m2 = ( self.g11 - self.g21 ) * ( -b.g12 + b.g22 ) @@ -1643,7 +1653,7 @@ def __mul__(self, b): m21 = self.g21 * b.g13 m22 = self.g31 * b.g12 m23 = self.g33 * b.g33 - + c1 = m6 + m14 + m19 c2 = m1 + m4 + m5 + m6 + m12 + m14 + m15 c3 = m6 + m7 + m9 + m10 + m14 + m16 + m18 @@ -1653,12 +1663,12 @@ def __mul__(self, b): c7 = m6 + m7 + m8 + m11 + m12 + m13 + m14 c8 = m12 + m13 + m14 + m15 + m22 c9 = m6 + m7 + m8 + m9 + m23 - - return rmat(g11=c1, g12=c2, g13=c3, - g21=c4, g22=c5, g23=c6, + + return rmat(g11=c1, g12=c2, g13=c3, + g21=c4, g22=c5, g23=c6, g31=c7, g32=c8, g33=c9) else: - print 'rmat multiplication error' + print('rmat multiplication error') # TODO: check into using warnings package #------------------------------------------------------------------------------ @@ -1666,7 +1676,7 @@ def __truediv__(self, b): """ matrix right division, a/b """ - if isinstance(b, rmat): + if isinstance(b, rmat): return self * b.inv() #------------------------------------------------------------------------------ @@ -1685,12 +1695,12 @@ def inv(self): c6 = badj.g23 * di c7 = badj.g31 * di c8 = badj.g32 * di - c9 = badj.g33 * di + c9 = badj.g33 * di - return rmat(g11=c1, g12=c2, g13=c3, - g21=c4, g22=c5, g23=c6, + return rmat(g11=c1, g12=c2, g13=c3, + g21=c4, g22=c5, g23=c6, g31=c7, g32=c8, g33=c9) - + #------------------------------------------------------------------------------ def det(self): @@ -1705,8 +1715,8 @@ def det(self): self.g13 * self.g22 * self.g31 return d -#------------------------------------------------------------------------------ - +#------------------------------------------------------------------------------ + def adj(self): """ returns the adjugate matrices @@ -1721,9 +1731,9 @@ def adj(self): m8 = -self.g11 * self.g32 + self.g12 * self.g31 m9 = self.g11 * self.g22 - self.g12 * self.g21 - return rmat(g11=m1, g12=m2, g13=m3, - g21=m4, g22=m5, g23=m6, - g31=m7, g32=m8, g33=m9) + return rmat(g11=m1, g12=m2, g13=m3, + g21=m4, g22=m5, g23=m6, + g31=m7, g32=m8, g33=m9) #------------------------------------------------------------------------------ @@ -1745,14 +1755,14 @@ def trace(self): return np.array(self.g11 + self.g22 + self.g33) -#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ class matthies(object): ''' Matthies Euler angles rotation representation. - + ... - + Attributes ---------- alpha : list, tuple, or numpy array of floats @@ -1773,22 +1783,22 @@ def __init__(self, alpha=0, beta=0, gamma=0): # be given for the remaining parameters if insufficient values are # passed to the function. The best way to avoid this problem is to # always type phi1=..., PHI=... etc. - + # check that the shapes of all are the same - if np.shape(alpha)==np.shape(beta)==np.shape(gamma): - + if np.shape(alpha)==np.shape(beta)==np.shape(gamma): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... self.alpha = util.vecarrayconvert(alpha) self.beta = util.vecarrayconvert(beta) self.gamma = util.vecarrayconvert(gamma) self.size = np.size(self.alpha) - + else: - print "bunge construction error: check that the lengths of phi1, "\ - "PHI, and phi2 are all the same." + print("bunge construction error: check that the lengths of phi1, "\ + "PHI, and phi2 are all the same.") # TODO: check into using warnings package return None - + #------------------------------------------------------------------------------- def numel(self): @@ -1804,41 +1814,41 @@ def to_array(self): def to_nfft(self): ''' converts Matthies Euler angles into nfft convention - + Note ---- - Algorithm from mtex 4.1.4 ''' import numpy as np - + def fft_rho(rho): # This is the contents of fft_rho.m rho = np.mod(rho + np.pi, 2.0 * np.pi) - np.pi prho = rho / 2.0 / np.pi prho[prho >= 0.5] = -0.5 return np.array(prho) - + def fft_theta(th): # This is the contents of fft_theta.m th = np.mod(th, 2.0 * np.pi) pth = th / 2.0 / np.pi return np.array(pth) - + al_fft = fft_rho(self.alpha) be_fft = fft_rho(self.beta) # TODO: MTEX has an 'nfft_bug' preference flag. It is false in my distribution. Check in to why this is there. ga_fft = fft_rho(self.gamma) - + return 2.0 * np.pi * np.vstack([al_fft, be_fft, ga_fft]).T - + #------------------------------------------------------------------------------- - + @classmethod def from_array(cls,arg): ''' create bunge object from numpy array ''' return cls(arg[:,0], arg[:,1], arg[:,2]) - + #------------------------------------------------------------------------------- @classmethod @@ -1846,11 +1856,11 @@ def from_angax(cls,arg): return matthies.from_rmat(rmat.from_angax(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_quat(cls, arg): ''' - + Example ------- >>> import numpy as np @@ -1858,31 +1868,31 @@ def from_quat(cls, arg): >>> v = np.arange(0.0, 2.0*np.pi, np.pi/7.0) >>> q = rot.quat.from_bunge(v, v, v) >>> m = rot.matthies.from_quat(q) - + Notes ----- - Algorithm from mtex 4.1.4 ''' import numpy as np - import cryspy.util as util + import righthand as rh q = arg.conj() # TODO: This is different from MTEX, since we are using a passive convention. The conjugate is required to get the same results... but is that what we want? at1 = np.arctan2(q.d, q.a) at2 = np.arctan2(q.b, q.c) alpha = at1 - at2 - beta = 2.0 * np.arctan2(np.sqrt(q.b**2 + q.c**2), + beta = 2.0 * np.arctan2(np.sqrt(q.b**2 + q.c**2), np.sqrt(q.a**2 + q.d**2)) gamma = at1 + at2 - - ind = util.isnegligible(beta, factor=10.0) # FIXME: 10x is somewhat arbitrary here. Can we do something better? - alpha[ind] = 2.0 * np.arcsin(np.maximum(-1.0, - np.minimum(1.0, - util.ssign(q.a[ind]) * q.d[ind]))) + + ind = rh.isnegligible(beta, factor=10.0) # FIXME: 10x is somewhat arbitrary here. Can we do something better? + alpha[ind] = 2.0 * np.arcsin(np.maximum(-1.0, + np.minimum(1.0, + rh.ssign(q.a[ind]) * q.d[ind]))) gamma[ind] = 0.0 - + alpha = np.mod(alpha, 2.0 * np.pi) gamma = np.mod(gamma, 2.0 * np.pi) - + return matthies(alpha, beta, gamma) #------------------------------------------------------------------------------- @@ -1895,31 +1905,31 @@ def from_rodri(cls, arg): @classmethod def from_rmat(cls, arg): - return matthies.from_quat(quat.from_rmat(arg)) + return matthies.from_quat(quat.from_rmat(arg)) #------------------------------------------------------------------------------- @classmethod def from_bunge(cls, arg): - + # algorithm from mtex 4.1.2 - import cryspy.util as util + import righthand as rh import numpy as np - - ind = ~util.isnegligible(arg.PHI) - + + ind = ~rh.isnegligible(arg.PHI) + alpha = arg.phi1 alpha[ind] = alpha[ind] - np.pi / 2.0 - + gamma = arg.phi2 gamma[ind] = gamma[ind] - 3.0 * np.pi / 2.0 - + return matthies(alpha, arg.PHI, gamma) #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np repstr = '{0:^29s}\n'.format('Matthies Euler angles (\xb0)') + \ @@ -1931,57 +1941,57 @@ def __repr__(self): n = self.size - 1 if n > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, - self.beta[i] * 180.0/np.pi, + repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, + self.beta[i] * 180.0/np.pi, self.gamma[i] * 180.0/np.pi) for i in np.arange(0, 3): repstr += ' {0:^7s} {0:^7s} {0:^7s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, - self.beta[i] * 180.0/np.pi, + repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, + self.beta[i] * 180.0/np.pi, self.gamma[i] * 180.0/np.pi) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, - self.beta[i] * 180.0/np.pi, + repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, + self.beta[i] * 180.0/np.pi, self.gamma[i] * 180.0/np.pi) - + repstr += '\n' return repstr #------------------------------------------------------------------------------- def __getitem__(self,index): - + alf = self.alpha[index] bet = self.beta[index] gam = self.gamma[index] - + return matthies(alpha=alf, beta=bet, gamma=gam) - -#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ def consolidate(list_of_rotation_objects): """ stack lists of rotation objects into numpy array - + Parameters ---------- list_of_rotation_objects : list Python list containing cryspy.rot classes of the same type - + Returns ------- - numpy array - + numpy array + Notes ----- To re-create a cryspy.rot class object with the results of consolidate, see the .from_array method for any of the classes. - + Examples -------- >>> import cryspy.rot as rot @@ -1993,18 +2003,18 @@ def consolidate(list_of_rotation_objects): >>> symm_equivs_as_quat = rot.quat.from_array(symm_equivs_to_r) """ import numpy as np - + r = list_of_rotation_objects[0].to_array() - + if np.size(list_of_rotation_objects) > 1: - + return [np.vstack([r, item.to_array()]) for item in \ list_of_rotation_objects[1:]][0] else: - + return r -#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ # TODO: see if there is another, more elegant way to prevent import loops diff --git a/tx.py b/cryspy/tx.py old mode 100644 new mode 100755 similarity index 86% rename from tx.py rename to cryspy/tx.py index 6b5978c..c9c28e7 --- a/tx.py +++ b/cryspy/tx.py @@ -1,300 +1,301 @@ -# -*- coding: utf-8 -*- -# Created on Thu Feb 02 15:54:49 2017 by paytonej - -class kernel: - """ creates a kernel for odf functions - - Notes - ----- - - This is essentially a partial translation of the kernel class from MTEX - 4.1 - - So far only the deLaValeePoussin kernel has been implemented - """ - - def __init__(self, name='deLaValeePoussin', halfwidth=10.0, psi=None, bandwidth=None, antipodal=True): - - import numpy as np - import scipy.special as spec - - if name is 'deLaValeePoussin': - kappa = 0.5 * np.log(0.5) / np.log(np.cos(halfwidth * np.pi / 360.0)) - bandwidth = np.round(kappa) - - # Constant C - cc = spec.beta(1.5, 0.5) / spec.beta(1.5, kappa + 0.5) - - # Legendre Coefficients - aa = np.ones(bandwidth + 1) - aa[1] = kappa / (kappa + 2.0) - for l in np.arange(1, bandwidth): - aa[l+1] = ((kappa - l + 1) * aa[l-1] - (2.*l + 1.) * aa[l]) / (kappa + l + 2) - - for l in np.arange(0, bandwidth+1): - aa[l] = (2. * l + 1.0) * aa[l] - - # Cut A (aa). This is a private function within the mtex kernel class. - # Here we will organize it into the initialization of the kernel. - epsilon = 0.01 # in mtex, this is set by an mtex preference - ind = np.where(np.abs(aa) <= np.amax([np.amin(np.abs(aa)), - epsilon]))[0][0] - - self.name = name - self.halfwidth = halfwidth - self.kappa = kappa - self.L = bandwidth - self.C = cc - self.A = aa[0:np.amin([ind + 1, np.shape(aa)[0]])] # Chebyshev coefficients - self.antipodal = antipodal - - def __repr__(self): - if self.antipodal == True: - ant = 'antipodal' - else: - ant = '' - print '{0}{1:s} kernel with halfwidth {1:.5f}\xb0'.format(ant, - self.name, - self.halfwidth) - -#------------------------------------------------------------------------------ - -def deg2dim(l): - """ return dimensions of the harmonic space up to order l - - Parameters - ---------- - l : float - - Returns - ------- - float - """ - import numpy as np - - dim = l * (2.0 * l - 1.0) * (2.0 * l + 1.0) / 3.0 - - return np.int(dim) - -#------------------------------------------------------------------------------ - - -# TODO: create similar functions to the below for fc2odf, odf2pf, pdf2pf, pf2odf. The code below should form a decent template to start from. -def odf2fc(quaternions, weights, kernel_legendre_coefficients): - """ computes Fourier coefficients for ODF using MTEX algorithm - - Parameters - ---------- - quaternions : cryspy.rot.quat class - crystal rotations - - weights : 1 x n numpy array - Weighting applied to each Euler angle - - kernel : cryspy.tx.kernel class - Kernel function to be used in ODF calculation - - Returns - ------- - 1 x n numpy array - Fourier coefficients - - Notes - ----- - Performs an external function call to MTEX's ODF calculation functions. - - Reference - --------- - - Examples - -------- - >>> import cryspy.io as cio - >>> import cryspy.tx as tx - >>> import numpy as np - >>> dat = cio.loadang('example_data.ang') - >>> o = dat.orientations - >>> c = c = np.ones(o.size) - >>> k = tx.kernel() - >>> fourier_coefficients = odf2fc(o.rotation, c, k) - """ - import cryspy.rot as rot - import cryspy.util as util - import numpy as np - import time - import subprocess - import io - - nfft_euler = rot.matthies.from_quat(quaternions).to_nfft().T - - # Convert our strings to byte string. - gb = nfft_euler.astype(np.float64).tostring() - cb = weights.astype(np.float64).tostring() - aab = kernel_legendre_coefficients.astype(np.float64).tostring() - - prgname = 'odf2fc' - - h = util.helper() # helper contains our system architecture-specific paths - prgpath = util.fullfile([h.prgpth, prgname + h.ext]) - - # Give our temporary file a unique name using the time since epoch - suffix = str(int(time.time() * 100.0)) - name = prgname + suffix - - # The listfile will point the external function to where the inputs have been - # temporarily written. Each time we write a variable file, we will log it. - listfilepath = util.fullfile([h.tmpdir, name + '.txt']) - listfile = io.open(listfilepath, 'w', newline='') # XXX: newline='' prevents the \lf\cr line endings in Windows. It is not clear to me at this time that this will work in Unix or Linux. - - # Write g to temporary folder and log it - datafilepath = util.fullfile([h.tmpdir, name + '_' + 'g' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(gb) - listfile.write(unicode('g: ' + datafilepath + '\n', 'utf-8')) - - # Write c to temporary folder and log it - datafilepath = util.fullfile([h.tmpdir, name + '_' + 'c' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(cb) - listfile.write(unicode('c: ' + datafilepath + '\n', 'utf-8')) - - # Write A to temporary folder and log it - datafilepath = util.fullfile([h.tmpdir, name + '_' + 'A' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(aab) - listfile.write(unicode('A: ' + datafilepath + '\n', 'utf-8')) - - # Give a path for the output results file - outputfilepath = util.fullfile([h.tmpdir, name + '_' + 'res1' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(aab) - listfile.write(unicode('res1: ' + outputfilepath + '\n', 'utf-8')) - - listfile.close() # close the file listing inputs - - # Use subprocess to perform the external system call to our function. - subprocess.call([prgpath, listfilepath]) # TODO: place in try/except structure and return an error if function call is unsuccessful - - # remove the temporary file containing the list of program inputs - del listfilepath - - # read in the output - result = np.fromfile(outputfilepath, 'd') - - return result - -#----------------------------------------------------------------------------- - -def calcFourier(component, kernel, ss): - - import numpy as np - import cryspy.rot as rot - - def gcA2Fourier(g, c, aa): - """ - Get Fourier coefficients from g, c, A - - same as the following subfunction gcA2fourier in MTEX: - [mtex dir]\ODFAnalysis\@unimodalComponent\calcFourier - """ - import cryspy.tx as tx - # this is a direct translation from mtex - # run NFSOFT - f = tx.odf2fc(g, c, aa) - return f[0::2] + 1j * f[1::2] - - def multiply_fourier_matrices(f1, f2, lA): - - import cryspy.tx as tx - - if not np.iscomplexobj(f1): - f1 = f1 + 0j - if not np.iscomplexobj(f2): - f1 = f2 + 0j - - f = np.zeros(np.size(f1)) + 0j - - for l in np.arange(0, lA): - - dmi = tx.deg2dim(l) - dmf = tx.deg2dim(l+1) - ind = np.arange(dmi, dmf) - shape = [2 * l + 1, 2 * l + 1] - f[ind] = np.matrix(np.reshape(f1[ind], shape)) * \ - np.matrix(np.reshape(f2[ind], shape)) - - return f - - # ----- - - cs = component.cs - - c = component.weights / ss.rotations.size / cs.rotations.size - - # Ralf original comment: - # "for a few center symmetrize before computing c_hat" - if 10 * component.size * ss.rotations.size * cs.rotations.size < \ - np.amax([kernel.L**3, 100]): - - # create a quat object containing all equiv rotations from xtal symm - g = rot.quat.from_array(rot.consolidate( - component.rotations.symmetrize(cs))) - - # add in the sample symmetries - g = rot.quat.from_array(rot.consolidate(g.symmetrize(ss))) - - c = np.tile(c, ss.rotations.size * cs.rotations.size) - - else: - g = component.rotations - - # Export Chebyshev coefficients - endval = np.amin([np.amax([2, kernel.L+1]), np.size(kernel.A)]) - 1 - aa = kernel.A[0:endval] - - # calculate Fourier coefficients - f_hat = gcA2Fourier(g, c, aa) - - # for many center symmetrize f_hat - # XXX: Note that this differs from MTEX 4.1.4 in that the greater than - # matches the less than for few centers above. - if 10 * component.size * ss.rotations.size * cs.rotations.size >= \ - np.amax([kernel.L**3, 100]): - - if cs.rotations.size != 1: - # symmetrize crystal symmetry - g = cs.rotations - aa[0:] = 1.0 - c = np.ones(cs.rotations.size) - f_hat = multiply_fourier_matrices(f_hat, - gcA2Fourier(g, c, aa), np.size(aa)-1) - - if ss.rotations.size != 1: - # symmetrize specimen symmetry - g = ss.rotations - aa[0:] = 1.0 - c = np.ones(ss.rotations.size) - f_hat = multiply_fourier_matrices(f_hat, - gcA2Fourier(g, c, aa), np.size(aa)-1) - - if component.antipodal == True: - f_hat = 0.5 * multiply_fourier_matrices(f_hat, - f_hat, - np.size(aa)-1) - - return f_hat - -#------------------------------------------------------------------------------ - -class odf(object): - - def __init__(self, orientations, antipodal=False): - import warnings - - self.rotations = orientations.rotations - self.size = orientations.size - self.antipodal = antipodal - self.weights = np.ones(component.size) # FIXME: This might not be correct for all cases - - if len(orientations.cs) == 1: - self.cs = orientations.cs[0] - else: - warnings.warn('Only one phase at a time ' + - 'can be analyzed for the ODF') \ No newline at end of file +# -*- coding: utf-8 -*- +# Created on Thu Feb 02 15:54:49 2017 by paytonej + +class kernel: + """ creates a kernel for odf functions + + Notes + ----- + - This is essentially a partial translation of the kernel class from MTEX + 4.1 + - So far only the deLaValeePoussin kernel has been implemented + """ + + def __init__(self, name='deLaValeePoussin', halfwidth=10.0, psi=None, bandwidth=None, antipodal=True): + + import numpy as np + import scipy.special as spec + + if name is 'deLaValeePoussin': + kappa = 0.5 * np.log(0.5) / np.log(np.cos(halfwidth * np.pi / 360.0)) + bandwidth = np.round(kappa) + + # Constant C + cc = spec.beta(1.5, 0.5) / spec.beta(1.5, kappa + 0.5) + + # Legendre Coefficients + aa = np.ones(bandwidth + 1) + aa[1] = kappa / (kappa + 2.0) + for l in np.arange(1, bandwidth): + aa[l+1] = ((kappa - l + 1) * aa[l-1] - (2.*l + 1.) * aa[l]) / (kappa + l + 2) + + for l in np.arange(0, bandwidth+1): + aa[l] = (2. * l + 1.0) * aa[l] + + # Cut A (aa). This is a private function within the mtex kernel class. + # Here we will organize it into the initialization of the kernel. + epsilon = 0.01 # in mtex, this is set by an mtex preference + ind = np.where(np.abs(aa) <= np.amax([np.amin(np.abs(aa)), + epsilon]))[0][0] + + self.name = name + self.halfwidth = halfwidth + self.kappa = kappa + self.L = bandwidth + self.C = cc + self.A = aa[0:np.amin([ind + 1, np.shape(aa)[0]])] # Chebyshev coefficients + self.antipodal = antipodal + + def __repr__(self): + if self.antipodal == True: + ant = 'antipodal' + else: + ant = '' + print('{0}{1:s} kernel with halfwidth {1:.5f}\xb0'.format(ant, + self.name, + self.halfwidth)) + +#------------------------------------------------------------------------------ + +def deg2dim(l): + """ return dimensions of the harmonic space up to order l + + Parameters + ---------- + l : float + + Returns + ------- + float + """ + import numpy as np + + dim = l * (2.0 * l - 1.0) * (2.0 * l + 1.0) / 3.0 + + return np.int(dim) + +#------------------------------------------------------------------------------ + + +# TODO: create similar functions to the below for fc2odf, odf2pf, pdf2pf, pf2odf. The code below should form a decent template to start from. +def odf2fc(quaternions, weights, kernel_legendre_coefficients): + """ computes Fourier coefficients for ODF using MTEX algorithm + + Parameters + ---------- + quaternions : cryspy.rot.quat class + crystal rotations + + weights : 1 x n numpy array + Weighting applied to each Euler angle + + kernel : cryspy.tx.kernel class + Kernel function to be used in ODF calculation + + Returns + ------- + 1 x n numpy array + Fourier coefficients + + Notes + ----- + Performs an external function call to MTEX's ODF calculation functions. + + Reference + --------- + + Examples + -------- + >>> import cryspy.io as cio + >>> import cryspy.tx as tx + >>> import numpy as np + >>> dat = cio.loadang('example_data.ang') + >>> o = dat.orientations + >>> c = c = np.ones(o.size) + >>> k = tx.kernel() + >>> fourier_coefficients = odf2fc(o.rotation, c, k) + """ + import cryspy.rot as rot + import cryspy.util as util + import righthand as rh + import numpy as np + import time + import subprocess + import io + + nfft_euler = rot.matthies.from_quat(quaternions).to_nfft().T + + # Convert our strings to byte string. + gb = nfft_euler.astype(np.float64).tostring() + cb = weights.astype(np.float64).tostring() + aab = kernel_legendre_coefficients.astype(np.float64).tostring() + + prgname = 'odf2fc' + + h = util.helper() # helper contains our system architecture-specific paths + prgpath = rh.fullfile([h.prgpth, prgname + h.ext]) + + # Give our temporary file a unique name using the time since epoch + suffix = str(int(time.time() * 100.0)) + name = prgname + suffix + + # The listfile will point the external function to where the inputs have been + # temporarily written. Each time we write a variable file, we will log it. + listfilepath = rh.fullfile([h.tmpdir, name + '.txt']) + listfile = io.open(listfilepath, 'w', newline='') # XXX: newline='' prevents the \lf\cr line endings in Windows. It is not clear to me at this time that this will work in Unix or Linux. + + # Write g to temporary folder and log it + datafilepath = rh.fullfile([h.tmpdir, name + '_' + 'g' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(gb) + listfile.write(unicode('g: ' + datafilepath + '\n', 'utf-8')) + + # Write c to temporary folder and log it + datafilepath = rh.fullfile([h.tmpdir, name + '_' + 'c' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(cb) + listfile.write(unicode('c: ' + datafilepath + '\n', 'utf-8')) + + # Write A to temporary folder and log it + datafilepath = rh.fullfile([h.tmpdir, name + '_' + 'A' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(aab) + listfile.write(unicode('A: ' + datafilepath + '\n', 'utf-8')) + + # Give a path for the output results file + outputfilepath = rh.fullfile([h.tmpdir, name + '_' + 'res1' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(aab) + listfile.write(unicode('res1: ' + outputfilepath + '\n', 'utf-8')) + + listfile.close() # close the file listing inputs + + # Use subprocess to perform the external system call to our function. + subprocess.call([prgpath, listfilepath]) # TODO: place in try/except structure and return an error if function call is unsuccessful + + # remove the temporary file containing the list of program inputs + del listfilepath + + # read in the output + result = np.fromfile(outputfilepath, 'd') + + return result + +#----------------------------------------------------------------------------- + +def calcFourier(component, kernel, ss): + + import numpy as np + import cryspy.rot as rot + + def gcA2Fourier(g, c, aa): + """ + Get Fourier coefficients from g, c, A + + same as the following subfunction gcA2fourier in MTEX: + [mtex dir]\ODFAnalysis\@unimodalComponent\calcFourier + """ + import cryspy.tx as tx + # this is a direct translation from mtex + # run NFSOFT + f = tx.odf2fc(g, c, aa) + return f[0::2] + 1j * f[1::2] + + def multiply_fourier_matrices(f1, f2, lA): + + import cryspy.tx as tx + + if not np.iscomplexobj(f1): + f1 = f1 + 0j + if not np.iscomplexobj(f2): + f1 = f2 + 0j + + f = np.zeros(np.size(f1)) + 0j + + for l in np.arange(0, lA): + + dmi = tx.deg2dim(l) + dmf = tx.deg2dim(l+1) + ind = np.arange(dmi, dmf) + shape = [2 * l + 1, 2 * l + 1] + f[ind] = np.matrix(np.reshape(f1[ind], shape)) * \ + np.matrix(np.reshape(f2[ind], shape)) + + return f + + # ----- + + cs = component.cs + + c = component.weights / ss.rotations.size / cs.rotations.size + + # Ralf original comment: + # "for a few center symmetrize before computing c_hat" + if 10 * component.size * ss.rotations.size * cs.rotations.size < \ + np.amax([kernel.L**3, 100]): + + # create a quat object containing all equiv rotations from xtal symm + g = rot.quat.from_array(rot.consolidate( + component.rotations.symmetrize(cs))) + + # add in the sample symmetries + g = rot.quat.from_array(rot.consolidate(g.symmetrize(ss))) + + c = np.tile(c, ss.rotations.size * cs.rotations.size) + + else: + g = component.rotations + + # Export Chebyshev coefficients + endval = np.amin([np.amax([2, kernel.L+1]), np.size(kernel.A)]) - 1 + aa = kernel.A[0:endval] + + # calculate Fourier coefficients + f_hat = gcA2Fourier(g, c, aa) + + # for many center symmetrize f_hat + # XXX: Note that this differs from MTEX 4.1.4 in that the greater than + # matches the less than for few centers above. + if 10 * component.size * ss.rotations.size * cs.rotations.size >= \ + np.amax([kernel.L**3, 100]): + + if cs.rotations.size != 1: + # symmetrize crystal symmetry + g = cs.rotations + aa[0:] = 1.0 + c = np.ones(cs.rotations.size) + f_hat = multiply_fourier_matrices(f_hat, + gcA2Fourier(g, c, aa), np.size(aa)-1) + + if ss.rotations.size != 1: + # symmetrize specimen symmetry + g = ss.rotations + aa[0:] = 1.0 + c = np.ones(ss.rotations.size) + f_hat = multiply_fourier_matrices(f_hat, + gcA2Fourier(g, c, aa), np.size(aa)-1) + + if component.antipodal == True: + f_hat = 0.5 * multiply_fourier_matrices(f_hat, + f_hat, + np.size(aa)-1) + + return f_hat + +#------------------------------------------------------------------------------ + +class odf(object): + + def __init__(self, orientations, antipodal=False): + import warnings + + self.rotations = orientations.rotations + self.size = orientations.size + self.antipodal = antipodal + self.weights = np.ones(component.size) # FIXME: This might not be correct for all cases + + if len(orientations.cs) == 1: + self.cs = orientations.cs[0] + else: + warnings.warn('Only one phase at a time ' + + 'can be analyzed for the ODF') \ No newline at end of file diff --git a/util.py b/cryspy/util.py old mode 100644 new mode 100755 similarity index 52% rename from util.py rename to cryspy/util.py index a898f83..0687519 --- a/util.py +++ b/cryspy/util.py @@ -1,1508 +1,919 @@ -# -*- coding: utf-8 -*- -""" -ovlib.util: Utilities -=============================================================================== -""" - -def vecarraycross(b, c): - ''' cross products of arrays of vectors ''' - import numpy as np - bx = vecarrayconvert(b[0]) - by = vecarrayconvert(b[1]) - bz = vecarrayconvert(b[2]) - cx = vecarrayconvert(c[0]) - cy = vecarrayconvert(c[1]) - cz = vecarrayconvert(c[2]) - - if np.shape(bx) == np.shape(by) == np.shape(bz) == \ - np.shape(cx) == np.shape(cy) == np.shape(cz): - - ax = by * cz - bz * cy - ay = bz * cx - bx * cz - az = bx * cy - by * cx - - return [ax, ay, az] - else: - print "vecarraycross error: check that the lengths of arguments are equal." - -#------------------------------------------------------------------------------- - -def vecarraynorm(a): - ''' norm of an array of vectors ''' - import numpy as np - ax = vecarrayconvert(a[0]) - ay = vecarrayconvert(a[1]) - az = vecarrayconvert(a[2]) - - if np.shape(ax) == np.shape(ay) == np.shape(az): - nrm=(ax*ax + ay*ay + az*az)**0.5 - else: - print "vecarraynorm error: check that the lengths of arguments are equal." - - return nrm - -#------------------------------------------------------------------------------- - -def vecarraydot(b,c): - ''' dot products of an array of vectors ''' - import numpy as np - bx = vecarrayconvert(b[0]) - by = vecarrayconvert(b[1]) - bz = vecarrayconvert(b[2]) - cx = vecarrayconvert(c[0]) - cy = vecarrayconvert(c[1]) - cz = vecarrayconvert(c[2]) - - if np.shape(bx) == np.shape(by) == np.shape(bz) == \ - np.shape(cx) == np.shape(cy) == np.shape(cz): - - return bx*cx + by*cy + bz*cz - - else: - - print "vecarraydot error: check that the lengths of arguments are equal." - -#------------------------------------------------------------------------------- - -def vecarraygcd(a,b): - ''' Compute greatest common denominator for values in two arrays''' - import numpy as np - - a = vecarrayconvert(a) - b = vecarrayconvert(b) - - if np.shape(a) == np.shape(b): - - r = np.zeros( np.size(a) ) # preallocate remainder array - - # if b is smaller than floating point error, we will make gcd=1 - a[ b < np.spacing(1) ] = 1.0 - j = b > np.spacing(1) - - if j.any(): - while j.any(): - r[j] = a[j] % b[j] - a[j] = b[j] - b[j] = r[j] - j[b==0]=False - - return a - else: - return a - -#------------------------------------------------------------------------------- - -def vecarrayconvert(a): # FIXME: Not necessary? Can just use asanyarray? - ''' convert any reasonable datatype into an n x 3 vector array''' - import numpy as np - - #a = np.asanyarray(a) - - #a = np.squeeze( np.float64( list( (a,) ))) - #a = np.atleast_2d(np.asarray(a)) - - # Make the arrays indexable if they are actually scalars - #if np.size(a) == 1: - # a = np.array([a]) - - a = np.atleast_1d(np.squeeze(np.asanyarray(a))) - - return a - -#------------------------------------------------------------------------------- - -def stereotrans(xyz, center=[0,0,1], south=[0,1,0], east=[1,0,0]): - ''' - Perform non-standard stereographic projections following - Kosel TH, J Mater Sci 19 (1984) - ''' - import numpy as np - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xyz = vecarrayconvert(xyz) - nrmxyz = 1.0/vecarraynorm(xyz) - - xx = xyz[0]*nrmxyz - yy = xyz[1]*nrmxyz - zz = xyz[2]*nrmxyz - - n = np.size(xx) - - aa = np.tile(abc[0]*nrmabc,n) - bb = np.tile(abc[1]*nrmabc,n) - cc = np.tile(abc[2]*nrmabc,n) - dd = np.tile(fed[0]*nrmdef,n) - ee = np.tile(fed[1]*nrmdef,n) - ff = np.tile(fed[2]*nrmdef,n) - uu = np.tile(uvw[0]*nrmuvw,n) - vv = np.tile(uvw[1]*nrmuvw,n) - ww = np.tile(uvw[2]*nrmuvw,n) - - cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) - cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) - cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) - - denom = 1.0/(1.0+np.absolute(cosal)) - - xproj = cosmu * denom - yproj = -cosdl * denom - - hemis = np.tile('S',n) - if np.array(n)>1.0: - hemis[cosal<0.0] = 'N' - else: - if cosal<0: - hemis = np.tile('N',n) - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def stereotransstandard(xyz): - ''' - Perform the standard stereographic transform on cartesian vectors - following De Graef & McHenry - - Returns projected x, projected y, and the hemisphere of the projection - ''' - import numpy as np - x = vecarrayconvert(xyz[0]) - y = vecarrayconvert(xyz[1]) - z = vecarrayconvert(xyz[2]) - nrm = 1.0/vecarraynorm([x,y,z]) - denom = nrm/(1.0+abs(z*nrm)) - xproj = x * denom - yproj = -y * denom - - n = np.shape(x) - hemis = np.tile('S',n) - if n[0]>1.0: - hemis[z<0.0] = 'N' - else: - if z<0.0: - hemis = 'N' - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def revstereotrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Reverse non-standard stereographic projection - ''' - import numpy as np - import numpy.linalg as npla - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - n = np.shape(xproj) - x = np.zeros(n) - y = np.zeros(n) - z = np.zeros(n) - - aa = abc[0]*nrmabc - bb = abc[1]*nrmabc - cc = abc[2]*nrmabc - dd = fed[0]*nrmdef - ee = fed[1]*nrmdef - ff = fed[2]*nrmdef - uu = uvw[0]*nrmuvw - vv = uvw[1]*nrmuvw - ww = uvw[2]*nrmuvw - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - - cosal = (1.0 - R) / (1.0 + R) - denom = abs(cosal) + 1.0 - cosmu = xproj * denom - cosdl = yproj * denom - - # For each case we determine xyz by solving a system of linear equations - for i in range(0,np.shape(cosal)[0]): - xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) - xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) - xyz = npla.solve(xyzcoef, xyzsoln) - x[i]= xyz[0] - y[i]=-xyz[1] - z[i]= xyz[2] - - # Correct z for the hemisphere - z = z * m - - return x,y,z - -#------------------------------------------------------------------------------- - -def revstereotransstandard(xyh): - ''' - Performs the reverse stereographic transform on the projected x and y - coordinates measured from the stereographic projection. - Reverse of De Graef and McHenry procedure. - - Returns the cartesian x, y, and z values of the normalized vector. - ''' - import numpy as np - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - z = (m - m * R) / (1.0 + R) - s = m * z + 1.0 - x = xproj * s - y = -yproj * s - - return x,y,z - -#------------------------------------------------------------------------------- - -def eatrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Perform non-standard equal area projections. - - Reference: - [1] Kosel TH, J Mater Sci 19 (1984) - ''' - import numpy as np - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xyz = vecarrayconvert(xyz) - nrmxyz = 1.0/vecarraynorm(xyz) - - xx = xyz[0]*nrmxyz - yy = xyz[1]*nrmxyz - zz = xyz[2]*nrmxyz - - n = np.shape(xx) - - aa = np.tile(abc[0]*nrmabc,n) - bb = np.tile(abc[1]*nrmabc,n) - cc = np.tile(abc[2]*nrmabc,n) - dd = np.tile(fed[0]*nrmdef,n) - ee = np.tile(fed[1]*nrmdef,n) - ff = np.tile(fed[2]*nrmdef,n) - uu = np.tile(uvw[0]*nrmuvw,n) - vv = np.tile(uvw[1]*nrmuvw,n) - ww = np.tile(uvw[2]*nrmuvw,n) - - cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) - cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) - cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) - - denom = 1.0/np.sqrt(1.0+np.absolute(cosal)) - - xproj = cosmu * denom - yproj = -cosdl * denom - - hemis = np.tile('S',n) - if np.array(n)>1.0: - hemis[cosal<0.0] = 'N' - else: - if cosal<0: - hemis = np.tile('N',n) - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def eatransstandard(xyz): - ''' - Perform the standard stereographic transform on cartesian vectors - following De Graef & McHenry - - Returns projected x, projected y, and the hemisphere of the projection - ''' - import numpy as np - - x=vecarrayconvert(xyz[0]) - y=vecarrayconvert(xyz[1]) - z=vecarrayconvert(xyz[2]) - nrm=1.0/vecarraynorm([x,y,z]) - denom=nrm/np.sqrt(1.0+abs(z*nrm)) - xproj = x * denom - yproj = -y * denom - - n=np.shape(x) - hemis = np.tile('S',n) - if n[0]>1.0: - hemis[z<0.0] = 'N' - else: - if z<0.0: - hemis = 'N' - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def reveatrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Reverse non-standard stereographic projection - ''' - import numpy as np - import numpy.linalg as npla - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - n = np.shape(xproj) - x = np.zeros(n) - y = np.zeros(n) - z = np.zeros(n) - - aa = abc[0]*nrmabc - bb = abc[1]*nrmabc - cc = abc[2]*nrmabc - dd = fed[0]*nrmdef - ee = fed[1]*nrmdef - ff = fed[2]*nrmdef - uu = uvw[0]*nrmuvw - vv = uvw[1]*nrmuvw - ww = uvw[2]*nrmuvw - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - - cosal = (1.0 - R) / (1.0 + R) - denom = (np.absolute(cosal) + 1.0)**2.0 - cosmu = xproj * denom - cosdl = yproj * denom - - # For each case we determine xyz by solving a system of linear equations - for i in range(0, np.shape(cosal)[0]): - xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) - xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) - xyz = npla.solve(xyzcoef, xyzsoln) - x[i]= xyz[0] - y[i]=-xyz[1] - z[i]= xyz[2] - - # Correct z for the hemisphere - z = z * m - - return x,y,z - -#------------------------------------------------------------------------------- - -def reveatransstandard(xyh): - ''' - Performs the reverse stereographic transform on the projected x and y - coordinates measured from the stereographic projection. - - Reverse of De Graef and McHenry procedure. - - Returns the cartesian x, y, and z values of the normalized vector. - ''' - import numpy as np - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - z = (m - m * R) / (1.0 + R) - s = (m * z + 1.0)**2.0 - x = xproj * s - y = -yproj * s - - return x,y,z - -#------------------------------------------------------------------------------- - -def gnomonictrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Perform the gnomonic projection, allowing different projection views - ''' - import numpy as np - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xyz = vecarrayconvert(xyz) - nrmxyz = 1.0/vecarraynorm(xyz) - - xx = xyz[0]*nrmxyz - yy = xyz[1]*nrmxyz - zz = xyz[2]*nrmxyz - - n = np.shape(xx) - - aa = np.tile(abc[0]*nrmabc,n) - bb = np.tile(abc[1]*nrmabc,n) - cc = np.tile(abc[2]*nrmabc,n) - dd = np.tile(fed[0]*nrmdef,n) - ee = np.tile(fed[1]*nrmdef,n) - ff = np.tile(fed[2]*nrmdef,n) - uu = np.tile(uvw[0]*nrmuvw,n) - vv = np.tile(uvw[1]*nrmuvw,n) - ww = np.tile(uvw[2]*nrmuvw,n) - - cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) - cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) - cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) - - denom = 1.0/np.absolute(cosal) - - xproj = cosmu * denom - yproj = -cosdl * denom - - hemis = np.tile('S',n) - if np.array(n)>1.0: - hemis[cosal<0.0] = 'N' - else: - if cosal<0: - hemis = np.tile('N',n) - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def gnomonictransstandard(xyz): - ''' - Perform the standard gnomonic projection - - Returns projected x, projected y, and the hemisphere of the projection - ''' - import numpy as np - - x=vecarrayconvert(xyz[0]) - y=vecarrayconvert(xyz[1]) - z=vecarrayconvert(xyz[2]) - denom=1.0/np.absolute(z) - xproj = x * denom - yproj = -y * denom - - n=np.shape(x) - hemis = np.tile('S',n) - if n[0]>1.0: - hemis[z<0.0] = 'N' - else: - if z<0.0: - hemis = 'N' - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def revgnomonictrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - reverse gnomonic projection allowing different projection views - ''' - import numpy as np - import numpy.linalg as npla - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - n = np.shape(xproj) - x = np.zeros(n) - y = np.zeros(n) - z = np.zeros(n) - - aa = abc[0]*nrmabc - bb = abc[1]*nrmabc - cc = abc[2]*nrmabc - dd = fed[0]*nrmdef - ee = fed[1]*nrmdef - ff = fed[2]*nrmdef - uu = uvw[0]*nrmuvw - vv = uvw[1]*nrmuvw - ww = uvw[2]*nrmuvw - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - - cosal = (1.0 - R) / (1.0 + R) - denom = np.absolute(cosal) - cosmu = xproj * denom - cosdl = yproj * denom - - # For each case we determine xyz by solving a system of linear equations - for i in range(0,np.shape(cosal)[0]): - xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) - xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) - xyz = npla.solve(xyzcoef, xyzsoln) - x[i]= xyz[0] - y[i]=-xyz[1] - z[i]= xyz[2] - - # Correct z for the hemisphere - z = z * m - - return x,y,z - -#------------------------------------------------------------------------------- - -def revgnomonictransstandard(xyh): - ''' - performs the reverse standard gnomonic projection - ''' - import numpy as np - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - z = (m - m * R) / (1.0 + R) - s = m * z - x = xproj * s - y = -yproj * s - - return x,y,z - -#------------------------------------------------------------------------------- - -def xtaldot(p1=1,p2=0,p3=0, - g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1, - q1=1,q2=0,q3=0): - ''' implements the dot product within the crystallographic reference frame: - p*G\q where p and q are vectors and G is a metric matrix ''' - - p1 = vecarrayconvert(p1) - p2 = vecarrayconvert(p2) - p3 = vecarrayconvert(p3) - q1 = vecarrayconvert(q1) - q2 = vecarrayconvert(q2) - q3 = vecarrayconvert(q3) - g11 = vecarrayconvert(g11) - g12 = vecarrayconvert(g12) - g13 = vecarrayconvert(g13) - g21 = vecarrayconvert(g21) - g22 = vecarrayconvert(g22) - g23 = vecarrayconvert(g23) - g31 = vecarrayconvert(g31) - g32 = vecarrayconvert(g32) - g33 = vecarrayconvert(g33) - - return (g11*q1+g12*q2+g13*q3)*p1+ \ - (g21*q1+g22*q2+g23*q3)*p2+ \ - (g31*q1+g32*q2+g33*q3)*p3 - -#------------------------------------------------------------------------------- - -def xtalangle(p1=1,p2=0,p3=0,q1=1,q2=0,q3=0, - g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1): - ''' compute the angle between two directions in a crystal''' - - import numpy as np - - p1 = vecarrayconvert(p1) - p2 = vecarrayconvert(p2) - p3 = vecarrayconvert(p3) - q1 = vecarrayconvert(q1) - q2 = vecarrayconvert(q2) - q3 = vecarrayconvert(q3) - g11 = vecarrayconvert(g11) - g12 = vecarrayconvert(g12) - g13 = vecarrayconvert(g13) - g21 = vecarrayconvert(g21) - g22 = vecarrayconvert(g22) - g23 = vecarrayconvert(g23) - g31 = vecarrayconvert(g31) - g32 = vecarrayconvert(g32) - g33 = vecarrayconvert(g33) - - nrm=1.0/(vecarraynorm([p1,p2,p3])*vecarraynorm([q1,q2,q3])) - - return np.arccos(nrm * - xtaldot(p1,p2,p3,g11,g12,g13,g21,g22,g23,g31,g32,g33,q1,q2,q3)) - -#------------------------------------------------------------------------------- - -def rationalize(v,maxval=9): - ''' produce rational indices from fractions ''' - import numpy as np - import copy - - v0 = vecarrayconvert(v[0]) - v1 = vecarrayconvert(v[1]) - v2 = vecarrayconvert(v[2]) - nrm=1.0/vecarraynorm([v0,v1,v2]) - v0 = v0 * nrm - v1 = v1 * nrm - v2 = v2 * nrm - - if np.shape(v0)==np.shape(v1)==np.shape(v2): - if np.size(v0)==1: - v0=np.array([v0]) - v1=np.array([v1]) - v2=np.array([v2]) - - n=np.size(v[0]) - vi=np.zeros([n,maxval]) - vj=copy.copy(vi); vk=copy.copy(vi); vq=copy.copy(vi) - for i in range(1,maxval+1): - vx = np.around(np.float64(i) * v0) - vy = np.around(np.float64(i) * v1) - vz = np.around(np.float64(i) * v2) - tmpx = np.absolute(vx); tmpy = np.absolute(vy); tmpz = np.absolute(vz) - - # calculate the greatest common divisor between tmpx, tmpy, and tmpz - # we have to do this manually because fractions.gcd doesn't work on - # arrays and numpy doesn't yet have a gcd function implemented - div = 1.0/vecarraygcd(vecarraygcd(tmpx,tmpy),tmpz) - - # multipy the irrational indices by the greatest common divisor - vi[:,i-1] = vx * div - vj[:,i-1] = vy * div - vk[:,i-1] = vz * div - nrm=1.0/vecarraynorm([vi[:,i-1],vj[:,i-1],vk[:,i-1]]) - vq[:,i-1] = np.arccos(np.amin([ - np.tile(1.0,n), - vecarraydot([ vi[:,i-1]*nrm,vj[:,i-1]*nrm,vk[:,i-1]*nrm], - [v[0],v[1],v[2]]) - ],axis=0)) - - # extract the best match rational values - loc=np.argmin(vq.T, axis=0) - vi=vi[range(0, n),loc] - vj=vj[range(0, n),loc] - vk=vk[range(0, n),loc] - vq=vq[range(0, n),loc] - - return vi, vj, vk,vq - - else: - print "rationalize error:"+ \ - "check that the lengths of arguments are equal." - -#------------------------------------------------------------------------------- - -def degsymbol(): - '''returns the degree symbol for plotting''' - return unichr(176).encode("latin-1") - -#------------------------------------------------------------------------------- - -def degrees(a): - '''converts radians to degrees''' - import numpy as np - - a=vecarrayconvert(a) - return a*180.0/np.pi - -#------------------------------------------------------------------------------- - -def radians(a): - '''converts degrees to radians''' - import numpy as np - - a=vecarrayconvert(a) - return a*np.pi/180.0 - -#------------------------------------------------------------------------------ - -def uniquerows(aa): - ''' returns the number of unique rows in an array. - - Parameters - ---------- - aa : numpy array - - Returns - ------- - cc : numpy array - Rows in aa without repetitions - ia : numpy Bool array - Indexing array such that cc = aa[ia] - ic : numpy index array - Indexing array such that aa = cc[ic] - - Notes - ----- - Mimics behavior of matlab function 'unique' with optional parameter 'rows'. - Algorithm modified from a stack overflow posting [1]_. - - References - --------- - .. [1] http://stackoverflow.com/questions/8560440/, Accessed 2012-09-10 - ''' - import numpy as np - - ind = np.lexsort(np.fliplr(aa).T) # indices for aa sorted by rows - rev = np.argsort(ind) # reverse of the sorting indices - ab = aa[ind] # ab is sorted version of aa - dd = np.diff(ab, axis=0) # get differences between the rows - ui = np.ones(np.shape(ab)[0], 'bool') # preallocate boolean array - ui[1:] = (dd != 0).any(axis=1) # if difference is zero, row is not unique - - ia = ui[rev] # positions of unique rows in aa (original, unsorted array) - cc = aa[ia] # unique rows in aa in original order - - loc = np.cumsum(np.uint64(ui))-1 # cumulative sum := locs of repeats in ab - - # - rev[ia] gives us the indices of the unique rows in aa - # - argsort(rev[ia]) gives us the indices of the corresponding rows in cc - # - argsort(rev[ia])[loc] gives us the indexing relationship for ab from cc - # - np.argsort(rev[ia])[loc][rev] give indexing reln in original order - ic = np.argsort(rev[ia])[loc][rev] - - return cc, ia, ic - -#------------------------------------------------------------------------------ - -def sortrows(a): - ''' - sorts an array by rows - - Notes - ----- - Mimics the behavior of matlab's function of the same name. - ''' - import numpy as np - order = np.lexsort(np.fliplr(a).T) - return a[order] - -#------------------------------------------------------------------------------ - -def sigdec(a, n=1): - ''' - Rounds the elements of a to n decimals. - A slight modification of Peter J. Acklam's Matlab function FIXDIG - ''' - import numpy as np - a = vecarrayconvert(a) - n = vecarrayconvert(n) - f = np.array(10.**n) - s = np.sign(a) - - return s * np.around(np.absolute(a) * f) / f - -#------------------------------------------------------------------------------- -def tic(): - ''' Timing function (like tic/toc in matlab). See also: toc''' - import time - return time.time() - -def toc(t): - ''' Timing function (like tic/toc in matlab). See also: tic''' - import time - t2 = time.time() - print str(t2 - t) + ' seconds elapsed.' - return t2 - t - -#------------------------------------------------------------------------------- -class progbar: - ''' console progress indicator - - Parameters - ---------- - finalcount : int - the last number in the range - period : float - the amount of time that is ignored for updates to reduce slowdowns - caused by frequent calls to the function - message : string - the message to be displayed above the bar in the console - - Returns - ------- - None - - Notes - ----- - This is a modification of the console progress indicator class recipe by - Larry Bates [1]_ to include ideas from Ben Mitch's matlab progbar [2]_. - - References - ---------- - .. [1] L. Bates, "Console progress indicator class." Accessed 2013-02-13. - URL: http://code.activestate.com/recipes/299207-console-text-progress-indicator-class/ - .. [2] B. Mitch, "progbar.m: general purpose progress bar for matlab." - Accessed 2013-02-13. - URL: http://www.mathworks.com/matlabcentral/fileexchange/869 - - Examples - -------- - >>> # Create the progbar object - >>> pb = progbar(finalcount=10, period=0.2, message='Progress indicator') - >>> for i in range(1,11,1): - ... pb.update(i) # pass the progress values to the update method - >>> pb.update(-1) # update a negative value to signal process is complete - # and trigger the output of the elapsed time. - - - ''' - def __init__(self, finalcount=1, period=0.2, - progresschar=None, message=''): - import time - import sys - import numpy as np - - self.finalcount = finalcount - self.blockcount = 0 - - # See if caller passed me a character to use on the - # progress bar (like "*"). If not use the block - # character that makes it look like a real progress - # bar. - if not progresschar: self.block = chr(149) - else: self.block = progresschar - - # Get pointer to sys.stdout so I can use the write/flush - # methods to display the progress bar. - self.f = sys.stdout - - # Store the current time and minimum period between updates - self.ctim = time.time() - self.itim = time.time() # initial time - self.period = period - self.finished = False - - # If the final count is zero, don't start the progress gauge - if not self.finalcount: return - if len(message) < 50: - val = max(np.floor(0.5 * (50 - len(message)))-1, 0) - else: - val = 0 - self.f.write('\n'+' '*val+message+'\n') - self.f.write('|_____________ P R O G R E S S ________________|\n') - self.f.write('|----1----2----3----4----5----6----7----8----9---|' + \ - ' FINISH!\n') - return - - def update(self, count): - import time - import numpy as np - - # Check if the elapsed time is greater than the period. If not, return. - etim = time.time() - if (etim - self.ctim < self.period and count != -1): return - - # If the elapsed time is greater than the period, update the bar - self.ctim = etim - - # Make sure I don't try to go off the end (e.g. >100%) - if count == -1: count = self.finalcount - count=min(count, self.finalcount) - - # If finalcount is zero, I don't start - if self.finalcount: - percentcomplete=int(np.around(100*count/self.finalcount)) - if percentcomplete < 1: percentcomplete = 1 - else: - percentcomplete = 100 - - blockcount = int(percentcomplete/2) - if blockcount > self.blockcount: - for i in range(self.blockcount,blockcount): - self.f.write(self.block) - self.f.flush() - - if percentcomplete == 100 or count == self.finalcount: - if self.finished == False: - self.f.write(" ELAPSED TIME: " + \ - time.strftime('%H:%M:%S', \ - time.gmtime(etim-self.itim)) + '\n\n') - self.finished = True - - self.blockcount = blockcount - return - -#------------------------------------------------------------------------------- - -def iseven(x): - ''' is it an even number? - - Returns True if x is even and False if x is odd - - Parameters - ---------- - x : int - - Returns - ------- - b : boolean - ''' - import numpy as np - - # using np.absolute in case input is array - xa = vecarrayconvert(x) - b = np.mod(xa, 1) == 0 and np.absolute(np.mod(xa, 2)) <= np.spacing(1) - return b - -#------------------------------------------------------------------------------ - -def fileconvert_eurodecimals_to_international(filepath, outputfilepath=None, \ - inplace=False): - ''' convert commas to points in numbers in text data files - - Converts a text file where the decimals are given by commas into one - where the decimals are given by decimal points. - - Parameters - ---------- - filepath : string - the path to the file - outputfilepath : string (optional) - The path where the new file should be saved. The default is the same - directory where the original file was stored. - inplace : Boolean - Flag for whether original file should be overwritten (Default=False) - - Notes - ----- - If inplace is true, it will overwrite your file! You might want to have - a backup somewhere in this case. - ''' - import time - - if outputfilepath==None: - outputfilepath = \ - time.strftime('_%Y%m%d_%H%M%S.').join(filepath.split('.')) - - if inplace==True: - outputfilepath = filepath - - with open(filepath,'r') as f: - - newf = open(outputfilepath,'r+') - - for line in f: - ll = line.split() - newll = [] - for item in ll: - if item[0].isdigit() and item[-1].isdigit() and item.find(','): - newll.append(item.replace(',','.')) - else: - newll.append(item) - newf.write('\t'.join(newll)+'\n') - newf.close() - -#------------------------------------------------------------------------------ - -def fileconvert_international_to_eurodecimals(filepath, outputfilepath=None, \ - inplace=False): - ''' convert points to commas in numbers in text data files - - Converts a text file where the decimals are given by decimal points into - one where the decimals are given by commas. - - Parameters - ---------- - filepath : string - the path to the file - outputfilepath : string (optional) - The path where the new file should be saved. The default is the same - directory where the original file was stored. - inplace : Boolean - Flag for whether original file should be overwritten (Default=False) - - Notes - ----- - If inplace is true, it will overwrite your file! You might want to have - a backup somewhere in this case. - ''' - import time - - if outputfilepath==None: - outputfilepath = \ - time.strftime('_%Y%m%d_%H%M%S.').join(filepath.split('.')) - - if inplace==True: - outputfilepath = filepath - - with open(filepath, 'r') as f: - - newf = open(outputfilepath, 'r+') - - for line in f: - ll = line.split() - newll = [] - for item in ll: - if item[0].isdigit() and item[-1].isdigit() and item.find(','): - newll.append(item.replace('.',',')) - else: - newll.append(item) - newf.write('\t'.join(newll)+'\n') - newf.close() - -#------------------------------------------------------------------------------ - -def polar(x=0, y=0, z=None): - ''' polar coordinates from cartesian coordinates - - Converts cartesian coordinates to polar coordinates using a right-handed - coordinate system that aligns with the standard definitions of polar - coordinates in 2D, i.e. theta is the azimuthal angle measured from the - positive x axis, r is the radius of the circle. This definition is extended - to the 3D case such that r corresponds to the radius of the sphere for 3D - data and the additional parameter rho is the polar angle measured from the - positive z direction. This allows the 3D spherical coordinates to be - projected in the x-y plane as polar coordinates with r_2D = r_3D*cos(rho). - - Parameters - ---------- - x : numpy array - y : numpy array - z : numpy array (optional) - - Returns - ------- - theta : numpy array - r : numpy array - rho : numpy array (variable argument out) - Rho is returned only when z values are passed as input parameters - - Notes - ----- - The conventions used here differ from those generally used by physicists - for spherical coordinates, which are specified in the ISO standard - 31-11 [1]_; however, the conventions used here are consistent with the - standard conventions used in polar coordinates (2D). - - References - ---------- - [1] "Spherical coordinate system." Wikipedia. Accessed 2013-03-27. - ''' - import numpy as np - - theta = np.arctan2(y, x) - loc = np.array(theta < 0.0) * np.array(theta > 2.0*np.pi) - theta[loc] = np.mod(theta[loc], 2.0*np.pi) - - loc = np.absolute(theta) < np.sqrt(np.spacing(1)) - theta[loc] = 0.0 - - if z==None: - r = np.sqrt(x**2.0 + y**2.0) - return theta, r - else: - r = np.sqrt(x**2.0 + y**2.0 + z**2.0) - rho = np.arccos(z / r) - loc = np.array(rho < 0.0) * np.array(rho > 2.0*np.pi) - rho[loc] = np.mod(rho[loc], 2.0*np.pi) - - loc = np.absolute(rho) < np.sqrt(np.spacing(1)) - rho[loc] = 0.0 - - return theta, r, rho - -#------------------------------------------------------------------------------ - -def cart(theta=0, r=0, rho=None): - ''' converts polar coordinates to cartesian coordinates in 2D - - Notes - ----- - uses a right-handed 3D coordinate system that aligns with the standard - definitions of polar coordinates in 2D, i.e. theta is the azimuthal angle - measured from the positive x axis and rho is the polar angle measured from - the positive z direction. - ''' - import numpy as np - - if rho==None: - x = r * np.cos(theta) - y = r * np.sin(theta) - return x, y - else: - x = r * np.sin(rho) * np.cos(theta) - y = r * np.sin(rho) * np.sin(theta) - z = r * np.cos(rho) - return x, y, z - -#------------------------------------------------------------------------------ - -def rand_vonMisesFisherM(n, kappa=0, mu=[1.0, 0.0, 0.0, 0.0]): - """ random number generation from von Mises-Fisher matrix distribution - - Return n samples of random unit directions centered around mu with - dispersion parameter kappa - - Parameters - ---------- - n : int - number of samples - - kappa : float > 0 - concentration parameter - - mu : iterable of floats - central vector; length determines dimensionality m - - Returns - ------- - x : n x m numpy array - rows correspond to random unit vectors from distribution - - Notes - ----- - This is a python translation of Sungkyu Jung's matlab code published on - his website, version dated 3 Feb 2010 [1]_. It uses the modified Ulrich's - algorithm from Wood [2]_. - - References - ---------- - .. [1] S. Jung, M. Foskey, J.S. Marron, "Principal Arc Analysis on Direct - Product Manifolds," Annnals of Applied Statistics (2011). - .. [2] A.T.A. Wood, "Simulation of the von Mises Fisher distribution," Commun. - Statist. 23 (1994). - """ - import numpy as np - import scipy.stats as stats - - # Convert mu to a 2d numpy array - mu = np.atleast_2d(np.squeeze(np.asarray(mu).ravel())) - - # the dimensionality is always given by the size of mu - m = mu.size - - b = (-2.0 * kappa + np.sqrt(4.0 * kappa**2.0 + (m - 1.0)**2.0)) / (m - 1.0) - x0 = (1.0 - b) / (1.0 + b) - c = kappa * x0 + (m - 1.0)*np.log(1.0 - x0**2.0) - - # steps 1 & 2 from [2] - nnow = n - ww = [] - while True: - ntrial = np.amax([np.around(nnow * 1.2), nnow + 10.0]) - z = stats.beta.rvs((m - 1.0) / 2.0, (m - 1.0) / 2.0, size=ntrial) - u = np.random.rand(ntrial) - w = (1.0 - (1.0 + b) * z) / (1.0 - (1.0 - b) * z) - - indicator = kappa * w + (m - 1.0) * np.log(1.0 - x0 * w) - c >= np.log(u) - if np.sum(indicator) >= nnow: - w1 = w[indicator] - ww = np.hstack([ww, w1[0:nnow]]) - break - else: - ww = np.hstack([ww, w[indicator]]) - nnow = nnow - np.sum[indicator] - - # step 3 from [2]: generate n uniformly distributed m dimensional random - # directions, using the logic: "directions of normal distribution are - # uniform on the sphere." - v = np.zeros([m - 1, n]) - nr = stats.norm.rvs(1.0, size=[m - 1, n]) - for i in range(0, n): - while True: - ni = np.dot(nr[:, i], nr[:, i]) # length of ith vector - # exclude too small values to avoid numerical discretization - if ni < np.sqrt(np.spacing(np.float64(1))): - # repeat randomization - nr[:, i] = stats.norm.rvs(1.0, size=[m - 1, 1]) - else: - v[:, i] = nr[:, i] / np.sqrt(ni) - break - - x = np.vstack([np.tile(np.sqrt(1.0 - ww**2.0), [m - 1, 1]) * v, - np.atleast_2d(ww)]) - - - # Get the rotation matrix that rotates the data to be centered at mu - d = mu.size - a = np.zeros(d) - a[-1] = 1.0 - a = np.atleast_2d(a) - ab = np.dot(a, mu.T) - alpha = np.arccos(ab) - ii = np.eye(d) - - if np.abs(ab - 1) < 1e-15: - rot = ii - elif np.abs(ab + 1) < 1e-15: - rot = -ii - else: - c = mu - a * ab - c = c / np.linalg.norm(c) - aa = np.dot(a.T, c) - np.dot(c.T, a) - rot = ii + np.sin(alpha)*aa + (np.cos(alpha) - 1.0)*(np.dot(a.T, a) + np.dot(c.T, c)) - - return np.dot(rot.T, x).T - -#------------------------------------------------------------------------------ - -def h5disp(f, quiet=False): - """ - Displays the groups, datasets, and attributes in an h5 file similar to the same command in Matlab - - Parameters - ---------- - f :: h5py file class - quiet :: True/False (False default), determines whether written to console - - Returns - ------- - out :: text output string (optional) - - Example - ------- - >>> import h5py - >>> f = h5py.File('my_file.h5') - >>> out = h5disp(f) - - """ - import h5py - - out = '\n\nHDF5 file: {0}\n----\n'.format(f.filename) - - mylist = [] - def func(name, obj): - if isinstance(obj, h5py.Dataset): - mylist.append(name) - f.visititems(func) # get dataset and group names - - oldparent = '' - tabs = '' - rootGroupUsed = 0 - for item in mylist: - newparent = f[item].parent.name - if newparent == '/': - tabs = '' - else: - tabs = ' ' * (len(f[mylist[0]].name.split('/')) - 1) - - if newparent != oldparent: - if newparent == '/': - if rootGroupUsed == 0: - out = out + '{0}Group \'{1}\'\n'.format(tabs, newparent) - rootGroupUsed = 1 - oldparent = newparent - else: - out = out + '{0}Group \'{1}\'\n'.format(tabs, newparent) - oldparent = newparent - - - out = out + "{0} Dataset \'{1}\': shape={2}\n".format(\ - tabs, f[item].name.split('/')[-1], f[item].shape) - - x = h5py.AttributeManager(f[item]).items() - if len(x) != 0: - out = out + "{0} Attributes:\n".format(tabs) - for a in x: - out = out+ "{0} \'{1}\': {2}\n".format(\ - tabs, a[0], a[1]) - if quiet==False: - print out - - return out - -#------------------------------------------------------------------------------ - -def ismember(A, B): - import numpy as np - return [ np.sum(a == B) for a in A ] - -#------------------------------------------------------------------------------ - -def ind2sub( sizes, index, num_indices ): - """ - Map a scalar index of a flat 1D array to the equivalent - d-dimensional index - - Notes - ----- - | 1 4 7 | | 1,1 1,2 1,3 | - | 2 5 8 | --> | 2,1 2,2 2,3 | - | 3 6 9 | | 3,1 3,2 3,3 | - """ - import numpy as np - - denom = num_indices - num_dims = sizes.shape[0] - multi_index = np.empty( ( num_dims ), np.int32 ) - for i in xrange( num_dims - 1, -1, -1 ): - denom /= sizes[i] - multi_index[i] = index / denom - index = index % denom - return multi_index - -#------------------------------------------------------------------------------ - -def sub2ind( sizes, multi_index ): - """ - Map a d-dimensional index to the scalar index of the equivalent flat array - - Notes - ----- - | 1,1 1,2 1,3 | | 1 4 7 | - | 2,1 2,2 2,3 | --> | 2 5 8 | - | 3,1 3,2 3,3 | | 3 6 9 | - """ - import numpy as np - - num_dims = sizes.shape[0] - index = 0 - shift = 1 - for i in np.arange( num_dims ): - index += shift * multi_index[i] - shift *= sizes[i] - return index - -#------------------------------------------------------------------------------ - -def ssign(x): - """ returns the sign for each item in an array - """ - import numpy as np - - y = np.ones(np.shape(x)) - y[x<0] = -1 - return y - -#------------------------------------------------------------------------------ - -def isnegligible(x, factor=10.0): - """ returns the indices of an array that are effectively zero - - Parameters - ---------- - x : numpy array - factor : float - factor over float spacing to consider effectively zero - - Returns - ------- - numpy bool array - """ - import numpy as np - - epsilon = np.spacing(1, dtype=x.dtype) - return np.abs(x) <= factor * epsilon - -#------------------------------------------------------------------------------ - -def fullfile(x): - """ builds full filename from parts - - Parameters - --------- - x : list of strings - path parts, folders, and filename - - Returns - ------- - string - - Notes - ----- - Provides same functionality as Matlab's FULLFILE function - - Example - ------- - >>> import os - >>> print(os.getcwd()) # print the current working directory - >>> x = os.getcwd().split(os.sep) - >>> print(x) # show cwd as a list of parts - >>> cwd_path = fullfile(x) - >>> print(cwd_path) # assemble back together into full path - """ - import os - return os.sep.join(x) - -#------------------------------------------------------------------------------ - -class helper(object): - """ Provides system-specific information to cryspy - """ - def __init__(self): - - import tempfile, os, platform, warnings, inspect - - # get platform-dependent temporary directory - tmp_path = tempfile.gettempdir() - - # Determine the path to cryspy - cryspy_path = os.sep.join(inspect.getfile(inspect.currentframe()).split(os.sep)[0:-2]) - - # Get the bits used by the platform - bits, linkage = platform.architecture() - bits = bits[0:2] - - # determine the operating system platform - plat = platform.system().lower() - - # parse the platform bits and OS - result = [] - extension = '' - if plat == 'windows': - result = 'win' + bits - extension = '.exe' - elif plat == 'darwin' and bits == '64': - result = 'maci' + bits - elif plat == 'darwin' and bits == '32': - result = 'maci' - elif (plat == 'linux' or plat == 'linux2') and bits == '32': - result = 'glnx86' - elif (plat == 'linux' or plat == 'linux2') and bits == '64': - result = 'glnxa64' - else: - warnings.warn('Required c binary not compiled for your system.') - - # Create the path to our platform-dependent binary executables - prgpth = fullfile([cryspy_path, 'c', 'bin', result]) - - # Store this info in our helper object - self.tmpdir = tmp_path - self.arch = result - self.ext = extension - self.prgpth = prgpth +# -*- coding: utf-8 -*- +""" +ovlib.util: Utilities +=============================================================================== +""" + +def vecarraycross(b, c): + ''' cross products of arrays of vectors ''' + import numpy as np + bx = vecarrayconvert(b[0]) + by = vecarrayconvert(b[1]) + bz = vecarrayconvert(b[2]) + cx = vecarrayconvert(c[0]) + cy = vecarrayconvert(c[1]) + cz = vecarrayconvert(c[2]) + + if np.shape(bx) == np.shape(by) == np.shape(bz) == \ + np.shape(cx) == np.shape(cy) == np.shape(cz): + + ax = by * cz - bz * cy + ay = bz * cx - bx * cz + az = bx * cy - by * cx + + return [ax, ay, az] + else: + print("vecarraycross error: check that the lengths of arguments are equal.") # TODO: check into using warnings package + +#------------------------------------------------------------------------------- + +def vecarraynorm(a): + ''' norm of an array of vectors ''' + import numpy as np + ax = vecarrayconvert(a[0]) + ay = vecarrayconvert(a[1]) + az = vecarrayconvert(a[2]) + + if np.shape(ax) == np.shape(ay) == np.shape(az): + nrm=(ax*ax + ay*ay + az*az)**0.5 + else: + print("vecarraynorm error: check that the lengths of arguments are equal.") # TODO: check into using warnings package + + return nrm + +#------------------------------------------------------------------------------- + +def vecarraydot(b,c): + ''' dot products of an array of vectors ''' + import numpy as np + bx = vecarrayconvert(b[0]) + by = vecarrayconvert(b[1]) + bz = vecarrayconvert(b[2]) + cx = vecarrayconvert(c[0]) + cy = vecarrayconvert(c[1]) + cz = vecarrayconvert(c[2]) + + if np.shape(bx) == np.shape(by) == np.shape(bz) == \ + np.shape(cx) == np.shape(cy) == np.shape(cz): + + return bx*cx + by*cy + bz*cz + + else: + + print("vecarraydot error: check that the lengths of arguments are equal.") # TODO: check into using warnings package + +#------------------------------------------------------------------------------- + +def vecarraygcd(a,b): + ''' Compute greatest common denominator for values in two arrays''' + import numpy as np + + a = vecarrayconvert(a) + b = vecarrayconvert(b) + + if np.shape(a) == np.shape(b): + + r = np.zeros( np.size(a) ) # preallocate remainder array + + # if b is smaller than floating point error, we will make gcd=1 + a[ b < np.spacing(1) ] = 1.0 + j = b > np.spacing(1) + + if j.any(): + while j.any(): + r[j] = a[j] % b[j] + a[j] = b[j] + b[j] = r[j] + j[b==0]=False + + return a + else: + return a + +#------------------------------------------------------------------------------- + +def vecarrayconvert(a): + ''' convert any reasonable datatype into an n x 3 vector array''' + import numpy as np + + # asanyarray makes an array unless it's already an array + # squeeze remove unnecessary dimensions + # atleast_1d makes it so that the arrays are indexable if they are scalars + a = np.atleast_1d(np.squeeze(np.asanyarray(a))) + + return a + +#------------------------------------------------------------------------------- + +def stereotrans(xyz, center=[0,0,1], south=[1,0,0], east=[0,1,0]): + ''' + Perform non-standard stereographic projections following + Kosel TH, J Mater Sci 19 (1984) + ''' + import numpy as np + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xyz = vecarrayconvert(xyz) + nrmxyz = 1.0/vecarraynorm(xyz) + + xx = xyz[0]*nrmxyz + yy = xyz[1]*nrmxyz + zz = xyz[2]*nrmxyz + + n = np.size(xx) + + aa = np.tile(abc[0]*nrmabc,n) + bb = np.tile(abc[1]*nrmabc,n) + cc = np.tile(abc[2]*nrmabc,n) + dd = np.tile(fed[0]*nrmdef,n) + ee = np.tile(fed[1]*nrmdef,n) + ff = np.tile(fed[2]*nrmdef,n) + uu = np.tile(uvw[0]*nrmuvw,n) + vv = np.tile(uvw[1]*nrmuvw,n) + ww = np.tile(uvw[2]*nrmuvw,n) + + cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) + cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) + cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) + + denom = 1.0/(1.0+np.absolute(cosal)) + + xproj = cosmu * denom + yproj = -cosdl * denom + + hemis = np.tile('S',n) + if np.array(n)>1.0: + hemis[cosal<0.0] = 'N' + else: + if cosal<0: + hemis = np.tile('N',n) + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def stereotransstandard(xyz): + ''' + Perform the standard stereographic transform on cartesian vectors + following De Graef & McHenry + + Returns projected x, projected y, and the hemisphere of the projection + ''' + import numpy as np + x = vecarrayconvert(xyz[0]) + y = vecarrayconvert(xyz[1]) + z = vecarrayconvert(xyz[2]) + nrm = 1.0/vecarraynorm([x,y,z]) + denom = nrm/(1.0+abs(z*nrm)) + xproj = x * denom + yproj = -y * denom + + n = np.shape(x) + hemis = np.tile('S',n) + if n[0]>1.0: + hemis[z<0.0] = 'N' + else: + if z<0.0: + hemis = 'N' + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def revstereotrans(xyh,center=[0,0,-1],south=[-1,0,0],east=[0,-1,0]): + ''' + Reverse non-standard stereographic projection + ''' + import numpy as np + import numpy.linalg as npla + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + n = np.shape(xproj) + x = np.zeros(n) + y = np.zeros(n) + z = np.zeros(n) + + aa = abc[0]*nrmabc + bb = abc[1]*nrmabc + cc = abc[2]*nrmabc + dd = fed[0]*nrmdef + ee = fed[1]*nrmdef + ff = fed[2]*nrmdef + uu = uvw[0]*nrmuvw + vv = uvw[1]*nrmuvw + ww = uvw[2]*nrmuvw + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + + cosal = (1.0 - R) / (1.0 + R) + denom = abs(cosal) + 1.0 + cosmu = xproj * denom + cosdl = yproj * denom + + # For each case we determine xyz by solving a system of linear equations + for i in range(0,np.shape(cosal)[0]): + xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) + xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) + xyz = npla.solve(xyzcoef, xyzsoln) + x[i]= xyz[0] + y[i]=-xyz[1] + z[i]= xyz[2] + + # Correct z for the hemisphere + z = z * m + + return x,y,z + +#------------------------------------------------------------------------------- + +def revstereotransstandard(xyh): + ''' + Performs the reverse stereographic transform on the projected x and y + coordinates measured from the stereographic projection. + Reverse of De Graef and McHenry procedure. + + Returns the cartesian x, y, and z values of the normalized vector. + ''' + import numpy as np + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + z = (m - m * R) / (1.0 + R) + s = m * z + 1.0 + x = xproj * s + y = -yproj * s + + return x,y,z + +#------------------------------------------------------------------------------- + +def eatrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Perform non-standard equal area projections. + + Reference: + [1] Kosel TH, J Mater Sci 19 (1984) + ''' + import numpy as np + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xyz = vecarrayconvert(xyz) + nrmxyz = 1.0/vecarraynorm(xyz) + + xx = xyz[0]*nrmxyz + yy = xyz[1]*nrmxyz + zz = xyz[2]*nrmxyz + + n = np.shape(xx) + + aa = np.tile(abc[0]*nrmabc,n) + bb = np.tile(abc[1]*nrmabc,n) + cc = np.tile(abc[2]*nrmabc,n) + dd = np.tile(fed[0]*nrmdef,n) + ee = np.tile(fed[1]*nrmdef,n) + ff = np.tile(fed[2]*nrmdef,n) + uu = np.tile(uvw[0]*nrmuvw,n) + vv = np.tile(uvw[1]*nrmuvw,n) + ww = np.tile(uvw[2]*nrmuvw,n) + + cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) + cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) + cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) + + denom = 1.0/np.sqrt(1.0+np.absolute(cosal)) + + xproj = cosmu * denom + yproj = -cosdl * denom + + hemis = np.tile('S',n) + if np.array(n)>1.0: + hemis[cosal<0.0] = 'N' + else: + if cosal<0: + hemis = np.tile('N',n) + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def eatransstandard(xyz): + ''' + Perform the standard stereographic transform on cartesian vectors + following De Graef & McHenry + + Returns projected x, projected y, and the hemisphere of the projection + ''' + import numpy as np + + x=vecarrayconvert(xyz[0]) + y=vecarrayconvert(xyz[1]) + z=vecarrayconvert(xyz[2]) + nrm=1.0/vecarraynorm([x,y,z]) + denom=nrm/np.sqrt(1.0+abs(z*nrm)) + xproj = x * denom + yproj = -y * denom + + n=np.shape(x) + hemis = np.tile('S',n) + if n[0]>1.0: + hemis[z<0.0] = 'N' + else: + if z<0.0: + hemis = 'N' + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def reveatrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Reverse non-standard stereographic projection + ''' + import numpy as np + import numpy.linalg as npla + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + n = np.shape(xproj) + x = np.zeros(n) + y = np.zeros(n) + z = np.zeros(n) + + aa = abc[0]*nrmabc + bb = abc[1]*nrmabc + cc = abc[2]*nrmabc + dd = fed[0]*nrmdef + ee = fed[1]*nrmdef + ff = fed[2]*nrmdef + uu = uvw[0]*nrmuvw + vv = uvw[1]*nrmuvw + ww = uvw[2]*nrmuvw + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + + cosal = (1.0 - R) / (1.0 + R) + denom = (np.absolute(cosal) + 1.0)**2.0 + cosmu = xproj * denom + cosdl = yproj * denom + + # For each case we determine xyz by solving a system of linear equations + for i in range(0, np.shape(cosal)[0]): + xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) + xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) + xyz = npla.solve(xyzcoef, xyzsoln) + x[i]= xyz[0] + y[i]=-xyz[1] + z[i]= xyz[2] + + # Correct z for the hemisphere + z = z * m + + return x,y,z + +#------------------------------------------------------------------------------- + +def reveatransstandard(xyh): + ''' + Performs the reverse stereographic transform on the projected x and y + coordinates measured from the stereographic projection. + + Reverse of De Graef and McHenry procedure. + + Returns the cartesian x, y, and z values of the normalized vector. + ''' + import numpy as np + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + z = (m - m * R) / (1.0 + R) + s = (m * z + 1.0)**2.0 + x = xproj * s + y = -yproj * s + + return x,y,z + +#------------------------------------------------------------------------------- + +def gnomonictrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Perform the gnomonic projection, allowing different projection views + ''' + import numpy as np + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xyz = vecarrayconvert(xyz) + nrmxyz = 1.0/vecarraynorm(xyz) + + xx = xyz[0]*nrmxyz + yy = xyz[1]*nrmxyz + zz = xyz[2]*nrmxyz + + n = np.shape(xx) + + aa = np.tile(abc[0]*nrmabc,n) + bb = np.tile(abc[1]*nrmabc,n) + cc = np.tile(abc[2]*nrmabc,n) + dd = np.tile(fed[0]*nrmdef,n) + ee = np.tile(fed[1]*nrmdef,n) + ff = np.tile(fed[2]*nrmdef,n) + uu = np.tile(uvw[0]*nrmuvw,n) + vv = np.tile(uvw[1]*nrmuvw,n) + ww = np.tile(uvw[2]*nrmuvw,n) + + cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) + cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) + cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) + + denom = 1.0/np.absolute(cosal) + + xproj = cosmu * denom + yproj = -cosdl * denom + + hemis = np.tile('S',n) + if np.array(n)>1.0: + hemis[cosal<0.0] = 'N' + else: + if cosal<0: + hemis = np.tile('N',n) + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def gnomonictransstandard(xyz): + ''' + Perform the standard gnomonic projection + + Returns projected x, projected y, and the hemisphere of the projection + ''' + import numpy as np + + x=vecarrayconvert(xyz[0]) + y=vecarrayconvert(xyz[1]) + z=vecarrayconvert(xyz[2]) + denom=1.0/np.absolute(z) + xproj = x * denom + yproj = -y * denom + + n=np.shape(x) + hemis = np.tile('S',n) + if n[0]>1.0: + hemis[z<0.0] = 'N' + else: + if z<0.0: + hemis = 'N' + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def revgnomonictrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + reverse gnomonic projection allowing different projection views + ''' + import numpy as np + import numpy.linalg as npla + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + n = np.shape(xproj) + x = np.zeros(n) + y = np.zeros(n) + z = np.zeros(n) + + aa = abc[0]*nrmabc + bb = abc[1]*nrmabc + cc = abc[2]*nrmabc + dd = fed[0]*nrmdef + ee = fed[1]*nrmdef + ff = fed[2]*nrmdef + uu = uvw[0]*nrmuvw + vv = uvw[1]*nrmuvw + ww = uvw[2]*nrmuvw + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + + cosal = (1.0 - R) / (1.0 + R) + denom = np.absolute(cosal) + cosmu = xproj * denom + cosdl = yproj * denom + + # For each case we determine xyz by solving a system of linear equations + for i in range(0,np.shape(cosal)[0]): + xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) + xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) + xyz = npla.solve(xyzcoef, xyzsoln) + x[i]= xyz[0] + y[i]=-xyz[1] + z[i]= xyz[2] + + # Correct z for the hemisphere + z = z * m + + return x,y,z + +#------------------------------------------------------------------------------- + +def revgnomonictransstandard(xyh): + ''' + performs the reverse standard gnomonic projection + ''' + import numpy as np + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + z = (m - m * R) / (1.0 + R) + s = m * z + x = xproj * s + y = -yproj * s + + return x,y,z + +#------------------------------------------------------------------------------- + +def xtaldot(p1=1,p2=0,p3=0, + g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1, + q1=1,q2=0,q3=0): + ''' implements the dot product within the crystallographic reference frame: + p*G\q where p and q are vectors and G is a metric matrix ''' + + p1 = vecarrayconvert(p1) + p2 = vecarrayconvert(p2) + p3 = vecarrayconvert(p3) + q1 = vecarrayconvert(q1) + q2 = vecarrayconvert(q2) + q3 = vecarrayconvert(q3) + g11 = vecarrayconvert(g11) + g12 = vecarrayconvert(g12) + g13 = vecarrayconvert(g13) + g21 = vecarrayconvert(g21) + g22 = vecarrayconvert(g22) + g23 = vecarrayconvert(g23) + g31 = vecarrayconvert(g31) + g32 = vecarrayconvert(g32) + g33 = vecarrayconvert(g33) + + return (g11*q1+g12*q2+g13*q3)*p1+ \ + (g21*q1+g22*q2+g23*q3)*p2+ \ + (g31*q1+g32*q2+g33*q3)*p3 + +#------------------------------------------------------------------------------- + +def xtalangle(p1=1,p2=0,p3=0,q1=1,q2=0,q3=0, + g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1): + ''' compute the angle between two directions in a crystal''' + + import numpy as np + + p1 = vecarrayconvert(p1) + p2 = vecarrayconvert(p2) + p3 = vecarrayconvert(p3) + q1 = vecarrayconvert(q1) + q2 = vecarrayconvert(q2) + q3 = vecarrayconvert(q3) + g11 = vecarrayconvert(g11) + g12 = vecarrayconvert(g12) + g13 = vecarrayconvert(g13) + g21 = vecarrayconvert(g21) + g22 = vecarrayconvert(g22) + g23 = vecarrayconvert(g23) + g31 = vecarrayconvert(g31) + g32 = vecarrayconvert(g32) + g33 = vecarrayconvert(g33) + + nrm=1.0/(vecarraynorm([p1,p2,p3])*vecarraynorm([q1,q2,q3])) + + return np.arccos(nrm * + xtaldot(p1,p2,p3,g11,g12,g13,g21,g22,g23,g31,g32,g33,q1,q2,q3)) + +#------------------------------------------------------------------------------- + +def rationalize(v, maxval=9): + ''' produce rational indices from fractions ''' + import numpy as np + import copy + + v0 = vecarrayconvert(v[0]) + v1 = vecarrayconvert(v[1]) + v2 = vecarrayconvert(v[2]) + nrm=1.0/vecarraynorm([v0,v1,v2]) + v0 = v0 * nrm + v1 = v1 * nrm + v2 = v2 * nrm + + if np.shape(v0)==np.shape(v1)==np.shape(v2): + if np.size(v0)==1: + v0=np.array([v0]) + v1=np.array([v1]) + v2=np.array([v2]) + + n=np.size(v[0]) + vi=np.zeros([n,maxval]) + vj=copy.copy(vi); vk=copy.copy(vi); vq=copy.copy(vi) + for i in range(1,maxval+1): + vx = np.around(np.float64(i) * v0) + vy = np.around(np.float64(i) * v1) + vz = np.around(np.float64(i) * v2) + tmpx = np.absolute(vx); tmpy = np.absolute(vy); tmpz = np.absolute(vz) + + # calculate the greatest common divisor between tmpx, tmpy, and tmpz + # we have to do this manually because fractions.gcd doesn't work on + # arrays and numpy doesn't yet have a gcd function implemented + div = 1.0/vecarraygcd(vecarraygcd(tmpx,tmpy),tmpz) + + # multipy the irrational indices by the greatest common divisor + vi[:,i-1] = vx * div + vj[:,i-1] = vy * div + vk[:,i-1] = vz * div + nrm=1.0/vecarraynorm([vi[:,i-1],vj[:,i-1],vk[:,i-1]]) + vq[:,i-1] = np.arccos(np.amin([ + np.tile(1.0,n), + vecarraydot([ vi[:,i-1]*nrm,vj[:,i-1]*nrm,vk[:,i-1]*nrm], + [v[0],v[1],v[2]]) + ],axis=0)) + + # extract the best match rational values + loc=np.argmin(vq.T, axis=0) + vi=vi[range(0, n),loc] + vj=vj[range(0, n),loc] + vk=vk[range(0, n),loc] + vq=vq[range(0, n),loc] + + return vi, vj, vk,vq + + else: + print("rationalize error:"+ \ + "check that the lengths of arguments are equal.") # TODO: check into using warnings package + +#------------------------------------------------------------------------------ + +def rand_vonMisesFisherM(n, kappa=0, mu=[1.0, 0.0, 0.0, 0.0]): + """ random number generation from von Mises-Fisher matrix distribution + + Return n samples of random unit directions centered around mu with + dispersion parameter kappa + + Parameters + ---------- + n : int + number of samples + + kappa : float > 0 + concentration parameter + + mu : iterable of floats + central vector; length determines dimensionality m + + Returns + ------- + x : n x m numpy array + rows correspond to random unit vectors from distribution + + Notes + ----- + This is a python translation of Sungkyu Jung's matlab code published on + his website, version dated 3 Feb 2010 [1]_. It uses the modified Ulrich's + algorithm from Wood [2]_. + + References + ---------- + .. [1] S. Jung, M. Foskey, J.S. Marron, "Principal Arc Analysis on Direct + Product Manifolds," Annnals of Applied Statistics (2011). + .. [2] A.T.A. Wood, "Simulation of the von Mises Fisher distribution," Commun. + Statist. 23 (1994). + """ + import numpy as np + import scipy.stats as stats + + # Convert mu to a 2d numpy array + mu = np.atleast_2d(np.squeeze(np.asarray(mu).ravel())) + + # the dimensionality is always given by the size of mu + m = mu.size + + b = (-2.0 * kappa + np.sqrt(4.0 * kappa**2.0 + (m - 1.0)**2.0)) / (m - 1.0) + x0 = (1.0 - b) / (1.0 + b) + c = kappa * x0 + (m - 1.0)*np.log(1.0 - x0**2.0) + + # steps 1 & 2 from [2] + nnow = n + ww = [] + while True: + ntrial = np.amax([np.around(nnow * 1.2), nnow + 10.0]) + z = stats.beta.rvs((m - 1.0) / 2.0, (m - 1.0) / 2.0, size=ntrial) + u = np.random.rand(ntrial) + w = (1.0 - (1.0 + b) * z) / (1.0 - (1.0 - b) * z) + + indicator = kappa * w + (m - 1.0) * np.log(1.0 - x0 * w) - c >= np.log(u) + if np.sum(indicator) >= nnow: + w1 = w[indicator] + ww = np.hstack([ww, w1[0:nnow]]) + break + else: + ww = np.hstack([ww, w[indicator]]) + nnow = nnow - np.sum[indicator] + + # step 3 from [2]: generate n uniformly distributed m dimensional random + # directions, using the logic: "directions of normal distribution are + # uniform on the sphere." + v = np.zeros([m - 1, n]) + nr = stats.norm.rvs(1.0, size=[m - 1, n]) + for i in range(0, n): + while True: + ni = np.dot(nr[:, i], nr[:, i]) # length of ith vector + # exclude too small values to avoid numerical discretization + if ni < np.sqrt(np.spacing(np.float64(1))): + # repeat randomization + nr[:, i] = stats.norm.rvs(1.0, size=[m - 1, 1]) + else: + v[:, i] = nr[:, i] / np.sqrt(ni) + break + + x = np.vstack([np.tile(np.sqrt(1.0 - ww**2.0), [m - 1, 1]) * v, + np.atleast_2d(ww)]) + + + # Get the rotation matrix that rotates the data to be centered at mu + d = mu.size + a = np.zeros(d) + a[-1] = 1.0 + a = np.atleast_2d(a) + ab = np.dot(a, mu.T) + alpha = np.arccos(ab) + ii = np.eye(d) + + if np.abs(ab - 1) < 1e-15: + rot = ii + elif np.abs(ab + 1) < 1e-15: + rot = -ii + else: + c = mu - a * ab + c = c / np.linalg.norm(c) + aa = np.dot(a.T, c) - np.dot(c.T, a) + rot = ii + np.sin(alpha)*aa + (np.cos(alpha) - 1.0)*(np.dot(a.T, a) + np.dot(c.T, c)) + + return np.dot(rot.T, x).T + +#------------------------------------------------------------------------------ + +class helper(object): + """ Provides system-specific information to cryspy + """ + def __init__(self): + + import tempfile, os, platform, warnings, inspect + + # get platform-dependent temporary directory + tmp_path = tempfile.gettempdir() + + # Determine the path to cryspy + cryspy_path = os.sep.join(inspect.getfile(inspect.currentframe()).split(os.sep)[0:-2]) + + # Get the bits used by the platform + bits, linkage = platform.architecture() + bits = bits[0:2] + + # determine the operating system platform + plat = platform.system().lower() + + # parse the platform bits and OS + result = [] + extension = '' + if plat == 'windows': + result = 'win' + bits + extension = '.exe' + elif plat == 'darwin' and bits == '64': + result = 'maci' + bits + elif plat == 'darwin' and bits == '32': + result = 'maci' + elif (plat == 'linux' or plat == 'linux2') and bits == '32': + result = 'glnx86' + elif (plat == 'linux' or plat == 'linux2') and bits == '64': + result = 'glnxa64' + else: + warnings.warn('Required c binary not compiled for your system.') + + # Create the path to our platform-dependent binary executables + prgpth = fullfile([cryspy_path, 'c', 'bin', result]) + + # Store this info in our helper object + self.tmpdir = tmp_path + self.arch = result + self.ext = extension + self.prgpth = prgpth + +#------------------------------------------------------------------------------ + +def uniformBungeGrid(delta): + ''' + Produce equal volume sampling over orientation space in Bunge Euler angles + + Parameters + ---------- + delta :: int + + Returns + ------- + b :: Bunge Euler angle object + + References + ---------- + .. [1] Kalidendi et al, Acta Mater 57 (2009) p3916. doi:10.1016/j.actamat.2009.04.055 + + ''' + import numpy as np + import cryspy.rot as rot + + phi1 = np.linspace(0.0, 2.0 * np.pi, delta) + PHI = np.cos(phi1) + phi2 = np.copy(phi1) + b = rot.bunge(phi1, PHI, phi2) + + return b + +#------------------------------------------------------------------------------ + +# TODO: def genCSL() \ No newline at end of file diff --git a/vis.py b/cryspy/vis.py old mode 100644 new mode 100755 similarity index 78% rename from vis.py rename to cryspy/vis.py index 8767b26..876f9d4 --- a/vis.py +++ b/cryspy/vis.py @@ -7,40 +7,46 @@ # TODO: hide all these imports in the classes and methods from cryspy.xtal import lattvec, miller, ipfgrid, fundzonePF, symm,\ unitcell, interpret_point_group_name -from cryspy.util import stereotrans, revstereotrans, revstereotransstandard, \ - vecarraynorm, vecarraydot, vecarraycross, radians,\ - rationalize, eatrans, reveatransstandard, reveatrans, \ - polar, cart, vecarrayconvert, tic, toc -from cryspy.ebsd import triverts +from cryspy import util +import righthand as rh +import cryspy.ebsd as ebsd import numpy as np import numpy.linalg as npla import matplotlib.pyplot as plt import matplotlib as mpl import matplotlib.collections as mplcollections -import matplotlib.delaunay as mpltriang #import visvis import inspect class stereoproj(object): ''' stereoproj() - + A stereographic projection represents the planes and the directions of a crystal. A pole figure is a stereographic projection of the planes in the figure. + + References + ---------- + .. [1] Matlab polar plot function. Revision 5.22.4.9, 2007/08/27, Copyright Mathworks Inc 1984-2007 + .. [2] TH Kosel, "Computational Techniques for Stereographic Projection" J Mater Sci 19 (1984) p4106. ''' - - def __init__(self, figure=None, subplot=None, - center=[0,0,1], south=[0,1,0], east=[1,0,0], **kwargs): - + + # FIXME: Add a buffer value so the edges of the grid don't cover up the circle around the plot + + def __init__(self, figure=None, subplot=None, + center=[0,0,1], south=[1,0,0], east=[0,1,0], **kwargs): + + import righthand as rh + if not 'figsize' in kwargs: - kwargs['figsize'] = (8, 8) - + kwargs['figsize'] = (rh.jfigw('double column'), rh.jfigw('double column')) + # Open the figure if figure: self._figure = figure else: self._figure = plt.figure(**kwargs) - + # Create first axis instance if subplot: self._ax = self._figure.add_subplot(subplot) @@ -48,60 +54,65 @@ def __init__(self, figure=None, subplot=None, else: self._ax = self._figure.add_subplot(111) self._subplot = 111 - + # Create the background - plt.axes(self._ax).set_xbound(-1.05,1.05) - plt.axes(self._ax).set_ybound(-1.05,1.05) - circ=plt.Circle((0,0),1,facecolor='w', - edgecolor='k', linewidth=1, alpha=1, clip_on=False) - self._ax.add_patch(circ) + plt.axes(self._ax).set_xbound(-1.05, 1.05) + plt.axes(self._ax).set_ybound(-1.05, 1.05) + circ1=plt.Circle((0, 0), 1, facecolor='w', + edgecolor='none', alpha=1, clip_on=False, zorder=0) + self._ax.add_patch(circ1) + circ2=plt.Circle((0, 0), 1, facecolor='none', + edgecolor='k', alpha=1, linewidth=1, clip_on=False, zorder=1000) + self._ax.add_patch(circ2) + plt.axes(self._ax).set_aspect('equal') plt.axes(self._ax).axison = False - - south = vecarrayconvert(south) - east = vecarrayconvert(east) - center = vecarrayconvert(center) - - if abs(vecarraydot(south,east))1: - hemis[cosal<0.0] = 'S' + + hemis = np.tile('N', n) + if np.array(n) > 1: + hemis[cosal < 0.0] = 'S' else: - if cosal<0.0: - hemis = np.tile('S',n) - - denom = np.sqrt(xproj*xproj+yproj*yproj) - denom[denom==0.0]=1.0 - cosrho = xproj/denom - sinrho = yproj/denom - cosrho[denom==0.0]=0.0 - sinrho[denom==0.0]=0.0 - + if cosal < 0.0: + hemis = np.tile('S', n) + + denom = np.sqrt(xproj * xproj + yproj * yproj) + denom[denom==0.0] = 1.0 + cosrho = xproj / denom + sinrho = yproj / denom + cosrho[denom==0.0] = 0.0 + sinrho[denom==0.0] = 0.0 + alpha = np.arccos(cosal) - - - i=-1 + + + i = -1 for almx in alpha: - + # 1/cos and tan have problems near pi/2 - if (abs(almx)-np.pi/2.0)180.0) or any(alpha<0.0): - print 'valid range for grid_verticalgreatcircles \ - is between 0 and 180' + print('valid range for grid_verticalgreatcircles \ + is between 0 and 180')# TODO: check into using warnings package # Put data in our valid range - alpha = alpha * np.pi/180.0 - alpha = alpha[alpha>0.0] - alpha = alpha[alpha 0.0] + alpha = alpha[alpha < np.pi] + alpha = alpha[alpha!=np.pi / 2.0] + poleclip = np.pi / 2.0 - rh.to_radians(poleclip) + np.spacing(1) + + rho = rh.to_radians(rotation) cosrho = np.cos(rho) sinrho = np.sin(rho) - - # clip near the poles using the geometry of + + # clip near the poles using the geometry of # the small circle and the chord formula - rsc = 1.0/np.tan(poleclip) - osc = 1.0/np.sin(poleclip) - + rsc = 1.0 / np.tan(poleclip) + osc = 1.0 / np.sin(poleclip) + for almx in alpha: - if almx=np.pi/2: rgc = 1.0/np.cos(almx) ogc = np.tan(almx) @@ -815,12 +863,12 @@ def grid_greatcircles(self,degreestep=45.0, v1=v1/npla.norm(v1) v2=np.array([x2-ogc,y2]) v2=v2/npla.norm(v2) - pc=np.arccos(np.dot(v1, v2)) - + pc=np.arccos(np.dot(v1, v2)) + thi=3.0*np.pi/2.0-almx+pc thf=np.pi/2.0+almx-pc ths=(thf-thi)/resolution - + th = np.arange(thi,thf+0.5*ths,ths) x = rgc*np.cos(th)+ogc y = rgc*np.sin(th) @@ -845,7 +893,7 @@ def grid_greatcircles(self,degreestep=45.0, antialiased=antialiased, zorder=zorder, **kwargs) - + plt.axes(plt.gca()).set_xbound(-1.05,1.05) plt.axes(plt.gca()).set_ybound(-1.05,1.05) @@ -877,58 +925,58 @@ def grid_smallcircles(self,degreestep=45.0, **kwargs): plt.subplot(self._subplot) - + # assign relevant defaults from mpl.rcParams - if linewidth is None : + if linewidth is None : linewidth=mpl.rcParams['lines.linewidth'] - if linestyle is None : + if linestyle is None : linestyle=mpl.rcParams['lines.linestyle'] - if marker is None : + if marker is None : marker=mpl.rcParams['lines.marker'] - if color is None : + if color is None : color=mpl.rcParams['lines.color'] - if markersize is None : + if markersize is None : markersize=mpl.rcParams['lines.markersize'] - if dash_capstyle is None : + if dash_capstyle is None : dash_capstyle=mpl.rcParams['lines.dash_capstyle'] - if dash_joinstyle is None : + if dash_joinstyle is None : dash_joinstyle=mpl.rcParams['lines.dash_joinstyle'] - if solid_capstyle is None : + if solid_capstyle is None : solid_capstyle=mpl.rcParams['lines.solid_capstyle'] - if solid_joinstyle is None : + if solid_joinstyle is None : solid_joinstyle=mpl.rcParams['lines.solid_joinstyle'] - if drawstyle is None : + if drawstyle is None : drawstyle='default' - if antialiased is None : + if antialiased is None : antialiased=mpl.rcParams['lines.antialiased'] - - # Create the alpha range + + # Create the alpha range beta = np.arange(mindegrees,maxdegrees+degreestep,degreestep) if any(beta>180.0) or any(beta<0.0): - print 'valid range for grid_verticalgreatcircles \ - is between 0 and 180' + print('valid range for grid_verticalgreatcircles \ + is between 0 and 180') # TODO: check into using warnings package # Put data in our valid range - beta = beta * np.pi/180.0 + beta = beta * np.pi/180.0 beta = beta[beta>0.0] beta = beta[beta=np.pi/2: + + if bemx>=np.pi/2.0: rsc = 1.0/np.tan(bemx) org = 1.0/np.sin(bemx) thi = bemx-np.pi/2.0 thf = 3.0*np.pi/2.0-bemx ths = (thf-thi)/resolution th = -np.arange(thi,thf+0.5*ths,ths) - + x = rsc*np.cos(th) y = -rsc*np.sin(th)+org plt.plot(x*cosrho-y*sinrho,y*cosrho+x*sinrho, @@ -984,7 +1032,7 @@ def grid_smallcircles(self,degreestep=45.0, antialiased=antialiased, zorder=zorder, **kwargs) - + plt.axes(plt.gca()).set_xbound(-1.05,1.05) plt.axes(plt.gca()).set_ybound(-1.05,1.05) @@ -1015,39 +1063,39 @@ def grid_rings(self,degreestep=45.0, **kwargs): plt.subplot(self._subplot) - + # assign relevant defaults from mpl.rcParams - if linewidth is None : + if linewidth is None : linewidth=mpl.rcParams['lines.linewidth'] - if linestyle is None : + if linestyle is None : linestyle=mpl.rcParams['lines.linestyle'] - if marker is None : + if marker is None : marker=mpl.rcParams['lines.marker'] - if color is None : + if color is None : color=mpl.rcParams['lines.color'] - if markersize is None : + if markersize is None : markersize=mpl.rcParams['lines.markersize'] - if dash_capstyle is None : + if dash_capstyle is None : dash_capstyle=mpl.rcParams['lines.dash_capstyle'] - if dash_joinstyle is None : + if dash_joinstyle is None : dash_joinstyle=mpl.rcParams['lines.dash_joinstyle'] - if solid_capstyle is None : + if solid_capstyle is None : solid_capstyle=mpl.rcParams['lines.solid_capstyle'] - if solid_joinstyle is None : + if solid_joinstyle is None : solid_joinstyle=mpl.rcParams['lines.solid_joinstyle'] - if drawstyle is None : + if drawstyle is None : drawstyle='default' - if antialiased is None : + if antialiased is None : antialiased=mpl.rcParams['lines.antialiased'] - - # Create the alpha range + + # Create the alpha range r = np.arange(mindegrees,maxdegrees+degreestep,degreestep) if any(r>90.0) or any(r<0.0): - print 'valid range for grid_rings is between 0 and 90' + print('valid range for grid_rings is between 0 and 90') # TODO: check into using warnings package # Put data in our valid range - r = r * np.pi/180.0 + r = r * np.pi/180.0 r = r[r>0.0] r = r[r1: hemis[cosal<0.0] = 'S' else: if cosal<0.0: hemis = np.tile('S',n) - + denom = np.sqrt(xproj*xproj+yproj*yproj) denom[denom==0.0]=1.0 cosrho = xproj/denom sinrho = yproj/denom cosrho[denom==0.0]=0.0 sinrho[denom==0.0]=0.0 - + alpha = np.arccos(cosal) - - + + i=-1 for almx in alpha: - + # 1/cos and tan have problems near pi/2 if (abs(almx)-np.pi/2.0)>> import cryspy + >>> from cryspy import cio >>> import matplotlib.pyplot as plt - >>> data = loadang('my_file.ang') + >>> data = cio.loadang('my_file.ang') >>> fig = plt.figure() >>> ax = fig.add_subplot(111) >>> col = mplebsdmap(data, data.pq) >>> ax.add_collection(col) >>> plt.axis('equal') >>> plt.show() - ''' - + ''' + # convert the kernfaces and kernverts data to a sequence of xy tuples for # plotting a collection of polygons. tmpa = np.zeros([np.shape(ovdat.f)[1],np.shape(ovdat.f)[0],2]) @@ -2372,19 +2419,17 @@ def mplebsdmap(ovdat, prop): if np.amax(prop)>1: prop = prop/np.amax(prop) - - if np.size(np.shape(prop)) == 1: - ncr = 3 - else: - ncr = 1 - x = 0; del x # prevents redefine bug flag on next line - clr = [tuple(x) for x in np.tile(prop, [ncr,1]).T] - - t = tic() + if np.size(np.shape(prop)) == 1: # otherwise, assume RGB triplets + prop = np.tile(prop, [3, 1]).T + + x = 0; del x # prevents redefine bug flag on next line + clr = [tuple(x) for x in prop] + + t = rh.tic() col = mplcollections.PolyCollection(verts, facecolors=clr, edgecolors=clr) - toc(t) - + rh.toc(t) + return col #------------------------------------------------------------------------------ @@ -2393,78 +2438,79 @@ def mplebsdmap(ovdat, prop): # ''' # EBSDMAP uses the visvis library to interact with opengl for fast and # interactive plotting. -# +# # For publication-quality plots, use of mplebsdmap is recommended. -# +# # BASIC USAGE: -# import ovlib as ov +# import ovlib as ov # data = ov.loadang('my_file.ang') -# ebsdmap(data,data.pq) -# +# ebsdmap(data,data.pq) +# # ''' # h = figure # hax = axes # del figure, axes -# +# # if h == None: # h = visvis.figure() # else: # visvis.figure(h) -# +# # if hax == None: # hax = visvis.gca() # else: # visvis.axes(hax) -# +# # if np.size(np.shape(c)) == 1: # c = np.tile(c,[3,1]).T -# +# # if np.amax(c) > 1: # print 'c values must range between zero and one. \ # Proceeding by normalizing values.' # c = c / np.amax(c) -# -# # To use visvis, we need to break our grid up into triangular -# # elements -# +# +# # To use visvis, we need to break our grid up into triangular +# # elements +# # if np.size(ovdat.cols) == 2: # hexagonal grid -# c = np.reshape(np.tile(c.T,[12,1]).T, [-1, 3]) +# # FIXME: c2 = np.reshape(np.tile(c, [1,2]), [-1, 3]) should probably work +# c = np.reshape(np.tile(c.T,[12,1]).T, [-1, 3]) # XXX: This must only have worked for one color value??? # else: # square grid -# c = np.reshape(np.tile(c.T,[6,1]).T, [-1, 3]) +# c = np.reshape(np.tile(c.T,[6,1]).T, [-1, 3]) # -# if hasattr(ovdat,'tv') and hasattr(ovdat,'vf'): +# if hasattr(ovdat,'tv') and hasattr(ovdat,'vf'): # tv = ovdat.tv -# vf = ovdat.vf +# vf = ovdat.vf # else: # tv, vf = triverts(ovdat.nr, ovdat.v, ovdat.f, ovdat.cols) -# +# # m = visvis.mesh(vertices=tv, faces=vf) # visvis.processing.unwindFaces(m) # m.SetValues(c) -# +# # # Flat painting # visvis.Mesh.shininess.fset(m,0) # visvis.Mesh.diffuse.fset(m,1) # visvis.Mesh.specular.fset(m,0) -# -# h.bgcolor = [1,1,1] +# +# h.bgcolor = [1,1,1] # # hax.cameraType = '2d' # da = hax.daspect # hax.daspect = da[0], -abs(da[1]), da[2] # visvis.axis('off') -# visvis.Axes.SetLimits(hax, rangeX=(np.amin(ovdat.x), np.amax(ovdat.x)), +# visvis.Axes.SetLimits(hax, rangeX=(np.amin(ovdat.x), np.amax(ovdat.x)), # rangeY=(np.amin(ovdat.y), np.amax(ovdat.y))) # -# +# # return h, hax #------------------------------------------------------------------------------ -#def ipfkey(symmetry, resolution=0.5, uc=None, +#def ipfkey(symmetry, resolution=0.5, uc=None, # trans='stereo', figure=None, axes=None): # ''' plot an ipf color key -# +# # Plots an inverse pole figure color key for interpreting ebsd orientation # maps. # @@ -2472,41 +2518,41 @@ def mplebsdmap(ovdat, prop): # ----------- # pointgroup : point group identifier # the point group. default notation is numeric. -# +# # Options # ------- # resolution : float # the approximate angular resolution required -# +# # uc : unit cell class object # a unit cell object containing the unit cell parameters -# +# # trans : str # transformation: 'ea' for equal area; 'stereo' for stereographic -# +# # notation : str # the notation of the point group input. Default is numeric. -# +# # figure : visvis figure handle -# +# # axes : visvis axis handle -# +# # Returns # ------- # h : visvis figure handle -# +# # hax : visvis axis handle # ''' # # h = figure # hax = axes # del figure, axes -# +# # if h == None: # h = visvis.figure() # else: # visvis.figure(h) -# +# # if hax == None: # hax = visvis.gca() # else: @@ -2514,56 +2560,132 @@ def mplebsdmap(ovdat, prop): # # if uc==None: # uc = unitcell() -# -# xproj, yproj, hem, edge = ipfgrid(symmetry.point_group_number, -# resolution=resolution, +# +# xproj, yproj, hem, edge = ipfgrid(symmetry.point_group_number, +# resolution=resolution, # uc=None, trans=trans) -# -# cens, edg, tri, neigh = mpltriang.delaunay(xproj, yproj) -# +# +# cens, edg, tri, neigh = mpl.tri.Triangulation(xproj, yproj) +# # zproj = np.zeros(np.shape(xproj)) # tv2 = np.array([xproj, yproj, zproj]).T # vf2 = np.array(tri, dtype=np.uint32) -# m = visvis.mesh(vertices=tv2, faces=vf2, verticesPerFace=3) +# m = visvis.mesh(vertices=tv2, faces=vf2, verticesPerFace=3) # TODO: This was changed without checking if it works, due to depreciation of matplotlib.Delaunay # #visvis.processing.unwindFaces(m) # # # This next part seems a little ridiculous... we are converting in and out of things perhaps unnecessarily -# if trans=='ea': +# if trans=='ea': # x,y,z = reveatransstandard( (xproj, yproj, hem) ) # elif trans=='stereo': # x,y,z = revstereotransstandard( (xproj, yproj, hem) ) -# +# # mil, dev = miller.from_cartesian( (x, y, z), uc, rationalizevals=False) -# c = ipfcolor(mil, symmetry, uc) +# c = ipfcolor(mil, symmetry, uc) # m.SetValues(c) -# +# # # Flat painting # visvis.Mesh.shininess.fset(m, 0) # visvis.Mesh.diffuse.fset(m, 1) # visvis.Mesh.specular.fset(m, 0) -# -# h.bgcolor = [1,1,1] -# +# +# h.bgcolor = [1,1,1] +# # hax.cameraType = '2d' # da = hax.daspect # hax.daspect = da[0], -abs(da[1]), da[2] # visvis.axis('off') -# visvis.Axes.SetLimits(hax, rangeX=(np.amin(xproj), np.amax(xproj)), +# visvis.Axes.SetLimits(hax, rangeX=(np.amin(xproj), np.amax(xproj)), # rangeY=(np.amin(yproj), np.amax(yproj))) -# +# # return h, hax + +#------------------------------------------------------------------------------ + +def mplipfkey(symmetry, resolution=0.5, uc=None, + trans='stereo', figure=None, axes=None): + ''' plot an ipf color key + + Plots an inverse pole figure color key for interpreting ebsd orientation + maps. + + Parameters + ----------- + pointgroup : point group identifier + the point group. default notation is numeric. + + Options + ------- + resolution : float + the approximate angular resolution required + + uc : unit cell class object + a unit cell object containing the unit cell parameters + + trans : str + transformation: 'ea' for equal area; 'stereo' for stereographic + + notation : str + the notation of the point group input. Default is numeric. + + figure : visvis figure handle + + axes : visvis axis handle + + Returns + ------- + h : visvis figure handle + + hax : visvis axis handle + ''' + + h = figure + hax = axes + del figure, axes + + if h == None: + h = plt.figure() + else: + plt.figure(h) + + if hax == None: + hax = h.add_subplot(1, 1, 1) + else: + plt.axes(hax) + + if uc==None: + uc = unitcell() + + xproj, yproj, hem, edge = ipfgrid(symmetry.point_group_number, + resolution=resolution, + uc=uc, trans=trans) + + triang = mpl.tri.Triangulation(x=xproj, y=yproj) + + if trans=='ea': + x,y,z = util.reveatransstandard( (xproj, yproj, hem) ) + elif trans=='stereo': + x,y,z = util.revstereotransstandard( (xproj, yproj, hem) ) + + mil, dev = miller.from_cartesian( (x, y, z), uc, rationalizevals=False) + c = ipfcolor(mil, symmetry, uc) + c2 = np.split(c, np.shape(c)[0]) + + hax.tripcolor(triang, facecolors=c2) + + return h, hax + #------------------------------------------------------------------------------ def ipfcolor(m, symmetry, uc): """ color by directions in the external reference frame - + Parameters ---------- m : miller object symmetry : symmetry object uc : unit cell object - + Returns ------- c : n x 3 numpy array of floats between 0.0 and 1.0 @@ -2573,69 +2695,69 @@ def ipfcolor(m, symmetry, uc): lcs = symmetry.laueclass() h = m.to_fundzone(lcs, uc) x, y, z = h.to_cartesian(uc) - nrm = 1.0/vecarraynorm([x, y, z]) + nrm = 1.0/util.vecarraynorm([x, y, z]) x = x * nrm y = y * nrm z = z * nrm - q, r, p = polar(x, y, z) + q, r, p = rh.polar(x, y, z) #del r - + #visvis.figure() #x, y, z = cart(q, r, p) #visvis.plot(x,y,z,ms='.', ls='') #visvis.figure() - + # Treat m-3 as a special case if lcs.point_group_number == 28: pm = p > np.pi/4.0 p[pm] = np.pi/2.0 - p[pm] - + # get limits of the fundamental zone of the stereographic projection mintheta, maxtheta, minrho, maxrho = fundzonePF(lcs.point_group_number) - + if inspect.isfunction(maxrho): pmax = maxrho(q) # for m-3 and m-3m else: pmax = maxrho # for everything else - + # TSL colors the upper and lower halves identically for triclinic if lcs.point_group_number==1: q = np.arccos(np.cos(q)) maxtheta = maxtheta * 0.5 - + # normalize rho and theta p = p / (pmax + np.spacing(1)) q = q / (maxtheta + np.spacing(1)) - + scale = [2.6,2.0,2.0]#[2.6, 2.0, 2.0] pwr = [0.8,1.0,1.0]#[0.8, 1.0, 1.0] shift = 0.4#0.4 - + # assign red tmp = np.sqrt(scale[0]) r = tmp * (tmp - np.absolute(q - shift)) * (1.0 - p) r[r > 1.0] = 1.0 - + # assign green tmp = scale[1] * p tmp[tmp>1.0] = 1.0 g = scale[1] * (1.0 - q) * tmp g[g > 1.0] = 1.0 - + # assign blue tmp = scale[2] * p tmp[tmp>1.0] = 1.0 b = scale[2] * q * tmp b[b > 1.0] = 1.0 - + # adjust colors r = r**pwr[0] g = g**pwr[1] b = b**pwr[2] - + # fix out-of-range values r[r < 0] = 0.0 g[g < 0] = 0.0 b[b < 0] = 0.0 - + return np.squeeze(np.array([r, g, b]).T) \ No newline at end of file diff --git a/cryspy/vis.zip b/cryspy/vis.zip new file mode 100755 index 0000000000000000000000000000000000000000..da83062dabf8b048687c066cd9d02695af7bfdf3 GIT binary patch literal 14957 zcmZ|0V{k6P)_@z^wr$(kv2EMdj%_D9wr$(ov7NlJ?c9CN`TpFhd#9>r^{npc`88G3 z&ofI=1{4ep2nYxYs5@j^Sx@d&{D&I|D6Rn*2;yJX-O7c*(d)|BCV7)1?bPE3CZ@}p z%1y;uDzd48^InxA`SN&2>Hx>b-9=^*P)Jod!X~~zR+84;^7EAo8bAgV1Xk|ua~Xpq zF*7zW5Pae*bQ=`IATg|?RA}5A^uQwU%VS3vb%5%vR9_n2n{?#X3*6)E0i|9+T-rr7X`A;&r!;6%YNIR zkNCYYdT0=_5Q=6@JfTEOv0Dz6%g-l^yz9~K(hjr%)NHehBqsOvkgj_h4fnDkJ=s=8 z|Ck>kjk_n%e@f@h9=3eXI9ob>R0+#xp1up!3(!-eQLLoAw(R7!7c*2k&NAcs%|Mu?v?7;BGAQhh?f;4d~FmdgCZ*rM@_E6V{ zM{Kw}dl}aKrf~0{%m?KB`(3Y5&I8;`lIV>yurG4RgBNRz?Z}9NHn6~K`LziX(?{&dRe3H;1vq|!@Hz<)K<^I%YsnZRdMFJu?+`g`72Jr`0UN2EhVYy zgUexucpTDfamWcl`=DN|e87??m(K=P7ItFl4?>J`Bo~&F}$^pozQ&7?5SD>pS_2_#wUNGBFX_Y zAwbzo%Wh5+vB%N^I%*i=sdbG1MF!CURUGGG(@j;1q>dQl5vo;+f|%b@e_7k#(w{J_ za|{aYoqEf_1kH^Hvd3wt81vu}0Qys!i}<`}2-BuWHMx?AK}h24b%V4oddDoQTPWJX z@jHlaY%o`rUFuS3?ELX9Kc*iP9>|vhDgYAyfMTW}1CPr=u?qTE_>ylrLZ&g=W!e?O zY&x)2W^R5Whg7zy4wEHYn{d{LZ~z2v2ci+0<+c#0ek%$bZlG-g)kCAu3fOG-StgER z5fs}zu-anqpHei*LT*pVjWHS(yR@SYvB)duKn!_(Q?b$?x+qWEAWH#zrjyp&n2Jai z5nOW3z`r06SI;gaanbYP^d*t+s87HZGq6Df;q=p%1J$bg+;iHNu7&D*-^5M4FW0jk zd2GkYEsZiNe=_Cj;U8Q<)7YhyR|W}@Guon~baj$fs?;>1(P=@BN0?!FTFAfwG%OcE z>)z}5<>HjgpanBL{^nq!09*DXoUPs>caEUiAWZaQo(ra^*P~hwr6usr`}-N4T^a0l zqjE2lP+A+ysP{H7cnes|FdcA*f_bVA!N^}${J1zZKr?L8!ZV1;K^fz!K<6YXz|&!} z=#HQnFmoUo&w(8CR5d0NMQBTDzo_fkV^_-UELHpMq>7e$8jEE(0;hpbGdtKGZ&)1A zukSe)G|hH;tmvBA>kH!Xne=r-^wGS%>dylfLa`Yl(ogp3f3*}EB6?O9pyKdumCkcL z7eqPv?{bmG=zMfh}1IAFS8@GCf{tAr)?BS7R<|sLAxz z!L1TvtC_ejsbUvLInz!S*4Aml$)?HP*hCGdY^AapOKU|#-v|aZ%dr;O%>o6NTMF%> zgk0I^fwf{pP)+IXXzJmvuVC-O@j`;MZZ2=AS>#qM+V9f#A%Z-2?d|C3#iL@=q`pa0 zw+380=A1_?gXe4q2`!y>?m)kU=BhzrhCuE@k=vvdg4{GtlPiHjT-&Kw;-LN9N1*?% zgEFoY@=xWh7-5Jl7V6AD`DUV=2C-Ig-Lo-S)j}K4} zwzX+#Y@^q%F@mpA4^XZODz>(g37Bh9+_gVw$rh!zwb9XYDSyzS)4s|T#m`@=Gz`~4 zE_8GjhY+#zSgT&j#=HX1^70)>wxsj&k|hQ?8rLr2g+4dZlQ>~*OyxujTVNiWMcLPT zc(u#GCm5?2O%6nQ)fd2dIL2R7tff^zrYSM9FwR2ykWuVTMG+zPc-B9J51HdO2M&QK zPM^&FosE@=8;k_gPu(a@%E?A>EQK{KD*?}zKkhDF#bC0>hXz}QmZV1=(KxME45vlQ zyuzszL9teQ@I2=Vi#xomG*%SVz^!c~DksHj)_27%MxRYcp;i2n+uPmu{u^@XoGJCxi1mF;_Vmen!|~dGCd=Wf z$B@h@!*;~He!udUE4G7;^5aPBt~{q z&L~)+TWyJY@`>#U_omgN?yhe1T4A-Y+}ZdJ9@T{&>ifS?PU8YHp9yLMyO({%XTfWi z=>rAnXM8qWd45>T4Sr>wHIPyXMOnj*oReoZNnw5OKyTTczPX?Vax03G^`E{@nK+V7 zCV}V1beXCI*O~pW`L$cXFSd;-4qq?HU(MjL&(1}RBvsNK+!%vODlR?B_JB=J7bot% zo%%XhU(rVW7K2`4YPx4m@ONe}5BVcC#P96!#h^S?FXy)ir*EFx9t0(eWS^Ue4X&6k z%QhGq#=OxShqJ6&Y%PfaV%;z$O+5I|AML##m)x7|lcxQj(nX`OADrZ!xF7c1aPpsv z>34s%?;F`^X4p{%#xWzo=w{svQ_v<@I(Fi*l}k=yrUkMMBQZ|_pXW8J_KIG_#t}A; zUxZY^s|1wlwq(4LS>0i?7%V4asts#xLDXHuxO^vM*}A4*JHLt>wINCDiWamhbV|x) zp$TYdmaQCo99iej*z9g#C}>ib@3|pg&PrBaTgh3!wq5RP6iy8i4v&AQxt;c@C!QQ_ za=)1`Zi$NgG#;~=jd{D|S3Ap{`ncrh7frv>*&tLe|8T##%lEFcy&xfcswO+QuY)+8 zrDp_Y0^uUy?r#R)`|?3xLIpaq2L`&1X}Mc4bmXGqzvXks<-y%!t@Q>OV~7MG zGo-`ZHiNFcpzmox-dd9b4Si~C2T(2pA%K8;ZUA@d)AlzZ>}|^fL1B4-J49I_`JuwwpF+cQD~Hz}=gdldN6SAzivN#0-UA zvm<+Q{Cha&_+V(6Z&8d9`tU^SK^X9NN1@NB($aZWAmmmmT)x&Aqn2F;x;50FEEExt z0woxu+?>3e(O@9#sH}y3M~J(GZZZ`uHVsbq54XTgw1bVKL0O>Gh#IVCJ%9N)}BhqN0lF{nuQvX8!#4GEFq!2tbf*9Wdc0 z=SK9z=q6-4@(v43D|aO5Rte-}bd}65>ImIfm2d7<^pk`90>s5eY6WBGupI|dRZN*wAxbouHHz8I zG)3UzKg-x=XC2Z-bdSV{OYOW=nC3;3u44E_eRcg=_%*og+GY($vv3 ztFBIUO}aMJLApN0ZX~bZDQ#YvTX!$pdhOm>;6%PbuHV0d%&$0N-debMUl0IqmtT8Cmp*rT=y1#je+q=mIlN9g})wtZlXDkBKjHlI>(l=hJTe(m1 z`8@e1?87<_q$Q;m=C?W$u(sGwpXH>?vYOq?OC2!3+YO9+Epxb?c$i*&3P5*hgSfg^ zdt}a1h`!_>Wvl652U2rq9O`T+4vXxHWs?B&;J)#f?n;kDA(z1W%0QXo^@gbIAen-F zoh#sPRxbCHpAY<0z=O_9VoiGPmBsbT^k^1p*Foi& z@9TTnmNY1ORKVartl?%aaaQFs$G6UBE$d-a%5h%W2|v+OmWK{i1bIVIkA26HKowdL zp~T?HnfL;b**7?^e>dW80h@7>v8f?vD+KRSzh*&bBv14(XjGsz>LHX+-N*_QTVt(v z#8!ryo={i*`vh?*Z^)?r8rbk-=?G{fYP)+!E3g8bQV(uTwy6M0b9h(BVw*g6FSlF$ zt}~BHtzOfvk-{L`dO5K&`|?)R6kd>fB-~z4c~LJ()3>fryL%b)THdvdm9|6%Xu%pB zly*l2(W2M?suXk_*gd@s8{hmymOLBOlui$3p+~PT+KOa#&zi%_AgUV74-VL>H$Teq z(%|%{HfVZR8#g@bj~L%fM2+lJq01m5YS6jw)M{4q88m2m4H!2(MvN$Xk748!`pCVn zI|=a4T8zCtxMi0xQZ|JOEa}t$6^{s34(Z|wYXr4byQH0A#Vt`k>MIL+NzNV8)%gZ% z93kIvYP`%{pkJW*|Dcy*V`hZ)Vn&ALJd3Mk*O)18K?=)KxW*#0UBmfw*K3n>64kjA zq4x}mVsfv~-&Cjb+sI;mY+~RfgCXljH_CkBl#oca7`l-eGN z8u29QX?FXW)jX=IAml7a?rNwC3v|w;BEpknUAs%DjG%^I<1{SjrtF|lm+$0XB{p@p znoS@V*9fa#a-&TaeD=XII}o5eyc%m2`v7v%>6~mV)}vQN7|9b7QcC zCBYsmgy5h-M1i_-+(oscj|vETiUK}EN~(=R*>LoNeThLHo52#l$kf`g5K>*B?z~P# zExo>IaZOE6cooOc6qi&E=t6A`PpyGCB=_P_dQ9{6jFip|7kx}HPrZ$S3c3 zrQ-)p`uKEL0h56OjvevR&LlP&O0-G1&^Q2lR}>-(pA*WCS_)mKWQod*hru6D8I5ICR?1+ z_`bfrx^zLg?*hGXANa<3t^)Ax7^eQ)%_H48iFn~Yn2~uN7~Ec;Ix#Niy}&*xeO6-) z`}$N8UE_MMCij%Pp=aSXpJ80t-x;N+me$)*87#AWn33-Li5h_Aby95$iO%d{IUQ&5 z3hUsAxsYS(-eFKk-sml6t#U<>C_xHdR9L&BN+V=xYr2bqM<05a{O=m~0F}IRYvl1)hxqd5R5sdiC@2^7ZlYMfv*q_4%dW z>&GbAR~G+Q-EMl<`S9alIMlLiYqL>HhfNBxp1b$FmLU&TN|9%7xA9IrC$KQx?|yMi)Mo_j<2!LtF63 z!Y&2I6!mk1rhG%{h;!yPU77$#v*zWMRm0lmim_EglR`=!#Ub<*CApv-&!<9S9tS?} zlmafC#hX`J9!HI@qD$P_|n#;11T^}lIl{8vEZ_`+!gi)gFDPH zm)zlpV#84|WWedMxJj`}Q7o%QCqzu)X=rauuMF$~hh^-5yeyJM$HW7ayNA|vYIaKX z_Z=YCI>uxaO?U&-F%#S1NYe5Jrq@>~;+7@8p7q)McPM^OrTZB@I~>{}VY-Zk?*fhW z$sYtwMx0GwbrCd!O>_rMLq{xHW-A#%!}lO^vAJ&p_)Y!Kqeg&fkHtCqQ*{+Rm^-i$ z>hKP9BQeW5qnmJzwvpW%p%6y)_6YR)&pE=k+6yyktXU>OHD2BWy=sQgFzIFU;A7R| zW@zH2etG@lyeRmloE+@}{5o z)SY&nT@Ty;HmRcViuU|2@dr|{Ez3vcC0$gSm{%8iJZLA8#n9^Ve~M;~M+82i^sgKD zqax~w)NF(7q{jC?O@)H0X)j=WOwi%{3I6{o_4RL(DA zI2Z)>2nGXIY+yj|iL8VO$Pg^k2`CV#U`#)AWGWAP9Dn}=f(;%hfep?G>Jg=|`%HpH z?!$ou4*BUu?omp$!k8d(_v-+gu~gJQ_^=Qj4BeD_f{RMk_yWL-$`taXPx=Dj#s|@R zztN5X)#%W7oR5GaIcEif7!c#lfow0PrYa@{(wm4Iqlqq?ImT`@{f4QDg{e^%moKa) zjNgoqi`N1~FRxK%fgnKII+K=DfRm$>i`CU#gP)gnW-zTJyj>S}*ylnlt^5clKlc>7 zK93I{5A@5EPp$a7CwbuoAP%&V?0NIQxT$yB#6)d%PPDh5+YC?v9s5X`&!1fbAVdK} zcODAjffHS$OM@8Xfz10E*Wm?Bt8*oOQ)}Z_~X~aTLUy3Lr6Sy}Cuh%yT&sQQ* zVcWksePvb&Jw*9PiLJuni8d$FjBofL?raMbnY}ekmkOI#eH1TlE*)wv+%3)I5p-1U zTGM0srU^`vv{A1ep7)m0QGlBMQXQ{+wRUIcR8D)>A~HU=A{=C^hH% zv2$=AJ2`EI7*h|?1}guHXil`a&~bYG&Vo5U!sYMhARsf(^t)y3Ixb1QWVQ0;2B7|yW?p$bGA+J7Z$(Nh9-sk7NTlr2d+ zU=$&o(TVKF)qpuw@)QT+bluQpkEAcw5^2F($TeVWeAH6djJzyr24D~{o}#jDN-%d0&_ zt1jIzuc}BLnssl4lpn%=TPts&`*Jd3x3uf7hH<(=1RrNMn3c4mJ3MM?nf26Ha$r`H z@*CFS3(;|Gpm{vK&iOG%BS7kBlJ~(U0UQDiZkehM>k~HGLPoOfSsh>zd|`T>kubSG z^xcm}uw($cefpu;OJxwcjT8iTO{DD`^M4KpT#2=s^4+0Y#D;+wiQr7UM-9i+Jghmt zuQr(wqT3uYApZhq1paC#UN*12o1QX0kl#9!%a93Ntl$k)$+XexXXanbu;0%dHmu~C zquQ>9_nFE+dS&ZsE#I?uG&rHw{)KAQ)N8OHgjc(U&_z;YwnCCO+M!*Mp{Tewe``t! zD(Ba@A!3Vf?j_tU8i5!L=pT-|<6dbw+J{&)E?PRTvejc>Fwpq_(qbY_Kkep`ho(?$ zr0u>hakQSX9&xlh^S|$mw~J_?N^4c=fGYt6ZbJK7#ue{;dyZOL>5pWH3 z>`9VH!8N@FLn=tsza-X7#+(Y=odD>_@O?;FCXkKJq*3NGvOmtsD6Zt32CdckxuFOQ8DB~nJ+Ge$w~=C zQy+1E6cu@=F^?L|=+QjhmIVVmjql;5t%MUE7@AJ576KQ>s856bW2piuyt7S5CG{_$jj@n{3OPX8WS9H5E-5}%|ohyGjeedaS6 zU71kfYK{FE8$69wvD&|5#6pPIY$ZxXxQ3w23su=ejB+&UapKuatcOr~^5sk`!SH9H zBNP~b3m#MywjuOa5a@MOX=#eb!fqSonF}mb)=X?Xh2cE*kGt)iT83(=%@I@_|Ym zf6~znHp5X(uh#x~95<`~dK~!0|9YILuK#+R$F~1@9Qgn9I7R>SI354zaXS9zab7F@ z_JzGD3$A>@vPQr)Q(iDY;`^HBJPm{&+!&)i>jTaoxEW;K;|CkS^is7oCssruxw1^x zaV&vfYVKbEJ8(hzS76P5H#yy{<~qtoD+j4kqI1*~CYCQa>@1y(b&wvX=4m%M*<$0; ztfOw{${%Z=<;SNu-CgD?p3*A$QR1I7BtDi*`nF6~S3k(U{pfFBY+%3B>`+Plr&@ne z`mbvDX7z#B*d05FkKkrB-*g5;_+Q}t=B2o5*by4rrQhjG>;pgrp!;*m;qZR1V8OY6 z)->4Az3Eu6eeS$<8JIT|tw_9MMKF}kYQSP0oc*rYKf#qZ$RwS0q2vVhjdAQ0H~72% z2+>TFfaM9Bp={*_l0gF;{LDaI+sDje>-VQ{Xji(TvdHcoCPxP8&<_Z2al74b>9u4(oCa_VOz2IL6!eEhT zsNt?xzdlL$_;<@c=*@NO64{}11-U{Xebe!TNZZ2+#3OOKNt;djn1{DzR^f0EUvp4%WbFARmP?Etx|c8QAClt624{q$ zDRF4gHTyY}2GMxAKbvS|t%b@t=B<#Ns5$b{&$Why%36Tj!`D20yu3bL!NpyEiMNko zEO2gnglwHLEE{yMxcj`KPmURWvQa7vRH{ru$ZZJWCuZ6r651Dl2|k9Ph1 zC9&+Xh8DPVRY0u>0+5iDxSZb;&$b#5j}f?><$6LAy*02lXShk42xBim+tUfHW2Alj%R7EQxx2Y)ZGh{3yRCks!;*nLN5zfjfAGH(d(Q>n z8$imL7T%&uld#AkxvN2BVV6*q3Ix44qZ1xpC;|}(!fh9iKN_9xdb z*I&L(wHCIk4_knRCto**8#N?K9r;bbGm+!(P@f^i;qz!t$Sr(OntB2Q7BCjyRU{@t z@(?f)P=vu@9zovo;b69qE?2r>&sS%f=rx#$Aou&M3C|!O9qu{+|7(_$EL}in<#Km) z$$a0c{Gn4itHvx&&L5d~AMvuiq;G_*RE$TDxZRSDfg83;1=fG$AEf@-&Ba?-oGHKr z7HEiwoka=9hzp0Pk!)bQaP75w8nQT>Aa~qw|D-D*V2g25Nm4lH2 z*MV@am3E41?U)Rq8}9SEXnnHzob zeZ~Q$RKD`5$OvtJAbh{m4j#%mE7lh|Atx1p7RwCuQZpDh5FUpXr2bU!?$AP3pGYZ; zGj@}E=a>z5$13FtjKSKRf7(}GK*x{CnF1IVM=(>; zWsoBpF8xx$;7<{M8gv~%28|MsT4-FOl$lb7bgm*5jrE&YY}C9g&`wPQN7po!dfp(L z_(e5YTTX>rK!^L3WagYQQHYb}&FyVUK6U;YJ9F5m^@mvFv_VL;)^kB5mIB5o1u@yI z6HieP?F-W}rQ}2;t}(kkzlbi1ZZRaqc0s(he#$UUMmm}I zd!uYh&_V<3?33GX+Yfj26Xk2|YlSzZ%;0w@<|C*ghSNX7uNO@ide)6Dr99`LJiEDB zF=8i^q?=G(Q^nPe)$-~sM=iXGnp8N>CbE3rKnuH_5Q5R~&h3=rdJ)n#6-p~oWrBlt zw*+jJ^J5Q{ji4x8@5*kMINvI5rE}a2Pnh*~s%2-wb5$&xNge4f{T-9&0{d&WvsH_= zkJRY+Wg>MMq@c;9ht-s0q5%>xQmt%^I2|?tl3xNZF8w$YNd0d#ao6OyYN3fOM32lt z=_qx#VGK7YCZD%+^{z_$4L0hhk^>pc#)#@LAWsG(h;-HTy#y95xPu7LMuTXU&)FY>SH1u*nmJU7}Y5Jn`Ldv#QL!mDa&+V#dSzPqo5ZF?8%WT zo!mMkrN~DD84TC(AJWI-Eb<*$!@j~C{d_4^>5j4sL2z> z=?4`+RKYb}zlM%pu4TRKZ773`Q3Q+u;NnIfSKzWuJj27NssdP6IoZ2?c{c-F ztCH}_Ns|RoJpvMBFr+~+Xb>O6(g?Yv(3|A;S4p-30n59@NDBXL~Xk_$_$HSIwSb?=mItqm9zQC4pz+`cuEj9jx zfV(1S02_zvvu!Levd%g01r=(Ba303$;O{JlE9n%gQPIcfV3-ieGX`atZ_7&x{HK3WZ8{JBoRBV8>nv zC9&uT#Id$M7ej@$n#$-B{>%`;ab1q3LimAPyJPtX%?7rCOq!w?oxGUx4TtYmJivsZ z(9t`qu22j3zN_;5fWHdDYP@RAF)>=yNJjJx?2+*m`*gn0A#EZEhGgwaI6ko5$GPTo zhrsLyoJH%&r|EN~Wn7H?Y1#Af+DrNf*fN1A6*>-INn_C-JgoeGA%&>_A%zkst$fRY zoPZRyt^Xqm-0{p0q>|YEAtZlT*Dt+_?)l`$gscq~!EX_iJncXeOH3S5H8zR(_+N~<9{mDTgukw>cg3&w04L;OR9DDVS?NMK4C zanbSMq09Ul6hI=@39&6+n?u_(>;bhlE17P2-8xt=XxH`=741EF>|Ay1Y^^7Qq@{S- z_$R)gcO<0811q}Fmm!xg)7iMnbRQzv2~9|q*oORtTXy< zn0+4X6T2y`B(C#vjU@XGKR#gN!d+q2ymC~jMP(gyEna(e(q|_CdUadgr!BAm@OVW8i>2 z+IQRFbhBl7n$-G_Aud}za3mH=*zJ&^s9O74s&XE4MyR$_X5-T4+#e^XAD%PdROQ`| zDrRxmci5NY0#uS2uD;R|JmxjOQaBYiRQ;!(5XioE_~zHZW@LWR@BGA~<_zR_nA^UkaLRep zDK+XBsAj<~Clb`JzLP}*q~f1uq25PMQ5_bkG>@xl(h-urCTVqlx9|a!)W?jJS~M(k zy_ny=|JMD0s3hKR{~9ons3M0+=i_-_tm-2fSJ>?xWUu4qYh->So*2)hpc9dG^z=hM z__$DH^oT@!)`v1Vf$~z|YP~@uc#b1@U^v{LN@rY$@et%aK1&SQWSf9F2$yopGC+Ax zC#Z-#16cl6b7a#0Z9`ZYS2V2Sb$8XjPOuB20B*wN6y_GZPY+bMKiX=KztqO3%LAiP z@edfdEuJ-p0o6hv8I?BZrMC*m??(O^rSn5LPKJ`uZtcw zyIyz@3E}NcdIVBGA%5EHXt1|}0cYN~S09aO?+NNLIeRywd+)lgg=mH11(t{MdPR#o4!Mk z^p-7a&`&D4CuROZY>wZ}w`&J_-}dv$O5KRWL(L`IUGV2-BCw1>>o>4yH+I}^{Yrv8 z<&XTBV}E$`JfOWl1qBL%32xlkLuMt>LuQY7Y@i??HqJ^-a8M5dW2RyttbN~L4*?R_ za`(hkt3gFF9;2av#DrYHB3j)x4gZ->-F-0nwl??>9F3hNWF_Y?(UW2HgnQYJz& zsKr`)+li*>^MClbL~T)BkWA+0^^VNQqljRbY;tW%#oLti`1i^f<$KUElY6P+!~^)X*4tM!K! z8E`&-cVluYuZFXDTcByXWuMHP`(R5rLzoY7O3;QsMo<9avBA&NN~-MYYQ znS=@mG6hh=30<+Fe2zl^eNiy?0{N$h$~MTy+)Had&<>u;7Rqc!4HsptBHW>`V;zv2 zlExn9-t(j=XmpGtCnZ-d$;i;|dh8Ovxgo_x3#rG44w5OCg+F|ok+68?RPkip12Eu= z=~&?j$Wz8jQ6IY#p124j>hh;zJPzJeV)|7-#bvPNdvs(#AngEg@k!4S??_@U1)R(8 z;Nv#=jy1;c{7{fSLt1qY(gZVaEWg35pl<-fAHVWsHdZq)gy4BJr!Y z<8(^6W_Vq^b}IbUZ=t4WQAey`ssq@fe+U?T(eMro${i8EVo>zBucT(>MaAeI3*vpR zAhjFG0-Ujtj??u5+zI4w0b=yKb34k!hler>Y|lWP11sZph+kw^rYJi9V?`&VdhR{a zT4x>e8>~NCCS^bMnYdN<{R8|d`0W5UQj#5fP@@R&D=t7{+hq$&u}H6EEXJcELzCoP zgBeY@dK$f$x3hpAiEv>jAEfkyu+uhO6mzk6+6+W9f!vZ+bo{uiH?=J zQTSwU>7J;-*GMjIkZsp=D4!b|G;0g8XJ1-?)v7wUrGai)b8zIRu{sXg6~$zAtCBeb zwL>=y3!Buj!BXBs0mT9TtA=gMK@?B$M6KcOoA7+FO(Lu{$DnPEJMhLr6_8jDp?Wo= zSkE_#*bc*UTy-?2`Cu-(ptd_xgQuE_JVlY*(&B*|PO-Sxw>ot`Rq8hh!okPk z#Rp8lq?i@n)t9CC=HJ=>NUDI4ie}$%?3`JpkQ7~$R0#W`;rht~;U484{u6ev@)dvn z9~)dy4|Q{TKO&V>&9%zmQBCqbM4MEp(2edRDA zQ`an+rKU+Lut{stsWVAc$qUSpltiHUsAdPC&L~9{ZDN%WU6oSx=;0K4(h0Pvv!-m@ zv5MT8r>Y~<%{8KM3f(mu`7cy8K55OO8e5a49=!;MY-aJu5oKhfb8IBWW=9yq`ff0- znp;G6aPY`*Egu(i5?DH+R4o`gW>z+UfC_)nYGq|(&!yRNGMoU`j<@>h%6_pZPnt{H zh-l?Iuu#@l?#Nq-ZVjmya0#T81hvbC!G!LYIwNG3_t#e zhWT|>CUa2zSsMJP1ohsN1YS4OwD71y-u|O9+Dj6^D}o>DVhiYT{6_dQN&Y8dhQoxX zJqoaPnO{4;21?h%Duu4Q)H4=`$Te7?8a(MmL%&7;$JwO*AixLTqI_&BZwhzr27EoP zw?8CTV(uDYzhMv74h_WP>A@xTwsaz`4xGJl#9+?khy3ii)i~#2|edQFiuo^fmDg)zt-cuMvi6V2ZSL21DNnH#imT{yaR;&$gu+ z(d0ZbW(bd_VT(GdzwjQ_597uzsJB6jb~ws>Kt%Ge3f760P0)SKZI# z5uyz4A|McrT@Mc2nRz1^d3aiPH=4v;Cs^@`v>xW``h+VF78Z2}`R)*%c@yL+=hj}0 zyQ=!+@)o6dw5hSqER!iet7AT1jR>hnW|c3&h9%#m&se+g`?uJ7pa+z)kwbt|JKkud z`lvC-_vA=CQ~wVq=3~qr^>=+J!I|%`1P09HyVdqXlyAfY&#*0de=M^0Nt;J*%0ouR zy&q?>d?}D{^bo579tY$*hntlF9|GRJUfe|vH1Fm!#>jto34@dbdECgi3NzVWc zUDH2(W(KAiX?f@s8>PzXmN_jvJ-~ccUS7aIeP~R303GbKwl{=LNK1&u_A6`bQ17Ds zjE4OZxDR9;_bpV0fB_Uox 32: - print 'Invalid point group number. Returning 32.' + + print('Invalid point group number. Returning 32.') # TODO: check into using warnings package laue = 32 if pgid <= 29: @@ -161,16 +167,16 @@ def laueclass(pointgroup, notation='international'): def ctf_laue_from_laue_group(laueclass): ''' channel text file laue group interpretation - + Converts between the identification scheme for Laue groups used in ovlib (which is the point group corresponding to the Laue class) to the scheme used in Channel Text Files (*.ctf). - + Parameters ---------- laueclass : int Point group number corresponding to the laue class of the phase. - + Returns ------- ctflaue : int @@ -211,33 +217,33 @@ def ctf_laue_from_laue_group(laueclass): def rotationelements(point_group_name, notation='international'): """ Function for generating rotational symmetries - + Parameters ---------- point_group_name : str The name of the point group. - + notation : string (optional) Name of convention used: {'schoenflies', 'geo', 'international', 'tsl'} Default: 'international' - + Returns ------- rotsymm : quaternion class The set of rotational symmetries. - + Notes ----- As the name suggests, this function returns only the rotational symmetry elements and NOT the full symmetry, which may include rotoinversions. Thus, this should be used carefully and only applied to orientations. Many symmetry-related operations on vectors or miller indices should use the - full set of symmetry operations. + full set of symmetry operations. """ import numpy as np import numpy.linalg as npla - import cryspy.util as util - + import righthand as rh + pge = pointgroupelements(point_group_name, notation) n = pge.numel() a = pge.to_array() @@ -247,8 +253,8 @@ def rotationelements(point_group_name, notation='international'): for i in range(0,n): determinant[i] = npla.det(a[i,:].reshape(3,3)) - determinant = util.sigdec(determinant, 1) - + determinant = rh.sigdec(determinant, 1) + if np.shape(determinant)[0] > 1: rotsymm = pge[np.squeeze(determinant == 1)] else: @@ -260,7 +266,7 @@ def rotationelements(point_group_name, notation='international'): def interpret_point_group_number(point_group_number, notation='international'): """ convert point group number to point group name - + Parses a point group number and converts it to the point group name in the desired notation. @@ -432,7 +438,7 @@ def interpret_point_group_number(point_group_number, notation='international'): def interpret_point_group_name(point_group_name, notation='international'): ''' convert point group name to number - + Parses a point group name and converts it to the point group number. Parameters @@ -619,10 +625,10 @@ def interpret_point_group_name(point_group_name, notation='international'): def pointgroupelements(point_group_name, notation='international'): ''' generate point group elements - - Function for generating point group symmetry elements containing + + Function for generating point group symmetry elements containing subfunctions for creating the point group symmetries. - + Parameters ---------- point_group_name : {str, int} @@ -630,8 +636,8 @@ def pointgroupelements(point_group_name, notation='international'): notation : str Name of convention used: {'schoenflies', 'geo', 'international', 'tsl'} - Default: 'international' - + Default: 'international' + Returns ------- pge : rmat class @@ -640,7 +646,7 @@ def pointgroupelements(point_group_name, notation='international'): Notes ----- This is a Python translation of the codes from [1]_. - + References ---------- .. [1] M. De Graef, "Introduction to Conventional Transmission Electron @@ -649,7 +655,7 @@ def pointgroupelements(point_group_name, notation='international'): def make_generator_string(point_group_id_number): """ - + Notes: - Need to check that point groups 39 and 90 are correct. - The matrices produced may be incorrect for space groups 115-120, @@ -703,7 +709,7 @@ def make_generator_string(point_group_id_number): def interpret_generator_string(generator_string): import numpy as np - + n = len(generator_string) generator_matrices = np.zeros([n,9]) for i in range(0,n): @@ -764,16 +770,16 @@ def generate_point_group_elements(generator_matrices): I have some concerns that the exact matrices produced in the present code may, however, not all be correct. See "notes" in make_generator_string. - + Parameters ---------- generator_matrices : numpy array - + Returns ------- - + ''' - import cryspy.util as util + import righthand as rh import numpy as np n1 = 0 @@ -788,7 +794,7 @@ def generate_point_group_elements(generator_matrices): generator_matrices = np.concatenate((generator_matrices, tmp.reshape(1,9)), axis=0) - generator_matrices, loc, tmp = util.uniquerows(generator_matrices) + generator_matrices, loc, tmp = rh.uniquerows(generator_matrices) n1 = n2 n2 = generator_matrices.shape[0]-1 @@ -857,8 +863,8 @@ def __init__(self, x=0, y=0, z=0): self.z = util.vecarrayconvert(z) else: - print "lattvec construction error: check that the lengths of u,"\ - " v, and w are all the same." + print("lattvec construction error: check that the lengths of u,"\ + " v, and w are all the same.") # TODO: check into using warnings package return None #------------------------------------------------------------------------------- @@ -872,7 +878,7 @@ def rotate(self, q2): ''' import numpy as np import cryspy.rot as rot - + if isinstance(q2, rot.quat): t2 = q2.a * q2.b t3 = q2.a * q2.c @@ -903,7 +909,7 @@ def to_cartesian(self, unit_cell): multiply by direct structure matrix to get cartesian vector ''' if isinstance(unit_cell, unitcell): - + d = unit_cell.d vx = d[0, 0] * self.x + d[0, 1] * self.y + d[0, 2] * self.z vy = d[1, 0] * self.x + d[1, 1] * self.y + d[1, 2] * self.z @@ -919,7 +925,7 @@ def from_cartesian(cls, arg, unit_cell, maxval=9): multiply by inverse transpose direct structure matrix ''' import cryspy.util as util - + if isinstance(unit_cell, unitcell): x = arg[0] y = arg[1] @@ -962,7 +968,7 @@ def angle(self, site2, unit_cell, origin): ''' import cryspy.util as util import numpy as np - + if isinstance(site2, lattsite) and isinstance(origin, lattsite) and \ isinstance(unit_cell, unitcell): @@ -1003,7 +1009,7 @@ def __repr__(self): import numpy as np qlist=np.array([self.x,self.y,self.z]).T - print '\n Lattice site coordinates [x y z]' + print('\n Lattice site coordinates [x y z]') # TODO: check into using warnings package np.set_printoptions(precision=3,suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1058,8 +1064,8 @@ def __init__(self, u=0, v=0, w=0): #self.w_ = self.w * nrm else: - print "lattvec construction error: check that the lengths of u,"\ - " v, and w are all the same." + print("lattvec construction error: check that the lengths of u,"\ + " v, and w are all the same.") # TODO: check into using warnings package return None #------------------------------------------------------------------------------- @@ -1094,23 +1100,39 @@ def dot(self, q2): #------------------------------------------------------------------------------- - def rotate(self, q2): + def rotate(self, r): ''' rotate lattice vector using quaternion rotation object + + Parameters + ---------- + q : quat object + object containing quaternions or rotation matrices + + Returns + ------- + v : lattvec object + rotated lattice vector + + Examples + -------- + >>> v = lattvec(1,0,4) + >>> s = pointgroupelements('m-3m') + >>> ms = v.rotate(s) ''' - from cryspy.rot import quat + from cryspy.rot import quat, rmat import numpy as np - if isinstance(q2, quat): - t2 = q2.a * q2.b - t3 = q2.a * q2.c - t4 = q2.a * q2.d - t5 = -q2.b * q2.b - t6 = q2.b * q2.c - t7 = q2.b * q2.d - t8 = -q2.c * q2.c - t9 = q2.c * q2.d - t10 = -q2.d * q2.d + if isinstance(r, quat): + t2 = r.a * r.b + t3 = r.a * r.c + t4 = r.a * r.d + t5 = -r.b * r.b + t6 = r.b * r.c + t7 = r.b * r.d + t8 = -r.c * r.c + t9 = r.c * r.d + t10 = -r.d * r.d v1new = np.array([2.0 * ( (t8 + t10) * self.u + (t6 - t4) * self.v + @@ -1123,6 +1145,14 @@ def rotate(self, q2): (t5 + t8) * self.w ) + self.w]) return lattvec(u=v1new, v=v2new, w=v3new) + + if isinstance(r, rmat): + + v1new = self.u * r.g11 + self.v * r.g21 + self.w * r.g31 + v2new = self.u * r.g12 + self.v * r.g22 + self.w * r.g32 + v3new = self.u * r.g13 + self.v * r.g23 + self.w * r.g33 + + return lattvec(u=v1new, v=v2new, w=v3new) #------------------------------------------------------------------------------- @@ -1132,13 +1162,13 @@ def to_cartesian(self, unit_cell): ''' import cryspy.util as util if isinstance(unit_cell, unitcell): - + d = unit_cell.d vx = d[0, 0] * self.u + d[0, 1] * self.v + d[0, 2] * self.w vy = d[1, 0] * self.u + d[1, 1] * self.v + d[1, 2] * self.w vz = d[2, 0] * self.u + d[2, 1] * self.v + d[2, 2] * self.w nrm = 1.0 / util.vecarraynorm([vx, vy, vz]) - + return [vx, vy, vz] * nrm #------------------------------------------------------------------------------- @@ -1170,7 +1200,7 @@ def length(self, unit_cell): import cryspy.util as util import numpy as np if isinstance(unit_cell, unitcell): - + m = unit_cell.m return np.sqrt(util.xtaldot(p1=self.u, p2=self.v, p3=self.w, g11=m[0, 0], g12=m[0, 1], g13=m[0, 2], @@ -1198,6 +1228,120 @@ def angle(self, vec2, unit_cell): return np.arccos(nrm * pgq) +# ---------------------------------------------------------------------------- + +# def to_fundzone(self, symmetry, unit_cell, fundzone=None): +# TODO: Copied from miller class but not yet translated to lattvec +# ''' put miller object into the fundamental zone +# +# Parameters +# ---------- +# symmetry : symm object +# unit_cell : unitcell object +# fundzone : quat object (optional) +# Quaternion describing rotation required to put the Miller indices +# into the desired fundamental zone. Defaults to identity quaternion. +# +# Returns +# ------- +# mf : miller object +# miller indices projected into the fundamental zone for the given +# symmetry +# +# Examples +# -------- +# >>> import numpy as np +# >>> from cryspy.util import stereoproj +# >>> pgn = 'm-3m' +# >>> s = symm(pgn) +# >>> pointgroupnumber = interpret_point_group_name(pgn) +# >>> maxval = 49 +# >>> numvals = 500 +# >>> h = maxval * np.random.rand(numvals) - maxval / 2.0 +# >>> k = maxval * np.random.rand(numvals) - maxval / 2.0 +# >>> l = maxval * np.random.rand(numvals) - maxval / 2.0 +# >>> m = miller(h, k, l) # create miller +# >>> uc = unitcell() +# >>> m2 = m.to_fundzone(s, uc) +# >>> pf = stereoproj() +# >>> pf.add_miller(m, uc, uppermarkerfacecolor='r', +# ... lowermarkerfacecolor='w', +# ... uppermarkeredgecolor='r', +# ... lowermarkeredgecolor='r') +# >>> pf.add_miller(m2, uc, uppermarkerfacecolor='b', +# ... lowermarkerfacecolor='w', +# ... uppermarkeredgecolor='b', +# ... lowermarkeredgecolor='b') +# ''' +# import inspect +# import cryspy.rot as rot +# import cryspy.util as util +# import righthand as rh +# import numpy as np +# +# if fundzone==None: +# fundzone = rot.quat() +# +# ms = self.symmetrize(symmetry) +# +# # Consolidate symmetrized list into a single array of cartesian vectors +# x = np.zeros([self.size, np.size(ms)]) +# y = np.zeros([self.size, np.size(ms)]) +# z = np.zeros([self.size, np.size(ms)]) +# k = 0 +# for item in ms: +# tmp = item.to_cartesian(unit_cell) +# x[0:self.size, k] = tmp[0] +# y[0:self.size, k] = tmp[1] +# z[0:self.size, k] = tmp[2] +# k += 1 +# +# # Normalize cartesian vectors +# nrm = 1.0 / util.vecarraynorm([x, y, z]) +# x = x * nrm +# y = y * nrm +# z = z * nrm +# +# # Convert to spherical coordinates +# theta, r, rho = rh.polar(x, y, z) +# +# # get limits of the fundamental zone of the stereographic projection +# mintheta, maxtheta, minrho, maxrho = fundzonePF( +# symmetry.point_group_number) +# +# # handle theta cases on edges of fundamental zone +# cutoff = np.sqrt(np.spacing(1)) +# loc = np.absolute(theta - mintheta) < cutoff +# theta[loc] = mintheta +# loc = np.absolute(theta - maxtheta) < cutoff +# theta[loc] = maxtheta +# +# if inspect.isfunction(maxrho): +# mxr = maxrho(theta) +# loc = np.absolute(rho - minrho) < cutoff +# rho[loc] = minrho +# loc = np.absolute(rho - mxr) < cutoff +# rho[loc] = mxr[loc] +# crt = np.array(theta <= maxtheta) * np.array(rho <= mxr) * \ +# np.array(theta >= mintheta) * np.array(rho >= minrho) +# else: +# crt = np.array(theta <= maxtheta) * np.array(rho <= maxrho) * \ +# np.array(theta >= mintheta) * np.array(rho >= minrho) +# loc = np.argmax(crt, axis=1) +# +# # extract the symmetrically-equivalent miller indices in the fundamental zone +# h = np.zeros(self.size) +# k = np.zeros(self.size) +# l = np.zeros(self.size) +# +# for i in range(0, np.size(loc)): +# h[i] = np.squeeze(ms[loc[i]].h)[i] +# k[i] = np.squeeze(ms[loc[i]].k)[i] +# l[i] = np.squeeze(ms[loc[i]].l)[i] +# +# # create a new miller object and rotate it into the desired zone +# return lattvec(x, y, z).rotate(fundzone) + #------------------------------------------------------------------------------- def __sub__(self,vec2): @@ -1218,7 +1362,7 @@ def __repr__(self): import numpy as np qlist=np.array([self.u,self.v,self.w]).T - print '\n Lattice direction vectors [u v w]' + print('\n Lattice direction vectors [u v w]') np.set_printoptions(precision=3,suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1296,8 +1440,8 @@ def __init__(self, h=0, k=0, l=1): self.size = np.size(h) else: - print "lattvec construction error: check that the lengths of u,"\ - " v, and w are all the same." + print("lattvec construction error: check that the lengths of u,"\ + " v, and w are all the same.") # TODO: check into using warnings package return None #------------------------------------------------------------------------------- @@ -1408,7 +1552,7 @@ def from_cartesian(cls, arg, unit_cell, maxval=9, rationalizevals=True): import numpy as np import cryspy.util as util if isinstance(unit_cell,unitcell): - + x = arg[0] y = arg[1] z = arg[2] @@ -1451,10 +1595,10 @@ def angle(self, plane2, unit_cell): -------- >>> plane1.angle(plane2, my_unit_cell) #TODO: Fix example ''' - + import cryspy.util as util import numpy as np - + if isinstance(unit_cell, unitcell) and isinstance(plane2, miller): m = unit_cell.minv @@ -1500,6 +1644,7 @@ def cross(self, plane2): >>> common_dir = my_first_plane.cross( my_second_plane ) #TODO: Fix example ''' import cryspy.util as util + if isinstance(plane2, miller): u, v, w = util.vecarraycross( [self.h, self.k, self.l], @@ -1525,13 +1670,13 @@ def rotate(self, r): Examples -------- - >>> m = miller(1,0,4) + >>> m = miller(1, 0, 4) >>> s = pointgroupelements('m-3m') - >>> ms= m * s + >>> ms = m.rotate(s) ''' from cryspy.rot import quat, rmat import numpy as np - + if isinstance(r, quat): t2 = r.a * r.b t3 = r.a * r.c @@ -1658,6 +1803,7 @@ def to_fundzone(self, symmetry, unit_cell, fundzone=None): import inspect import cryspy.rot as rot import cryspy.util as util + import righthand as rh import numpy as np if fundzone==None: @@ -1684,7 +1830,7 @@ def to_fundzone(self, symmetry, unit_cell, fundzone=None): z = z * nrm # Convert to spherical coordinates - theta, r, rho = util.polar(x, y, z) + theta, r, rho = rh.polar(x, y, z) # get limits of the fundamental zone of the stereographic projection mintheta, maxtheta, minrho, maxrho = fundzonePF( @@ -1714,7 +1860,7 @@ def to_fundzone(self, symmetry, unit_cell, fundzone=None): h = np.zeros(self.size) k = np.zeros(self.size) l = np.zeros(self.size) - + for i in range(0, np.size(loc)): h[i] = np.squeeze(ms[loc[i]].h)[i] k[i] = np.squeeze(ms[loc[i]].k)[i] @@ -1747,7 +1893,7 @@ def __repr__(self): import numpy as np qlist=np.array(np.squeeze([self.h, self.k, self.l])).T - print '\n Miller indices (h k l)' + print('\n Miller indices (h k l)') # TODO: check into using warnings package np.set_printoptions(precision=3, suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1783,7 +1929,7 @@ def __init__(self, a=1.0, b=1.0, c=1.0, # always type g11=..., g12=... etc. import numpy as np import numpy.linalg as npla - import cryspy.util as util + import righthand as rh # check that the shapes of all are the same if np.shape( a)==np.shape( b)==np.shape( c)==\ @@ -1796,9 +1942,9 @@ def __init__(self, a=1.0, b=1.0, c=1.0, self.a = np.float64( np.squeeze( np.array( list( (a,) )))) self.b = np.float64( np.squeeze( np.array( list( (b,) )))) self.c = np.float64( np.squeeze( np.array( list( (c,) )))) - self.alpha = util.radians(np.float64( np.squeeze( np.array( list( (alpha,) ))))) - self.beta = util.radians(np.float64( np.squeeze( np.array( list( (beta,) ))))) - self.gamma = util.radians(np.float64( np.squeeze( np.array( list( (gamma,) ))))) + self.alpha = rh.to_radians(np.float64( np.squeeze( np.array( list( (alpha,) ))))) + self.beta = rh.to_radians(np.float64( np.squeeze( np.array( list( (beta,) ))))) + self.gamma = rh.to_radians(np.float64( np.squeeze( np.array( list( (gamma,) ))))) # calculate the metric matrix and its inverse ca = np.cos(self.alpha) @@ -1834,7 +1980,7 @@ def __repr__(self): import numpy as np qlist=np.array([self.a,self.b,self.c,self.alpha,self.beta,self.gamma]).T - print '\n Unit cell a, b, c, alpha, beta, gamma' + print('\n Unit cell a, b, c, alpha, beta, gamma') # TODO: check into using warnings package np.set_printoptions(precision=3,suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1845,27 +1991,27 @@ def __repr__(self): class symm(object): """symmetry object - + Wraps up the symmetry elements for a given point group into a single object. Pure rotations are described in quaternion form while the complete set of symmetry operations (i.e., including improper rotations) is given as rotation matrices. - + Parameters ---------- pointgroup : str point group of the crystal notation : {'international', 'schoenflies', 'geo', 'tsl', 'numeric'} Default: 'international' - + Notes ----- - No .size method is provided for this class because the number of symmetry elements is different from the number of rotation operations. Both of these parameters are stored within the symm object and have their own - .size methods. + .size methods. """ - + def __init__(self, pointgroup='1', notation='international'): import cryspy.rot as rot @@ -1876,7 +2022,7 @@ def __init__(self, pointgroup='1', notation='international'): self.notation = 'international' else: self.point_group_name = str(pointgroup) - + # Use string arguments to construct symm object attributes self.point_group_number = interpret_point_group_name( self.point_group_name, @@ -1909,32 +2055,39 @@ def __init__(self, quaternions=None, pointgroupnumbers=None, reference=None): import cryspy.rot as rot import cryspy.xtal as xtal + import cryspy.util as util import numpy as np import warnings - if reference==None: + if reference is None: reference = rot.quat() - if quaternions==None: + if quaternions is None: quaternions = rot.quat() - if pointgroupnumbers==None: - pointgroupnumbers = 1 + if pointgroupnumbers is None: + pointgroupnumbers = np.tile(1, quaternions.size) + + pointgroupnumbers = util.vecarrayconvert(pointgroupnumbers) if isinstance(quaternions, rot.quat): self.rotations = quaternions else: self.rotations = rot.quat() + loc = np.where(np.logical_or(pointgroupnumbers > 32, pointgroupnumbers < 1))[0] + if loc.size != 0: + pointgroupnumbers[loc] = 1 + #FIXME: make a warning warnings.warn() + print('\nWARNING: Invalid point group numbers at:\n{0}'.format(loc)) - loc = np.where(np.logical_or(pointgroupnumbers > 32, pointgroupnumbers < 1)) - pointgroupnumbers[loc] = 1 - warnings.warn('Invalid point group numbers at:\n{0}'.format(loc)) self.symmetry = pointgroupnumbers if isinstance(reference, rot.quat): self.reference = reference - + else: + warnings.warn('reference needs to be quaternion') + self.size = self.rotations.size # Add two parameters to the orientation object. One is a list of @@ -1947,17 +2100,18 @@ def __init__(self, quaternions=None, for item in u: cs_set = xtal.symm(item, 'numeric') cs.append(cs_set) - cslocs = np.where(self.symmetry==item) + cslocs.append(np.where(self.symmetry==item)[0]) self.cs = cs self.cslocs = cslocs - def __getitem__(self,index): + def __getitem__(self, index): + return orientation(quaternions = self.rotations[index], pointgroupnumbers = self.symmetry[index], - reference = self.reference()) + reference = self.reference) def __repr__(self): - + import numpy as np ar= self.reference.a[0] @@ -1967,48 +2121,107 @@ def __repr__(self): repstr = '{0:^45s}\n'.format('orientation object') repstr += '{0:-^45s}\n'.format('----') - repstr += ' {0:^7s}{1:^7s}{2:^7s}{3:^7s} # {4:6.0f}\n' + ' # {4: <2d}\n' if np.shape(self.rotations.a)[0] > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.rotations.a[i], - self.rotations.b[i], - self.rotations.c[i], - self.rotations.d[i], + repstr += fmtstr.format(self.rotations.a[i], + self.rotations.b[i], + self.rotations.c[i], + self.rotations.d[i], self.symmetry[i]) for i in np.arange(0, 3): repstr += ' {0:^7s}{0:^7s}{0:^7s}{0:^7s} \n'.format('.') n = np.shape(self.reference.a)[0]-1 for i in np.arange(n-5, n): - repstr += fmtstr.format(self.rotations.a[i], - self.rotations.b[i], - self.rotations.c[i], - self.rotations.d[i], - self.symmetry[i]) + repstr += fmtstr.format(self.rotations.a[i], + self.rotations.b[i], + self.rotations.c[i], + self.rotations.d[i], + self.symmetry[i]) else: - for i in np.arange(0, self.size): - repstr += fmtstr.format(self.rotations.a[i], - self.rotations.b[i], - self.rotations.c[i], - self.rotations.d[i], + repstr += fmtstr.format(self.rotations.a[i], + self.rotations.b[i], + self.rotations.c[i], + self.rotations.d[i], self.symmetry[i]) - - repstr += '{0:-^45s}\n'.format('----') + + repstr += '{0:-^45s}\n'.format('----') namestr = 'reference q = ' namestr += '<{0: >7.3f}{1: >7.3f}{2: >7.3f}{3: >7.3f} >'.format(ar, br, cr, dr) - repstr += '{0:' 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 " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @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)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(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/ovlib.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ovlib.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/ovlib" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ovlib" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(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: + $(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." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +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." diff --git a/docs/README.txt b/docs/README.txt new file mode 100755 index 0000000..405d876 --- /dev/null +++ b/docs/README.txt @@ -0,0 +1,51 @@ +============================================================================== +CrysPy: Crystallographic Computations in Python:: +*A Python Toolbox for EBSD Data Analysis* +============================================================================== + +CrysPy is a Python toolbox for Crystallographic Computations. It intends to provide a set of +open source tools for visualization, analysis, and postprocessing of EBSD data. + +============================================================================== + +COMPONENTS + +* io :: modules for import and export of EBSD data +* rot :: modules for rotation representation, manipulation, and conversion +* vis :: modules for data visualization +* ebsd :: modules for EBSD data analysis and manipulation +* xtal :: modules for crystallography and symmetry calculations +* util :: utilities + +============================================================================== + +CONTENT HIGHLIGHTS + +io +------------------------------------------------------------------------------ +* loadang:: support for loading TSL *.ang files + +rot +------------------------------------------------------------------------------ +* angax :: class for the angle/axis convention +* bunge :: class for Bunge Euler angles +* quat :: class for quaternion representations +* rodri :: class for Rodrigues vector representations +* tvec :: class for transformation vectors (reshaped rotation matrices) + +xtal +------------------------------------------------------------------------------ +* lattvec :: class for representing lattice vectors +* miller :: class for representing lattice plane normals +* unitcell:: class for representing unit cells +* rotsymm :: class for rotational symmetry objects + +vis +------------------------------------------------------------------------------ +* stereoproj :: class for plotting stereographic projections +* eaproj :: class for plotting equal area projections + +util +------------------------------------------------------------------------------ +* rationalize :: function for turning fractional vectors into rational indices +* sigdec :: function for rounding to n significant digits diff --git a/docs/conf.py b/docs/conf.py new file mode 100755 index 0000000..67da5d2 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- +# +# ovlib documentation build configuration file, created by +# sphinx-quickstart on Tue Mar 26 18:32:41 2013. +# +# 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 + +# 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.viewcode', 'numpydoc'] + +# 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'ovlib' +copyright = u'2013, Eric Payton, Federal Institute for Materials Research \ +and Testing, Berlin' + +# 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 = '' +# The full version, including alpha/beta/rc tags. +release = '' + +# 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 = ['_build'] + +# 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 = {} + +# Add any paths that contain custom themes here, relative to this directory. +#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 = None + +# 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'] + +# 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 + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# 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 + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ovlibdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ovlib.tex', u'ovlib Documentation', + u'Author', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# 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 + +# 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 = [ + ('index', 'ovlib', u'ovlib Documentation', + [u'Author'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ovlib', u'ovlib Documentation', + u'Author', 'ovlib', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'ovlib' +epub_author = u'Author' +epub_publisher = u'Author' +epub_copyright = u'2013, Author' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True diff --git a/docs/cryspy.rst b/docs/cryspy.rst new file mode 100755 index 0000000..c96722d --- /dev/null +++ b/docs/cryspy.rst @@ -0,0 +1,34 @@ +.. automodule:: ovlib.ebsd + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: ovlib.io + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: ovlib.orrl + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: ovlib.rot + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: ovlib.util + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: ovlib.vis + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: ovlib.xtal + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100755 index 0000000..79d93a0 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,45 @@ +What is the _a pyori_ project philosophy? +----------------------------------------- + +We intend for _a pyori_ to be a cross-platform set of tools for EBSD data analysis that is implemented primarily in Python packages that are installed with Python(x,y) and Spyder. + +All functions in the OV library are directly available for the user. There are very few private functions. You can mix and match the functions that you need in order to do any analysis that you can dream up. + + +Why Python? +----------- + +It doesn't take very long to pick up the basics of playing guitar and be able to play a tune, yet even seasoned musicians continue to use guitars to make music. Python is similar: it is a relatively easy language to pick up for people with little programming experience, and professional programmers routinely turn to Python to make sophisticated programs. With packages like python(x,y), inexperienced users have an extremely powerful environment at their fingertips to try out new ideas. The environment is free and extensible, and some knowledge of Python will remain useful to the beginner. + +The primary purpose of the orientation visualizer library is to allow for new EBSD data analysis techniques to be invented and implemented by researchers interested in doing such things. A secondary purpose is to allow for automated analysis in routine investigations through scripting. + + +What is the difference between _a pyori_ and mtex? +------------------------------------------- + +The core focus of MTEX is the mathematics of texture analysis, while the core focus of _a pyori_ is enabling the testing of novel EBSD data analysis ideas. MTEX requires a MATLAB license, while _a pyori_ is written in Python, which is both free in both the 'beer' and 'speech' senses. + +Mtex offers many functions that are not currently available in OV (but may be available in the future). It is important to note that OV does not currently support texture analysis, 3D data sets, non-cubic pole figures, tensor analyses, and many other things that are available in mtex. + +What is the difference between _a pyori_ and DREAM.3D? +------------------------------------------------------ + +The core focus of DREAM.3D is the analysis of 3D EBSD datasets, while _a pyori_ only has the capability of working with 2D data. + +Why can't the best features of all of these EBSD freeware projects be combined? +------------------------------------------------------------------------------- + +OV is essentially a library of functions that are available to the user, with a simple user interface that allows for routine analyses. + +When developing new analysis techniques or implementing experimental ones, it is desirable to have full access to a library of subfunctions. The OV user interface is built off of this library. The OV library is written with the purpose of making each function as intuitive as possible, and most of the code is extensively commented so that interested users can easily understand the code. + +OV intentionally avoids using user-defined object classes, so the user has the possibility of manipulating their data as they wish. This is incompatible with mtex, since object classes are a fundamental part of mtex. It would take a lot of work to make every aspect of every function and class in mtex fully available for the user – this would require a complete rewriting of mtex. On the other hand, implementing mtex algorithms into OV is relatively easy. + +One major drawback of using specially-defined object classes is that the end user needs to have a greater understanding of the operations of the program. + +Mtex is covered by the GPLv2. This is a copyleft license that allows the code to be reused as long as the end program is also free and open source. Therefore, code can be shared between OV and mtex. + +Disclaimers +----------- + +OV has been developed in the course of the regular work duties of its authors. This code is a work in progress and is distributed on an as-is basis without warranties or conditions of any kind, either express or implied. \ No newline at end of file diff --git a/docs/generate_docs.py b/docs/generate_docs.py new file mode 100755 index 0000000..6f065d3 --- /dev/null +++ b/docs/generate_docs.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from subprocess import call +call('make html', shell=True) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100755 index 0000000..5a3ed56 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,50 @@ +.. ovlib documentation master file + +ovlib: The Orientation Visualizer Library +***************************************** + +Function Highlights +=================== +- Import *.ang and *.ctf files +- Convert between various rotation representations +- Perform crystallographic calculations +- Plot stereographic and equal area projections + +*The package contents below contains a link to the complete list of functions +in the module.* + +Future Work & Key Limitations +============================= +- Cannot perform texture analysis +- No scale bars +- No GUI + +FAQ +==== +.. toctree:: + :maxdepth: 4 + + faq + +Installation & Beginner's Tutorial +================================== + +.. toctree:: + :maxdepth: 4 + + installation + +Package Contents +================ + +.. toctree:: + :maxdepth: 4 + + ovlib + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100755 index 0000000..90d3ff8 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,58 @@ +These instructions explain how to get OVLib up and running on your computer. In principle, any distribution of Python 2.7 will do; however, the instructions below are written specifically for using the Spyder IDE and Python(x,y). We recommend the Spyder IDE due to its Matlab-like interface, which many users may find familiar. + +1. Install Python(x,y) and visvis +================================= + +Detailed instructions for Windows users +--------------------------------------- +1. Download and install `python(x,y) `_ +2. Install the `visvis plugin `_ for Python(x,y). +3. Install `pyqtgraph ` using the Windows installer. + +Detailed instructions for Mac users +----------------------------------- +Installing python(x,y) on a macintosh requires compiling it from the source code. This is *very* easy; however, downloading and compiling all the components can take several hours to complete. + +Follow these steps: + +1. Install Apple's Xcode Developer Tools + a. Launch Xcode and accept the EULA + b. If OS is older than Mountain Lion, you may need to go into preferences and add command line tools. +2. Install MacPorts using the .pkg installer. +3. Open up a terminal window and run the following + a. sudo port selfupdate + b. sudo port upgrade outdated + c. sudo port -v install py27-spyder + d. sudo port install py27-opengl + e. sudo easy-install pip + f. pip install visvis + g. pip install pyqtgraph +4. Drag the spyder.app (available from me) to your applications folder, double click to run it. + +2. Install OVLib using Mercurial +================================ + +1. Install a Mercurial client + +* For Mac users, we recommend `MacHg `_ +* For Windows users, we recommend: `TortoiseHg `_ + +2. Clone the OVLib bitbucket repository. The following links may be helpful: + +* http://ratfactor.com/mercurial-named-branches#bitbucket +* http://sce.uhcl.edu/support/files/StepsMacHG.pdf + +To update your OVLib, pull incoming changes. + +Contributing to ovlib may be done by creating a bitbucket account, forking the code, making your own changes, then pushing them to bitbucket. + +3. Add OVLib to your Python Path +================================ +1. Open up Spyder, go to the "Tools" menu, and click on PYTHONPATH manager. +2. Add your local ovlib directory to your python path and click "sychronize." +3. Restart Spyder + + +Beginner's tutorial +=================== +*Coming soon.* \ No newline at end of file diff --git a/orrl.py b/orrl.py deleted file mode 100644 index 41bbae6..0000000 --- a/orrl.py +++ /dev/null @@ -1,1127 +0,0 @@ -# -*- coding: utf-8 -*- -''' -ovlib.orrl: Orientation relationship characterization -=============================================================================== -''' - -def namedOR(name): - """ Returns ksi values for named orientation relationships - - Parameters - ---------- - name : {'ks', 'nw', 'bain'} - Orientation relationship name - - Returns - ------- - ksi_values : 1x3 numpy array of floats - ksi values in degrees - - Notes - ----- - TODO: Add plane parallel Greninger-Troiano, Kelly, etc. - TODO: Allow 'Kurdjumov-Sachs' as well as 'ks', etc. - """ - import numpy as np - - if isinstance(name, str): - - if name.lower() == 'ks': - - s6 = np.sqrt(6.0) - s3 = np.sqrt(3.0) - ksi1 = np.arccos((s6 + 1.0) / (2.0 * s3)) - ksi2 = np.arccos((s6 + 18.0) / (12.0 * s3)) - ksi3 = np.arccos((s6 + 12.0) / (6.0 * s6)) - ksi = np.array([ksi1, ksi2, ksi3]) - del s6, s3, ksi1, ksi2, ksi3 - - elif name.lower() == 'nw': - - s6 = np.sqrt(6) - s2 = np.sqrt(2) - ksi0 = np.arccos((s2 + 1.0) / s6) - ksi = np.array([0.0, ksi0, ksi0]) - - elif name.lower() == 'bain': - - ksi = np.array([0.0, 0.0, 0.0]) - - else: - - print 'namedOR: Unrecognized named OR' - - else: - - print 'namedOR requires a string input. Returning Bain.' - ksi = np.array([0.0, 0.0, 0.0]) - - return ksi * 180.0/np.pi - -def getRepresentativeVariant(ksi_values): - """ Returns quaternion of representative variant - - Parameters - ---------- - ksi_values : - - Returns - ------- - psiq : quat class - """ - import numpy as np - from cryspy.rot import quat - - if isinstance(ksi_values, str): - orrl = namedOR(ksi_values) - else: - # Convert OR specification into radians - orrl = ksi_values * np.pi / 180.0 - - csum = np.cos(orrl[0])+np.cos(orrl[1])+np.cos(orrl[2]) - x0 = 0.5 * np.sqrt(csum + 1.0) - x = 0.5 * np.sqrt(2. * np.cos(orrl) - csum + 1.0) - - ksiq = quat(x0, x[0], x[1], x[2]) - - # Calculate quaternion for normalised Bain correspondence. - - qw = 0.5 * np.sqrt(2.0 + np.sqrt(2.0)) - qz = (2.0 / np.sqrt(2.0)) / (4.0 * qw) - gammaq = quat(qw, 0.0, 0.0, qz); - - psiq = ksiq * gammaq - - return psiq - -def generateVariants(ksi_values): - """ Generate variants from Kurdjumov-Sachs angles - - Returns matrices of an orientation relationship specified in Kurjumov-Sachs - angles. - - Parameters - ---------- - ksi_values : length 3 iterable OR {'KS', 'NW', 'Bain'} - - Returns - ------- - vv : rmat object - rotation matrices corresponding to variants - - """ - import numpy as np - from cryspy.rot import rmat - from cryspy.util import vecarraynorm, uniquerows, sigdec - - if isinstance(ksi_values, str): - - ksi = namedOR(ksi_values) - - # convert ksi radians to rotation matrices - - mb = np.zeros([2, 9]) - - mb[0, 0] = np.cos(ksi[0]) - mb[0, 4] = np.cos(ksi[1]) - mb[0, 8] = np.cos(ksi[2]) - - costh = 0.5 * (np.sum(np.cos(ksi)) - 1.0) # sum(cos(ksi)) is the matrix trace - mosth = 1.0 - costh - sinth = np.sqrt(1.0 - costh**2.0) - - - r1 = np.sqrt((mb[0, 0] - costh) / mosth) - r2 = np.sqrt((mb[0, 4] - costh) / mosth) - r3 = np.sqrt((mb[0, 8] - costh) / mosth) - del costh - - r1r2 = r1 * r2 * mosth - r1r3 = r1 * r3 * mosth - r2r3 = r2 * r3 * mosth - r3st = r3 * sinth - r2st = r2 * sinth - r1st = r1 * sinth - del r1, r2, r3, mosth, sinth - - mb[0, 5] = r2r3 - r1st - mb[0, 7] = r2r3 + r1st - mb[1, :] = mb[0, :] - - mb[0, 1] = -r1r2 + r3st - mb[0, 2] = -r1r3 - r2st - mb[0, 3] = -r1r2 - r3st - mb[0, 6] = -r1r3 + r2st - del r1r2, r1r3, r2r3, r3st, r2st, r1st - - mb[1, 1] = -mb[0, 1] - mb[1, 2] = -mb[0, 2] - mb[1, 3] = -mb[0, 3] - mb[1, 6] = -mb[0, 6] - # mb[0] is the 'positive' solution; mb[1] is the 'negative' solution - - # create Bain correspondence matrices - bb = np.zeros([12, 9]) - bb[ 0, :] = [ 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0] - bb[ 1, :] = [ 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0] - bb[ 2, :] = [-1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - bb[ 3, :] = [ 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 0.0] - bb[ 4, :] = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0] - bb[ 5, :] = [ 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0] - bb[ 6, :] = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0] - bb[ 7, :] = [-1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - bb[ 8, :] = [ 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 0.0] - bb[ 9, :] = [ 1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0] - bb[10, :] = [ 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 0.0] - bb[11, :] = [-1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, -1.0] - - # normalize correspondence matrices - bb = rmat.from_array(bb / vecarraynorm(bb)) - mb = rmat.from_array(mb) - - # produce variants - vv = np.zeros([24, 9]) - tmp = mb[0] * bb - vv[np.arange(0, 24, 2), :] = tmp.to_array() - tmp = mb[1] * bb - vv[np.arange(1, 24, 2), :] = tmp.to_array() - - # reduce redundancies, if they exist (as they do, for example, in NW) - vv, ia, ic = uniquerows(sigdec(vv, 7)) - del ia, ic - - return rmat.from_array(vv) - - - -#------------------------------------------------------------------------------ - -def _bg_ksi1(bincenters): - """ returns ksi1 background - - Parameters - ---------- - bincenters : numpy array - centers of bins for ksi1 background - - Returns - ------- - bg : numpy array - background number fractions for ksi1 - - Notes - ----- - Background is determined by cubic spline interpolation from a large - numerical simulation containing 17156424 ksi values measured from - random orientations. - - """ - import numpy as np - import scipy.interpolate as interp - - d = np.array([ [ 0.00000000e+00, 0.00000000e+00], - [ 1.50000000e-01, 8.04400000e+03], - [ 4.50000000e-01, 2.45720000e+04], - [ 7.50000000e-01, 4.06200000e+04], - [ 1.05000000e+00, 5.61680000e+04], - [ 1.35000000e+00, 7.13330000e+04], - [ 1.65000000e+00, 8.80860000e+04], - [ 1.95000000e+00, 1.02050000e+05], - [ 2.25000000e+00, 1.17010000e+05], - [ 2.55000000e+00, 1.31760000e+05], - [ 2.85000000e+00, 1.47720000e+05], - [ 3.15000000e+00, 1.60180000e+05], - [ 3.45000000e+00, 1.75440000e+05], - [ 3.75000000e+00, 1.88830000e+05], - [ 4.05000000e+00, 2.03510000e+05], - [ 4.35000000e+00, 2.16520000e+05], - [ 4.65000000e+00, 2.29790000e+05], - [ 4.95000000e+00, 2.43770000e+05], - [ 5.25000000e+00, 2.56080000e+05], - [ 5.55000000e+00, 2.68910000e+05], - [ 5.85000000e+00, 2.81760000e+05], - [ 6.15000000e+00, 2.93690000e+05], - [ 6.45000000e+00, 3.06460000e+05], - [ 6.75000000e+00, 3.16310000e+05], - [ 7.05000000e+00, 3.27680000e+05], - [ 7.35000000e+00, 3.40790000e+05], - [ 7.65000000e+00, 3.50150000e+05], - [ 7.95000000e+00, 3.63730000e+05], - [ 8.25000000e+00, 3.74700000e+05], - [ 8.55000000e+00, 3.83250000e+05], - [ 8.85000000e+00, 3.93360000e+05], - [ 9.15000000e+00, 4.01330000e+05], - [ 9.45000000e+00, 4.08800000e+05], - [ 9.75000000e+00, 4.10170000e+05], - [ 1.00500000e+01, 4.16810000e+05], - [ 1.03500000e+01, 4.22100000e+05], - [ 1.06500000e+01, 4.24130000e+05], - [ 1.09500000e+01, 4.25640000e+05], - [ 1.12500000e+01, 4.26720000e+05], - [ 1.15500000e+01, 4.26860000e+05], - [ 1.18500000e+01, 4.25850000e+05], - [ 1.21500000e+01, 4.25010000e+05], - [ 1.24500000e+01, 4.19670000e+05], - [ 1.27500000e+01, 4.14030000e+05], - [ 1.30500000e+01, 4.06510000e+05], - [ 1.33500000e+01, 3.97900000e+05], - [ 1.36500000e+01, 3.85480000e+05], - [ 1.39500000e+01, 3.72070000e+05], - [ 1.42500000e+01, 3.55970000e+05], - [ 1.45500000e+01, 3.36740000e+05], - [ 1.48500000e+01, 3.11690000e+05], - [ 1.51500000e+01, 2.79820000e+05], - [ 1.54500000e+01, 2.57130000e+05], - [ 1.57500000e+01, 2.35370000e+05], - [ 1.60500000e+01, 2.18230000e+05], - [ 1.63500000e+01, 2.01140000e+05], - [ 1.66500000e+01, 1.84260000e+05], - [ 1.69500000e+01, 1.68320000e+05], - [ 1.72500000e+01, 1.54590000e+05], - [ 1.75500000e+01, 1.39760000e+05], - [ 1.78500000e+01, 1.27400000e+05], - [ 1.81500000e+01, 1.14020000e+05], - [ 1.84500000e+01, 1.03180000e+05], - [ 1.87500000e+01, 9.14750000e+04], - [ 1.90500000e+01, 8.00980000e+04], - [ 1.93500000e+01, 7.07470000e+04], - [ 1.96500000e+01, 6.10580000e+04], - [ 1.99500000e+01, 5.08750000e+04], - [ 2.02500000e+01, 4.25510000e+04], - [ 2.05500000e+01, 3.39610000e+04], - [ 2.08500000e+01, 2.52270000e+04], - [ 2.11500000e+01, 1.73040000e+04], - [ 2.14500000e+01, 1.12930000e+04], - [ 2.17500000e+01, 7.17200000e+03], - [ 2.20500000e+01, 3.76000000e+03], - [ 2.23500000e+01, 1.55200000e+03], - [ 2.26500000e+01, 3.94000000e+02], - [ 2.29500000e+01, 8.00000000e+00], - [ 2.32500000e+01, 0.00000000e+00], - [ 2.35500000e+01, 0.00000000e+00], - [ 2.38500000e+01, 0.00000000e+00], - [ 2.41500000e+01, 0.00000000e+00], - [ 2.44500000e+01, 0.00000000e+00], - [ 2.47500000e+01, 0.00000000e+00], - [ 2.50500000e+01, 0.00000000e+00], - [ 2.53500000e+01, 0.00000000e+00], - [ 2.56500000e+01, 0.00000000e+00], - [ 2.59500000e+01, 0.00000000e+00], - [ 2.62500000e+01, 0.00000000e+00], - [ 2.65500000e+01, 0.00000000e+00], - [ 2.68500000e+01, 0.00000000e+00], - [ 2.71500000e+01, 0.00000000e+00], - [ 2.74500000e+01, 0.00000000e+00], - [ 2.77500000e+01, 0.00000000e+00], - [ 2.80500000e+01, 0.00000000e+00], - [ 2.83500000e+01, 0.00000000e+00], - [ 2.86500000e+01, 0.00000000e+00], - [ 2.89500000e+01, 0.00000000e+00], - [ 2.92500000e+01, 0.00000000e+00], - [ 2.95500000e+01, 0.00000000e+00], - [ 2.98500000e+01, 0.00000000e+00], - [ 3.01500000e+01, 0.00000000e+00], - [ 3.04500000e+01, 0.00000000e+00], - [ 3.07500000e+01, 0.00000000e+00], - [ 3.10500000e+01, 0.00000000e+00], - [ 3.13500000e+01, 0.00000000e+00], - [ 3.16500000e+01, 0.00000000e+00], - [ 3.19500000e+01, 0.00000000e+00], - [ 3.22500000e+01, 0.00000000e+00], - [ 3.25500000e+01, 0.00000000e+00], - [ 3.28500000e+01, 0.00000000e+00], - [ 3.31500000e+01, 0.00000000e+00], - [ 3.34500000e+01, 0.00000000e+00], - [ 3.37500000e+01, 0.00000000e+00], - [ 3.40500000e+01, 0.00000000e+00], - [ 3.43500000e+01, 0.00000000e+00], - [ 3.46500000e+01, 0.00000000e+00]]) - - minval = np.sqrt(np.spacing(np.float32(1))) - cj = interp.UnivariateSpline(d[:,0], - d[:,1]+minval, - k=1, s=0.5) - - # Interpolate the spline for our bin array into a background for our histogram - bg = cj(bincenters) - bg[bg < minval] = minval # do not allow division by zero - - return bg - -def _bg_ksi2(bincenters): - """ returns ksi1 background - - Parameters - ---------- - bincenters : numpy array - centers of bins for ksi1 background - - Returns - ------- - bg : numpy array - background number fractions for ksi1 - - Notes - ----- - Background is determined by cubic spline interpolation from a large - numerical simulation containing 17156424 ksi values measured from - random orientations. The bin spacing in this histogram is 0.3 degrees. - - """ - import numpy as np - import scipy.interpolate as interp - - d = np.array([ [ 0.00000000e+00, 0.00000000e+00], - [ 1.50000000e-01, 3.80000000e+01], - [ 4.50000000e-01, 2.53000000e+02], - [ 7.50000000e-01, 6.37000000e+02], - [ 1.05000000e+00, 1.30400000e+03], - [ 1.35000000e+00, 2.14800000e+03], - [ 1.65000000e+00, 3.15500000e+03], - [ 1.95000000e+00, 4.51300000e+03], - [ 2.25000000e+00, 6.03800000e+03], - [ 2.55000000e+00, 7.55500000e+03], - [ 2.85000000e+00, 9.49000000e+03], - [ 3.15000000e+00, 1.15950000e+04], - [ 3.45000000e+00, 1.37350000e+04], - [ 3.75000000e+00, 1.64950000e+04], - [ 4.05000000e+00, 1.88700000e+04], - [ 4.35000000e+00, 2.18850000e+04], - [ 4.65000000e+00, 2.54230000e+04], - [ 4.95000000e+00, 2.88190000e+04], - [ 5.25000000e+00, 3.21320000e+04], - [ 5.55000000e+00, 3.60380000e+04], - [ 5.85000000e+00, 3.99840000e+04], - [ 6.15000000e+00, 4.36950000e+04], - [ 6.45000000e+00, 4.81920000e+04], - [ 6.75000000e+00, 5.32350000e+04], - [ 7.05000000e+00, 5.78030000e+04], - [ 7.35000000e+00, 6.26690000e+04], - [ 7.65000000e+00, 6.77040000e+04], - [ 7.95000000e+00, 7.24380000e+04], - [ 8.25000000e+00, 7.90320000e+04], - [ 8.55000000e+00, 8.46900000e+04], - [ 8.85000000e+00, 9.05220000e+04], - [ 9.15000000e+00, 9.71920000e+04], - [ 9.45000000e+00, 1.03820000e+05], - [ 9.75000000e+00, 1.10760000e+05], - [ 1.00500000e+01, 1.17170000e+05], - [ 1.03500000e+01, 1.23890000e+05], - [ 1.06500000e+01, 1.31260000e+05], - [ 1.09500000e+01, 1.38800000e+05], - [ 1.12500000e+01, 1.47670000e+05], - [ 1.15500000e+01, 1.54370000e+05], - [ 1.18500000e+01, 1.62540000e+05], - [ 1.21500000e+01, 1.71450000e+05], - [ 1.24500000e+01, 1.80180000e+05], - [ 1.27500000e+01, 1.88610000e+05], - [ 1.30500000e+01, 1.96360000e+05], - [ 1.33500000e+01, 2.06070000e+05], - [ 1.36500000e+01, 2.15760000e+05], - [ 1.39500000e+01, 2.25380000e+05], - [ 1.42500000e+01, 2.34630000e+05], - [ 1.45500000e+01, 2.45320000e+05], - [ 1.48500000e+01, 2.56620000e+05], - [ 1.51500000e+01, 2.62000000e+05], - [ 1.54500000e+01, 2.64870000e+05], - [ 1.57500000e+01, 2.69010000e+05], - [ 1.60500000e+01, 2.71560000e+05], - [ 1.63500000e+01, 2.74630000e+05], - [ 1.66500000e+01, 2.78040000e+05], - [ 1.69500000e+01, 2.80570000e+05], - [ 1.72500000e+01, 2.82790000e+05], - [ 1.75500000e+01, 2.85960000e+05], - [ 1.78500000e+01, 2.88360000e+05], - [ 1.81500000e+01, 2.89220000e+05], - [ 1.84500000e+01, 2.92980000e+05], - [ 1.87500000e+01, 2.93430000e+05], - [ 1.90500000e+01, 2.96900000e+05], - [ 1.93500000e+01, 2.97160000e+05], - [ 1.96500000e+01, 2.98050000e+05], - [ 1.99500000e+01, 3.00280000e+05], - [ 2.02500000e+01, 3.02770000e+05], - [ 2.05500000e+01, 3.01800000e+05], - [ 2.08500000e+01, 3.04190000e+05], - [ 2.11500000e+01, 3.05480000e+05], - [ 2.14500000e+01, 3.02770000e+05], - [ 2.17500000e+01, 3.01660000e+05], - [ 2.20500000e+01, 2.96840000e+05], - [ 2.23500000e+01, 2.91940000e+05], - [ 2.26500000e+01, 2.87030000e+05], - [ 2.29500000e+01, 2.79490000e+05], - [ 2.32500000e+01, 2.74410000e+05], - [ 2.35500000e+01, 2.68910000e+05], - [ 2.38500000e+01, 2.60700000e+05], - [ 2.41500000e+01, 2.54640000e+05], - [ 2.44500000e+01, 2.47580000e+05], - [ 2.47500000e+01, 2.42160000e+05], - [ 2.50500000e+01, 2.36140000e+05], - [ 2.53500000e+01, 2.29720000e+05], - [ 2.56500000e+01, 2.26630000e+05], - [ 2.59500000e+01, 2.17650000e+05], - [ 2.62500000e+01, 2.12590000e+05], - [ 2.65500000e+01, 2.06500000e+05], - [ 2.68500000e+01, 2.01920000e+05], - [ 2.71500000e+01, 1.95560000e+05], - [ 2.74500000e+01, 1.88290000e+05], - [ 2.77500000e+01, 1.84200000e+05], - [ 2.80500000e+01, 1.78390000e+05], - [ 2.83500000e+01, 1.72670000e+05], - [ 2.86500000e+01, 1.66430000e+05], - [ 2.89500000e+01, 1.61550000e+05], - [ 2.92500000e+01, 1.55230000e+05], - [ 2.95500000e+01, 1.50620000e+05], - [ 2.98500000e+01, 1.44690000e+05], - [ 3.01500000e+01, 1.00740000e+05], - [ 3.04500000e+01, 6.55290000e+04], - [ 3.07500000e+01, 4.09580000e+04], - [ 3.10500000e+01, 2.05860000e+04], - [ 3.13500000e+01, 3.76200000e+03], - [ 3.16500000e+01, 0.00000000e+00], - [ 3.19500000e+01, 0.00000000e+00], - [ 3.22500000e+01, 0.00000000e+00], - [ 3.25500000e+01, 0.00000000e+00], - [ 3.28500000e+01, 0.00000000e+00], - [ 3.31500000e+01, 0.00000000e+00], - [ 3.34500000e+01, 0.00000000e+00], - [ 3.37500000e+01, 0.00000000e+00], - [ 3.40500000e+01, 0.00000000e+00], - [ 3.43500000e+01, 0.00000000e+00], - [ 3.46500000e+01, 0.00000000e+00]]) - - minval = np.sqrt(np.spacing(np.float32(1))) - cj = interp.UnivariateSpline(d[:,0], - d[:,1]+minval, - k=1, s=0.5) - - # Interpolate the spline for our bin array into a background for our histogram - bg = cj(bincenters) - bg[bg < minval] = minval # do not allow division by zero - - return bg - -def _bg_ksi3(bincenters): - """ returns ksi1 background - - Parameters - ---------- - bincenters : numpy array - centers of bins for ksi1 background - - Returns - ------- - bg : numpy array - background number fractions for ksi1 - - Notes - ----- - Background is determined by cubic spline interpolation from a large - numerical simulation containing 17156424 ksi values measured from - random orientations. The bin spacing in this histogram is 0.3 degrees. - - """ - import numpy as np - import scipy.interpolate as interp - - d = np.array([ [ 0.00000000e+00, 0.00000000e+00], - [ 1.50000000e-01, 2.02400000e+03], - [ 4.50000000e-01, 6.40000000e+03], - [ 7.50000000e-01, 1.04980000e+04], - [ 1.05000000e+00, 1.44530000e+04], - [ 1.35000000e+00, 1.87920000e+04], - [ 1.65000000e+00, 2.27030000e+04], - [ 1.95000000e+00, 2.67350000e+04], - [ 2.25000000e+00, 3.07190000e+04], - [ 2.55000000e+00, 3.50120000e+04], - [ 2.85000000e+00, 3.91140000e+04], - [ 3.15000000e+00, 4.28890000e+04], - [ 3.45000000e+00, 4.71630000e+04], - [ 3.75000000e+00, 5.13210000e+04], - [ 4.05000000e+00, 5.43970000e+04], - [ 4.35000000e+00, 5.88480000e+04], - [ 4.65000000e+00, 6.29620000e+04], - [ 4.95000000e+00, 6.75770000e+04], - [ 5.25000000e+00, 7.14270000e+04], - [ 5.55000000e+00, 7.46270000e+04], - [ 5.85000000e+00, 7.91680000e+04], - [ 6.15000000e+00, 8.24090000e+04], - [ 6.45000000e+00, 8.65320000e+04], - [ 6.75000000e+00, 9.06090000e+04], - [ 7.05000000e+00, 9.47120000e+04], - [ 7.35000000e+00, 9.79920000e+04], - [ 7.65000000e+00, 1.01590000e+05], - [ 7.95000000e+00, 1.05900000e+05], - [ 8.25000000e+00, 1.09020000e+05], - [ 8.55000000e+00, 1.12940000e+05], - [ 8.85000000e+00, 1.17490000e+05], - [ 9.15000000e+00, 1.20630000e+05], - [ 9.45000000e+00, 1.23850000e+05], - [ 9.75000000e+00, 1.27110000e+05], - [ 1.00500000e+01, 1.33170000e+05], - [ 1.03500000e+01, 1.35750000e+05], - [ 1.06500000e+01, 1.38000000e+05], - [ 1.09500000e+01, 1.42120000e+05], - [ 1.12500000e+01, 1.45870000e+05], - [ 1.15500000e+01, 1.49250000e+05], - [ 1.18500000e+01, 1.52450000e+05], - [ 1.21500000e+01, 1.56420000e+05], - [ 1.24500000e+01, 1.59460000e+05], - [ 1.27500000e+01, 1.63300000e+05], - [ 1.30500000e+01, 1.65540000e+05], - [ 1.33500000e+01, 1.68700000e+05], - [ 1.36500000e+01, 1.71120000e+05], - [ 1.39500000e+01, 1.75060000e+05], - [ 1.42500000e+01, 1.78070000e+05], - [ 1.45500000e+01, 1.80540000e+05], - [ 1.48500000e+01, 1.83390000e+05], - [ 1.51500000e+01, 1.85630000e+05], - [ 1.54500000e+01, 1.89110000e+05], - [ 1.57500000e+01, 1.91660000e+05], - [ 1.60500000e+01, 1.95150000e+05], - [ 1.63500000e+01, 1.97860000e+05], - [ 1.66500000e+01, 1.99910000e+05], - [ 1.69500000e+01, 2.03460000e+05], - [ 1.72500000e+01, 2.05680000e+05], - [ 1.75500000e+01, 2.08830000e+05], - [ 1.78500000e+01, 2.10760000e+05], - [ 1.81500000e+01, 2.12770000e+05], - [ 1.84500000e+01, 2.14450000e+05], - [ 1.87500000e+01, 2.17540000e+05], - [ 1.90500000e+01, 2.19990000e+05], - [ 1.93500000e+01, 2.22920000e+05], - [ 1.96500000e+01, 2.24490000e+05], - [ 1.99500000e+01, 2.25390000e+05], - [ 2.02500000e+01, 2.27330000e+05], - [ 2.05500000e+01, 2.29780000e+05], - [ 2.08500000e+01, 2.31760000e+05], - [ 2.11500000e+01, 2.34130000e+05], - [ 2.14500000e+01, 2.34930000e+05], - [ 2.17500000e+01, 2.36170000e+05], - [ 2.20500000e+01, 2.39060000e+05], - [ 2.23500000e+01, 2.39200000e+05], - [ 2.26500000e+01, 2.40280000e+05], - [ 2.29500000e+01, 2.41970000e+05], - [ 2.32500000e+01, 2.42410000e+05], - [ 2.35500000e+01, 2.44470000e+05], - [ 2.38500000e+01, 2.44630000e+05], - [ 2.41500000e+01, 2.45470000e+05], - [ 2.44500000e+01, 2.46940000e+05], - [ 2.47500000e+01, 2.46660000e+05], - [ 2.50500000e+01, 2.48020000e+05], - [ 2.53500000e+01, 2.49590000e+05], - [ 2.56500000e+01, 2.50060000e+05], - [ 2.59500000e+01, 2.49730000e+05], - [ 2.62500000e+01, 2.49300000e+05], - [ 2.65500000e+01, 2.49770000e+05], - [ 2.68500000e+01, 2.51230000e+05], - [ 2.71500000e+01, 2.50670000e+05], - [ 2.74500000e+01, 2.50110000e+05], - [ 2.77500000e+01, 2.53140000e+05], - [ 2.80500000e+01, 2.50100000e+05], - [ 2.83500000e+01, 2.49790000e+05], - [ 2.86500000e+01, 2.49210000e+05], - [ 2.89500000e+01, 2.48700000e+05], - [ 2.92500000e+01, 2.47540000e+05], - [ 2.95500000e+01, 2.47380000e+05], - [ 2.98500000e+01, 2.47910000e+05], - [ 3.01500000e+01, 1.88290000e+05], - [ 3.04500000e+01, 1.38640000e+05], - [ 3.07500000e+01, 1.07030000e+05], - [ 3.10500000e+01, 8.20110000e+04], - [ 3.13500000e+01, 5.92320000e+04], - [ 3.16500000e+01, 4.13020000e+04], - [ 3.19500000e+01, 2.75660000e+04], - [ 3.22500000e+01, 1.75590000e+04], - [ 3.25500000e+01, 9.84800000e+03], - [ 3.28500000e+01, 4.43200000e+03], - [ 3.31500000e+01, 1.59800000e+03], - [ 3.34500000e+01, 1.18000000e+02], - [ 3.37500000e+01, 0.00000000e+00], - [ 3.40500000e+01, 0.00000000e+00], - [ 3.43500000e+01, 0.00000000e+00], - [ 3.46500000e+01, 0.00000000e+00]]) - - minval = np.sqrt(np.spacing(np.float32(1))) - cj = interp.UnivariateSpline(d[:,0], - d[:,1]+minval, - k=1, s=0.5) - - # Interpolate the spline for our bin array into a background for our histogram - bg = cj(bincenters) - bg[bg < minval] = minval # do not allow division by zero - - return bg - -def ksi1hist(ksi1values, binwidth=0.5, return_everything=False): - """ histogram ksi1 data - - Parameters - ---------- - ksi1values : numpy array of floats - ksi1values, in degrees - - binwidth : numpy float (optional) - width of bins, in degrees - - return_everything : bool (optional) - flag for whether - - Returns - ------- - fcorr : numpy array - background-corrected ksi1 number fractions - - bm : numpy array - mean values for the histogram bins - - ba : (optional, with return_everything flag) numpy array - histogram bin edges - - fraw : (optional, with return_everything flag) numpy array - ksi1 number fractions before background correction - """ - import numpy as np - - # Histogram ksi data - ba = np.r_[0:35:binwidth] - nb = ba.size - bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) - fraw, tmp = np.histogram(ksi1values, ba, density=True) - fraw = fraw / np.sum(fraw) - - bg = _bg_ksi1(bm) - - # Correct the histogram for measurement - fcorr = fraw / bg - fcorr = fcorr / np.sum(fcorr) - - if return_everything == False: - return fcorr, bm - else: - return fcorr, bm, ba, fraw - -def ksi2hist(ksi2values, binwidth=0.5, return_everything=False): - """ histogram ksi2 data - - Parameters - ---------- - ksi1values : numpy array of floats - ksi1values, in degrees - - binwidth : numpy float (optional) - width of bins, in degrees - - return_everything : bool (optional) - flag for whether - - Returns - ------- - fcorr : numpy array - background-corrected ksi1 number fractions - - bm : numpy array - mean values for the histogram bins - - ba : (optional, with return_everything flag) numpy array - histogram bin edges - - fraw : (optional, with return_everything flag) numpy array - ksi1 number fractions before background correction - """ - import numpy as np - - # Histogram ksi data - ba = np.r_[0:35:binwidth] - nb = ba.size - bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) - fraw, tmp = np.histogram(ksi2values, ba, density=True) - fraw = fraw / np.sum(fraw) - - bg = _bg_ksi2(bm) - - # Correct the histogram for measurement - fcorr = fraw / bg - fcorr = fcorr / np.sum(fcorr) - - if return_everything == False: - return fcorr, bm - else: - return fcorr, bm, ba, fraw - -def ksi3hist(ksi3values, binwidth=0.5, return_everything=False): - """ histogram ksi3 data - - Parameters - ---------- - ksi3values : numpy array of floats - ksi1values, in degrees - - binwidth : numpy float (optional) - width of bins, in degrees - - return_everything : bool (optional) - flag for whether - - Returns - ------- - fcorr : numpy array - background-corrected ksi1 number fractions - - bm : numpy array - mean values for the histogram bins - - ba : (optional, with return_everything flag) numpy array - histogram bin edges - - fraw : (optional, with return_everything flag) numpy array - ksi1 number fractions before background correction - """ - import numpy as np - - # Histogram ksi data - ba = np.r_[0:35:binwidth] - nb = ba.size - bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) - fraw, tmp = np.histogram(ksi3values, ba, density=True) - fraw = fraw / np.sum(fraw) - - bg = _bg_ksi3(bm) - - # Correct the histogram for measurement - fcorr = fraw / bg - fcorr = fcorr / np.sum(fcorr) - - if return_everything == False: - return fcorr, bm - else: - return fcorr, bm, ba, fraw - -def fit_foldnorm(f, bm, return_info=False, accuracy=None, iterations=1000): - """ fit ksi histogram to folded normal distribution - - Parameters - ---------- - fcorr : co - - Returns - ------- - mu : float - folded normal fit location parameter - sig : float - folded normal fit shape parameter - rss : float - final residual sum of squares difference between the fit and - the histogram - info : tuple - (1) the number of iterations required for fitting - (2) the exit mode from the optimizer (see notes) - (3) message describing exit mode - - Notes - ----- - Minimization is performed by scipy.optimize.fmin_slsqp. Refer to - the documentation for this function for further details on exit - mode returns. - """ - import numpy as np - from scipy.special import erf - import scipy.optimize as spop - - if accuracy == None: # default to floating point accuracy - accuracy = np.spacing(np.float32(0)) - - # Estimate the distribution fitting parameters and then minimize the RMS - # Note that RMS error is linearly related to log likelihood, so we should get - # comparable results in both minimizations - m1 = np.dot(bm, f) # first moment - m2 = np.dot(bm**2.0, f) # second moment - m4 = np.dot(bm**4.0, f) # fourth moment - s = np.dot((bm-m1)**2.0, f) # standard deviation - x = np.arange(0.0, np.amax(bm)+np.amax(bm)/1000.0, np.amax(bm)/1000.0) - - # Elandt finds that the 1st and 2nd moments give a better approximation - # when m1/s is greater than about 1.35, and the 2nd and 4th moment - # method is better otherwise. - if m1/s > 1.35: # estimate parameters by 1st and 2nd moments - a = m1**2.0 / m2 # ratio of moments - def i0(x): # Eq 2 [2] - return 1.0 - 0.5 * (1.0 + erf(x / np.sqrt(2.0))) - def g(th, i0, a): - return (np.sqrt(2.0/np.pi) * np.exp(-0.5 * th**2.0) - \ - th * (1.0 - 2.0 * i0(-th)))**2.0 / (a * (1.0 + th**2.0)) - 1.0 - - # Find the value of G closest to zero, limiting iterations - # (fminbnd is not sufficiently robust in our case because G - # sometimes is close to zero but does not necessarily intersect the - # x axis in the limit of the half normal distribution). Assuming - # the function will only come close to zero once, then we will just - # search for the the solution to the equation iteratively - lb = 0.0 - ub = np.amax(x) - res = np.inf - nit = 0 - j = 0 - while (res > accuracy and - nit < 1000 and - (ub - lb) / 1000.0 > accuracy): - - # set a search range - qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) - - tmp = np.absolute(g(qtm, i0, a)) - resn = np.amin(tmp) # get value closest to zero in range - j = np.argmin(tmp) - - ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound - lb = qtm[np.amax([j - 1, 0])] # get new lower bound - nit += 1 # increment number of iterations counter - th=qtm[j] - del qtm, j, ub, lb, res, g, a, i0, nit, tmp - - else: # Estimate Parameters by second and fourth moments - - b = m4 / m2**2.0 # ratio of moments - - def h(q, b): # Eq. 22 [2] - return b * (1.0 + q**2.0)**2.0 - (3.0 + 6.0 * q**2.0 + q**4.0) - - # Find the value of h closest to zero, limiting iterations. - # Same method as above. - lb = 0.0 - ub = np.amax(x) - res = np.inf - nit = 0 - j = 0 - while (res > accuracy and - nit < 1000 and - np.absolute(ub-lb) / 1000.0 > accuracy): - - # set a search range - qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) - - tmp = np.absolute(h(qtm, b)) - resn = np.amin(tmp) # get value closest to zero in range - j = np.argmin(tmp) - if np.isreal(resn): - res = resn # in case min cannot be found - ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound - lb = qtm[np.amax([j - 1, 0])] # get new lower bound - nit += 1 # increment number of iterations counter - - th = qtm[j] - del qtm, j, ub, lb, res, h, b, nit, tmp - del m2, m4 - - # Compute initial guess values - sgi = np.sqrt((s**2.0 + m1**2.0) / (1.0 + th**2.0)) - mui = th * sgi - - # Define minimization function as a least squares problem - def ff(params): - import numpy as np - import scipy.stats as stats - fx = stats.foldnorm.pdf(bm, params[0], loc=0, scale=params[1]) - fx = fx/np.sum(fx) - return np.sum((f-fx)**2.0) - - # Perform constrained minimization - mub = (accuracy, np.inf) - sgb = (accuracy, np.inf) - out, rss, its, imode, smode = spop.fmin_slsqp(ff, x0=[mui, sgi], - bounds=[mub, sgb], - full_output=True, - iter=iterations, - acc=accuracy, - iprint=1) - - mu = out[0] - sig = out[1] - - if return_info==False: - return mu, sig - else: - return mu, sig, rss, (its, imode, smode) - -def foldnorm_mode(mu, sig, accuracy=None): - """ mode/peak of the folded normal distribution - - Parameters - ---------- - mu : float - location parameter - sig : float - shape parameter - - Returns - ------- - expected_value : float - E(Y) - - Notes - ----- - Equation comes from the documentation for the folded normal distribution in - the R statistics package. - http://hosho.ees.hokudai.ac.jp/~kubo/Rdoc/library/VGAM/html/fnormal1.html - """ - import scipy.stats as stats - import numpy as np - import scipy.optimize as spop - - if accuracy == None: # default to floating point accuracy - accuracy = np.spacing(np.float32(0)) - - fn = stats.foldnorm(mu, loc=0, scale=sig) - def chk(x): - val = -fn.pdf(x) - return val - - res = spop.minimize(chk, 0.0, method='nelder-mead', - options={'xtol': 1e-8, 'disp': False}) - - return res.x[0] - - -def fit_ksivals(ksi1vals, ksi2vals, ksi3vals, binwidth=0.5, makeplot=True, \ - accuracy=None): - - import numpy as np - import scipy.stats as stats - import matplotlib.pyplot as plt - - f1c, bm, ba, f1r = ksi1hist(ksi1vals, - binwidth, return_everything=True) - - f2c, bm, ba, f2r = ksi2hist(ksi2vals, - binwidth, return_everything=True) - - f3c, bm, ba, f3r = ksi3hist(ksi3vals, - binwidth, return_everything=True) - - mu1, sig1, rss1, dat1 = fit_foldnorm(f1c, bm, return_info=True, - accuracy=accuracy) - - mu2, sig2, rss2, dat2 = fit_foldnorm(f2c, bm, return_info=True, - accuracy=accuracy) - - mu3, sig3, rss3, dat3 = fit_foldnorm(f3c, bm, return_info=True, - accuracy=accuracy) - - print foldnorm_mode(mu1, sig1), foldnorm_mode(mu2, sig2), foldnorm_mode(mu3, sig3) # FIXME: make output clearer to the user - - if makeplot==False: - - return np.array([[mu1, sig1],[mu2, sig2],[mu3, sig3]]) - - else: - - # parameters we might want to adjust - xlimv = [0.0, 15.0] - ylimv = [0.0, 15.0] - xtickstep = 3 - ytickstep = 3 - bgdistclr = [0.8,0.8,0.8] - uncorrclr = [0.55, 0.55, 0.55] - ksi1faceclr = [1.0, 1.0, 1.0] - ksi2faceclr = [1.0, 1.0, 1.0] - ksi3faceclr = [1.0, 1.0, 1.0] - fontsz=12 - - xv = np.arange(0.0, np.amax(bm) + np.amax(bm) / 1000.0, - np.amax(bm) / 1000.0) - - # Create an awesome plot - fig = plt.figure(figsize=(15, 4), dpi=96) - fig.patch.set_facecolor('w') - - # ksi1 subplot - ax1 = fig.add_subplot(131) - f = stats.foldnorm.pdf(xv, mu1, scale=sig1) - f = xv.size*f/np.sum(f) - ax1.plot(xv, f, '-k', linewidth=1) - ax1.bar(bm-binwidth/2.0, f1r*bm.size, width=binwidth, - facecolor=uncorrclr, edgecolor=uncorrclr) - ax1.bar(bm-binwidth/2.0, f1c*bm.size, width=binwidth, - facecolor=ksi1faceclr, edgecolor=[0.0, 0.0, 0.0]) - bg = _bg_ksi1(xv) - bg = bg/np.sum(bg) - ax1.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) - ax1.text(0.97, 0.97, - r'$Mo_{\xi1}$ = '+foldnorm_mode(mu1, sig1).astype('|S5') - +r'$^\circ$'+'\n'+ - r'$\mu_{\xi1}$ = '+mu1.astype('|S5')+r'$^\circ$'+'\n'+ - r'$\sigma_{\xi1}$ = '+sig1.astype('|S5')+r'$^\circ$', - fontsize=fontsz, - bbox=dict(edgecolor='k', facecolor='w'), - transform = ax1.transAxes, - horizontalalignment='right', - verticalalignment='top') - ax1.set_xlim(xlimv) - ax1.set_ylim(ylimv) - ax1.set_xticks(np.arange(xlimv[0],xlimv[1]+xtickstep, xtickstep)) - ax1.set_yticks(np.arange(ylimv[0],ylimv[1]+ytickstep, ytickstep)) - - # ksi2 subplot - ax2 = fig.add_subplot(132) - f = stats.foldnorm.pdf(xv, mu2, scale=sig2) - f = xv.size*f/np.sum(f) - ax2.plot(xv, f, '-k', linewidth=1) - ax2.bar(bm-binwidth/2.0, f2r*bm.size, width=binwidth, - facecolor=uncorrclr, edgecolor=uncorrclr) - ax2.bar(bm-binwidth/2.0, f2c*bm.size, width=binwidth, - facecolor=ksi2faceclr, edgecolor=[0.0, 0.0, 0.0]) - bg = _bg_ksi2(xv) - bg = bg/np.sum(bg) - ax2.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) - ax2.text(0.03, 0.97, - r'$Mo_{\xi2}$ = '+foldnorm_mode(mu2, sig2).astype('|S5') - +r'$^\circ$'+'\n'+ - r'$\mu_{\xi1}$ = '+mu2.astype('|S5')+r'$^\circ$'+'\n'+ - r'$\sigma_{\xi1}$ = '+sig2.astype('|S5')+r'$^\circ$', - fontsize=fontsz, - bbox=dict(edgecolor='k', facecolor='w'), - transform = ax2.transAxes, - horizontalalignment='left', - verticalalignment='top') - ax2.set_xlim(xlimv) - ax2.set_ylim(ylimv) - ax2.set_xticks(np.arange(xlimv[0],xlimv[1]+xtickstep, xtickstep)) - ax2.set_yticks(np.arange(ylimv[0],ylimv[1]+ytickstep, ytickstep)) - - - # ksi2 subplot - ax3 = fig.add_subplot(133) - f = stats.foldnorm.pdf(xv, mu3, scale=sig3) - f = xv.size*f/np.sum(f) - ax3.plot(xv, f, '-k', linewidth=1) - ax3.bar(bm-binwidth/2.0, f3r*bm.size, width=binwidth, - facecolor=uncorrclr, edgecolor=uncorrclr) - ax3.bar(bm-binwidth/2.0, f3c*bm.size, width=binwidth, - facecolor=ksi3faceclr, edgecolor=[0.0, 0.0, 0.0]) - bg = _bg_ksi3(xv) - bg = bg/np.sum(bg) - ax3.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) - ax3.text(0.03, 0.97, - r'$Mo_{\xi3}$ = '+foldnorm_mode(mu3, sig3).astype('|S5') - +r'$^\circ$'+'\n'+ - r'$\mu_{\xi1}$ = '+mu3.astype('|S5')+r'$^\circ$'+'\n'+ - r'$\sigma_{\xi1}$ = '+sig3.astype('|S5')+r'$^\circ$', - fontsize=fontsz, - bbox=dict(edgecolor='k', facecolor='w'), - transform = ax3.transAxes, - horizontalalignment='left', - verticalalignment='top') - ax3.set_xlim(xlimv) - ax3.set_ylim(ylimv) - ax3.set_xticks(np.arange(xlimv[0],xlimv[1]+xtickstep, xtickstep)) - ax3.set_yticks(np.arange(ylimv[0],ylimv[1]+ytickstep, ytickstep)) - - - ax1.set_ylabel(r'Relative Number Fraction') - ax1.set_xlabel(r'$\xi_1$ ($^\circ$)', fontsize=fontsz*1.2) - ax2.set_xlabel(r'$\xi_2$ ($^\circ$)', fontsize=fontsz*1.2) - ax3.set_xlabel(r'$\xi_3$ ($^\circ$)', fontsize=fontsz*1.2) - fig.subplots_adjust(bottom=0.15) - plt.show() - - return np.array([[mu1, sig1],[mu2, sig2],[mu3, sig3]]), \ - fig, ax1, ax2, ax3 \ No newline at end of file diff --git a/rmpyc.py b/rmpyc.py deleted file mode 100644 index 83834d0..0000000 --- a/rmpyc.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Searches subdirectories of the current directory looking for .pyc files and -deletes them. -""" -import os -for dirpath, dirnames, filenames in os.walk(os.getcwd()): - for each_file in filenames: - if each_file.endswith('.pyc'): - if os.path.exists(os.path.join(dirpath, each_file)): - print each_file - os.remove(os.path.join(dirpath, each_file)) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..123cb36 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +from distutils.core import setup + +setup( + name='cryspy', + version='0.0.0', + author='E. J. Payton', + author_email='eric dot payton at us dot af dot mil', + packages=['cryspy'], + scripts=['bin/DeGraefHenry.py', + 'bin/TESTS.py', + 'bin/TUTORIAL.py'], + url='http://www.wpafb.af.mil/afrl/rx/', + license='LICENSE.txt', + description='Visualization and postprocessing of EBSD data.', + long_description=open('README.txt').read(), + install_requires=[ + 'numpy >= 1.0.4', + 'scipy == 0.6.0', + 'platform >= 1.0.7', + 'matplotlib', + 'os', + 'copy', + 'time', + 'sys', + 'h5py' + ]# 'visvis>= 1.8' + ) \ No newline at end of file diff --git a/start.py b/start.py new file mode 100755 index 0000000..75980b0 --- /dev/null +++ b/start.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from cryspy import * \ No newline at end of file From 6e242abf72a69f78cb30df18efeccf6ecf58d69e Mon Sep 17 00:00:00 2001 From: Eric Payton Date: Mon, 11 Aug 2025 07:13:35 -0400 Subject: [PATCH 2/4] Actually update to py3 + add empirics module --- cryspy/__init__.py | 15 +- cryspy/cio/__init__.py | 21 +- cryspy/cio/loadGFtype1.py | 151 -- cryspy/cio/loadang.py | 476 ++-- cryspy/cio/loadctf.py | 242 +- cryspy/cio/loadebsd.py | 45 +- cryspy/cio/loadosc.py | 3406 ++++++++++++++-------------- cryspy/cio/writeang.py | 122 +- cryspy/colormaps.py | 1317 +++++++++++ cryspy/ebsd.py | 1072 ++++----- cryspy/empirics.py | 1021 +++++++++ cryspy/orrl.py | 4397 ++++++++++--------------------------- cryspy/rot.py | 920 ++++---- cryspy/tx.py | 601 +++-- cryspy/util.py | 2427 ++++++++++++-------- cryspy/vis.py | 1443 ++++++------ cryspy/vis.zip | Bin 14957 -> 0 bytes cryspy/xtal.py | 461 ++-- 18 files changed, 9213 insertions(+), 8924 deletions(-) delete mode 100755 cryspy/cio/loadGFtype1.py create mode 100755 cryspy/colormaps.py create mode 100755 cryspy/empirics.py delete mode 100755 cryspy/vis.zip diff --git a/cryspy/__init__.py b/cryspy/__init__.py index 304d065..8db0ac5 100755 --- a/cryspy/__init__.py +++ b/cryspy/__init__.py @@ -1,7 +1,8 @@ -from cryspy.cio import * -from cryspy.rot import * -from cryspy.xtal import * -from cryspy.util import * -from cryspy.vis import * -from cryspy.ebsd import * -from cryspy.orrl import * +from cryspy3.cio import * +from cryspy3.rot import * +from cryspy3.xtal import * +from cryspy3.util import * +from cryspy3.vis import * +from cryspy3.ebsd import * +from cryspy3.orrl import * +from cryspy3.colormaps import * diff --git a/cryspy/cio/__init__.py b/cryspy/cio/__init__.py index 625a79b..3951da7 100755 --- a/cryspy/cio/__init__.py +++ b/cryspy/cio/__init__.py @@ -1,11 +1,10 @@ -''' -ovlib.cio: Data Import and Export -=============================================================================== -''' - -# import the submodules so their functions are directly available -from .loadosc import loadosc -from .loadang import loadang -from .loadctf import loadctf -from .loadebsd import loadebsd -from .loadGFtype1 import loadGFtype1, returnPhaseNames \ No newline at end of file +''' +cryspy3.io: Data Import and Export +=============================================================================== +''' + +# import the submodules so their functions are directly available +from .loadosc import loadosc +from .loadang import loadang +from .loadctf import loadctf +from .loadebsd import loadebsd \ No newline at end of file diff --git a/cryspy/cio/loadGFtype1.py b/cryspy/cio/loadGFtype1.py deleted file mode 100755 index c2671d9..0000000 --- a/cryspy/cio/loadGFtype1.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- - -def returnPhaseNames(filepath): - ''' - Returns the phase names within a TSL Grain File Type 1 - - Parameters - ---------- - filepath : str - path to Grain File type 1 - - Returns - ------- - phasenames : list - phase names - ''' - - # open the file and separate out the header from the data - dat=[]; - with open(filepath,'r') as f: - for line in f: - if line[0] != '#': - dat.append(line.strip()) - - phasenames = [] - for line in dat: - s = line.split() - name = ' '.join(s[10:]) - if name not in phasenames: - phasenames.append(name) - - return phasenames - - -def loadGFtype1(filepath, phasedict, notation='international'): - ''' - Parameters - ---------- - filepath : str - path to Grain File type 1 - - phasedict : dict - dictionary for interpreting phase results - - Returns - ------- - ebsd : obj of ebsd class - - Notes - ----- - TSL Grain File Type 1 includes most of the data needed to interpret an - EBSD scan, including the euler angles and xy locations for each point. - However, the phase is not listed as an integer. Rather, it is a string for - the phase name (which may contain spaces). This is impossible to interpret - without - ''' - - import numpy as np - from cryspy import ebsd - from cryspy import rot - from cryspy import xtal - import righthand as rh - - # open the file and separate out the header from the data - hdr=[];dat=[]; - with open(filepath,'r') as f: - for line in f: - if line[0]=='#': - hdr.append(line.strip()[1:].strip()) # remove #'s and whitespace - else: - dat.append(line.strip()) - - - # We should obtain the grid, the x step, the y step, the numbers of - # columns, and the number of rows from the x and y data themselves, - # because WE CAN... just in case the header was copied and pasted - # from a different file. Then compare to what the header says and - # print something if there is a discrepancy - - pb = rh.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') - # Parse the data into lists - - eul1=np.zeros(np.shape(dat)) - eul2=np.zeros(np.shape(dat)) - eul3=np.zeros(np.shape(dat)) - x=np.zeros(np.shape(dat)) - y=np.zeros(np.shape(dat)) - iq=np.zeros(np.shape(dat)) - ci=np.zeros(np.shape(dat)) - fit=np.zeros(np.shape(dat)) - gid=np.zeros(np.shape(dat)) - edge=np.zeros(np.shape(dat)) - phasetmp=[] - - ndex = 0 - for line in dat: - ls = line.split() - phasetmp.append(' '.join(ls[10:])) - - s = [float(item) for item in ls[0:10]] - - eul1[ndex] = s[0] - eul2[ndex] = s[1] - eul3[ndex] = s[2] - x[ndex] = s[3] - y[ndex] = s[4] - iq[ndex] = s[5] - ci[ndex] = s[6] - fit[ndex] = s[7] - gid[ndex] = s[8] - edge[ndex] = s[9] - - pb.update(ndex) - ndex += 1 - - numangcols = len(s) - - phasedict2 = {} - index = 0 - for item in [key for key in phasedict.keys()]: - phasedict2[item] = index - index += 1 - - s = np.zeros(np.shape(dat)) - phase=np.zeros(np.shape(dat)) - index = 0 - for item in phasetmp: - phase[index] = phasedict2[item] - s[index] = xtal.interpret_point_group_name(phasedict[item], notation=notation) - index += 1 - - # Start working with what we've interpreted now - o = xtal.orientation(quaternions = rot.quat.from_bunge(rot.bunge(eul1,eul2,eul3)),\ - pointgroupnumbers = np.squeeze(s.astype(np.int))) - pb.update(-1) - - ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are TSL-specific, and that two of - # the fields (fit and ...) have not been added yet. - ebsd_data.iq = iq - ebsd_data.ci = ci - - ebsd_data._anghdr = u''.join(['# {0}\n'.format(line) for line in (hdr)]) - ebsd_data._originalheaderdata = hdr - ebsd_data._oimversion_numangcols = numangcols - ebsd_data._original_phase = phase - 1 - - return ebsd_data diff --git a/cryspy/cio/loadang.py b/cryspy/cio/loadang.py index d867492..60e4adf 100755 --- a/cryspy/cio/loadang.py +++ b/cryspy/cio/loadang.py @@ -1,238 +1,238 @@ -# -*- coding: utf-8 -*- - -def loadang(filepath, alldata=False): - - # TODO: Add an interpreter of what version based on number of columns etc - - import numpy as np - from cryspy import ebsd - from cryspy import rot - from cryspy import xtal - import righthand as rh - - # open the file and separate out the header from the data - hdr=[];dat=[]; - with open(filepath,'r') as f: - for line in f: - if line[0]=='#': - hdr.append(line.strip()[1:].strip()) # remove #'s and whitespace - else: - dat.append(line.strip()) - - - # Find the locations of information to extract from header - phaseloc=[]; - for i,istr in enumerate(hdr): - if (istr.lower().find('phase') != -1): - phaseloc.append(i) - - # TODO: THE FOLLOWING ARE NOT YET NEEDED IN THE CRYSPY CODE, BUT - # ARE NEEDED FOR WRITING ANG FILES - - #nom='TEM_PIXperUM' - #loc=lower(str).find(nom) - #if (loc != -1): - # tem_pix_per_um=float(str[(loc+len(nom)):].strip()) - - #nom='x-star' - #loc=lower(str).find(nom) - #if (loc != -1): - # x_star=float(str[(loc+len(nom)):].strip()) - - #nom='y-star' - #loc=lower(str).find(nom) - #if (loc != -1): - # y_star=float(str[(loc+len(nom)):].strip()) - - #nom='z-star' - #loc=lower(str).find(nom) - #if (loc != -1): - # z_star=float(str[(loc+len(nom)):].strip()) - - #nom='workingdistance' - #loc=lower(str).find(nom) - #if (loc != -1): - # workingdistance=float(str[(loc+len(nom)):].strip()) - - #nom='grid:' - #loc=lower(str).find(nom) - #if (loc != -1): - # grid=str[(loc+len(nom)):].strip() - - #nom='xstep:' - #loc=lower(str).find(nom) - #if (loc != -1): - # xstp=float(str[(loc+len(nom)):].strip()) - - #nom='ystep:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ystp=float(str[(loc+len(nom)):].strip()) - - #nom='ncols_odd:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ncols_odd=float(str[(loc+len(nom)):].strip()) - - #nom='ncols_even:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ncols_even=float(str[(loc+len(nom)):].strip()) - - #nom='nrows:' - #loc=lower(str).find(nom) - #if (loc != -1): - # ncols_even=float(str[(loc+len(nom)):].strip()) - - #nom='operator:' - #loc=lower(str).find(nom) - #if (loc != -1): - # operator=str[(loc+len(nom)):].strip() - - #nom='sampleid:' - #loc=lower(str).find(nom) - #if (loc != -1): - # sampleid=str[(loc+len(nom)):].strip() - - #nom='scanid:' - #loc=lower(str).find(nom) - #if (loc != -1): - # scanid=str[(loc+len(nom)):].strip() - - - # Parse phase data - phaseidnums=[];materialname=[];formula=[];info=[];symmetry=[];latticeconstants=[]; - numberfamilies=[];famloc=[]; - for i in phaseloc: - # Get the phase number - nom='phase' - sstr=hdr[i];loc=sstr.lower().find(nom) - phaseidnums.append(float(sstr[loc+len(nom):].strip())) - - # Assume that the material name always follows - nom='materialname' - sstr=hdr[i+1];loc=sstr.lower().find(nom) - materialname.append(sstr[loc+len(nom):].strip()) - - # Assume that the formula is always next - nom='formula' - sstr=hdr[i+2];loc=sstr.lower().find(nom) - formula.append(sstr[loc+len(nom):].strip()) - - # Assume that the info is always after the formula... - nom='info' - sstr=hdr[i+3];loc=sstr.lower().find(nom) - info.append(sstr[loc+len(nom):].strip()) - - # ... and then the symmetry - nom='symmetry' - sstr=hdr[i+4];loc=sstr.lower().find(nom) - symmetry.append(sstr[loc+len(nom):].strip()) - - # ... and then the lattice constants - nom='latticeconstants' - sstr=hdr[i+5];loc=sstr.lower().find(nom) - latticeconstants.append(sstr[loc+len(nom):].strip()) - - # ... and then the number of families - nom='numberfamilies' - sstr=hdr[i+6];loc=sstr.lower().find(nom) - numberfamilies.append(float(sstr[loc+len(nom):].strip())) - - # store the line location of the beginning of the families - famloc.append(i+7) - - # We should obtain the grid, the x step, the y step, the numbers of - # columns, and the number of rows from the x and y data themselves, - # because WE CAN... just in case the header was copied and pasted - # from a different file. Then compare to what the header says and - # print something if there is a discrepancy - - pb = rh.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') - # Parse the data into lists - - eul1=np.zeros(np.shape(dat)) - eul2=np.zeros(np.shape(dat)) - eul3=np.zeros(np.shape(dat)) - x=np.zeros(np.shape(dat)) - y=np.zeros(np.shape(dat)) - iq=np.zeros(np.shape(dat)) - ci=np.zeros(np.shape(dat)) - phase=np.zeros(np.shape(dat)) - other = [] # XXX: other = np.array([]) # see comments below - - if alldata==True: - - ndex = 0 - for line in dat: - - s = [float(item) for item in line.split()] - - eul1[ndex] = s[0] - eul2[ndex] = s[1] - eul3[ndex] = s[2] - x[ndex] = s[3] - y[ndex] = s[4] - iq[ndex] = s[5] - ci[ndex] = s[6] - phase[ndex] = s[7] - pb.update(ndex) - ndex += 1 - - # Throw the other data into an "other" group of flexible size - for i in range(8, len(s)): - #other = np.hstack([other, np.array([np.float(s[i])])]).T # FIXME: This adds a LOT of time!!! There must be a faster way to do this. - other.append(s[i]) - - else: - - ndex = 0 - for line in dat: - - s = [float(item) for item in line.split()] - - eul1[ndex] = s[0] - eul2[ndex] = s[1] - eul3[ndex] = s[2] - x[ndex] = s[3] - y[ndex] = s[4] - iq[ndex] = s[5] - ci[ndex] = s[6] - phase[ndex] = s[7] - pb.update(ndex) - ndex += 1 - - numangcols = len(s) - - # Cast data lists into arrays - phase=np.array(phase)+1 # because tsl prints 0 for the phase when there is - # only one phase. But what about when there are - # more than one? - - s = np.zeros(np.shape(phase)) - index = 0 - for item in phaseidnums: - loc = phase == item - s[loc] = xtal.interpret_point_group_name(symmetry[index], 'tsl') - index += 1 - # Start working with what we've interpreted now - o = xtal.orientation(quaternions = rot.quat.from_bunge(rot.bunge(eul1,eul2,eul3)),\ - pointgroupnumbers = np.squeeze(s.astype(np.int))) - pb.update(-1) - - ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are TSL-specific, and that two of - # the fields (fit and ...) have not been added yet. - ebsd_data.iq = iq - ebsd_data.ci = ci - - ebsd_data._anghdr = u''.join(['# {0}\n'.format(line) for line in (hdr)]) - ebsd_data._originalheaderdata = hdr - ebsd_data._other = other - ebsd_data._oimversion_numangcols = numangcols - ebsd_data._original_phase = phase - 1 - - return ebsd_data +# -*- coding: utf-8 -*- + +def loadang(filepath, alldata=False): + + # TODO: Add an interpreter of what version based on number of columns etc + + import string + import numpy as np + from cryspy.ebsd import ebsd + from cryspy.rot import bunge, quat + from cryspy.xtal import orientation, interpret_point_group_name + from cryspy.util import progbar + + # open the file and separate out the header from the data + hdr=[];dat=[]; + with open(filepath,'r') as f: + for line in f: + if line[0]=='#': + hdr.append(line.strip()[1:].strip()) # remove #'s and whitespace + else: + dat.append(line.strip()) + + + # Find the locations of information to extract from header + phaseloc=[]; + for i,istr in enumerate(hdr): + if (string.lower(istr).find('phase') != -1): + phaseloc.append(i) + + # TODO: THE FOLLOWING ARE NOT YET NEEDED IN THE CRYSPY CODE, BUT + # ARE NEEDED FOR WRITING ANG FILES + + #nom='TEM_PIXperUM' + #loc=lower(str).find(nom) + #if (loc != -1): + # tem_pix_per_um=float(str[(loc+len(nom)):].strip()) + + #nom='x-star' + #loc=lower(str).find(nom) + #if (loc != -1): + # x_star=float(str[(loc+len(nom)):].strip()) + + #nom='y-star' + #loc=lower(str).find(nom) + #if (loc != -1): + # y_star=float(str[(loc+len(nom)):].strip()) + + #nom='z-star' + #loc=lower(str).find(nom) + #if (loc != -1): + # z_star=float(str[(loc+len(nom)):].strip()) + + #nom='workingdistance' + #loc=lower(str).find(nom) + #if (loc != -1): + # workingdistance=float(str[(loc+len(nom)):].strip()) + + #nom='grid:' + #loc=lower(str).find(nom) + #if (loc != -1): + # grid=str[(loc+len(nom)):].strip() + + #nom='xstep:' + #loc=lower(str).find(nom) + #if (loc != -1): + # xstp=float(str[(loc+len(nom)):].strip()) + + #nom='ystep:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ystp=float(str[(loc+len(nom)):].strip()) + + #nom='ncols_odd:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ncols_odd=float(str[(loc+len(nom)):].strip()) + + #nom='ncols_even:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ncols_even=float(str[(loc+len(nom)):].strip()) + + #nom='nrows:' + #loc=lower(str).find(nom) + #if (loc != -1): + # ncols_even=float(str[(loc+len(nom)):].strip()) + + #nom='operator:' + #loc=lower(str).find(nom) + #if (loc != -1): + # operator=str[(loc+len(nom)):].strip() + + #nom='sampleid:' + #loc=lower(str).find(nom) + #if (loc != -1): + # sampleid=str[(loc+len(nom)):].strip() + + #nom='scanid:' + #loc=lower(str).find(nom) + #if (loc != -1): + # scanid=str[(loc+len(nom)):].strip() + + + # Parse phase data + phaseidnums=[];materialname=[];formula=[];info=[];symmetry=[];latticeconstants=[]; + numberfamilies=[];famloc=[]; + for i in phaseloc: + # Get the phase number + nom='phase' + str=hdr[i];loc=string.lower(str).find(nom) + phaseidnums.append(float(str[loc+len(nom):].strip())) + + # Assume that the material name always follows + nom='materialname' + str=hdr[i+1];loc=string.lower(str).find(nom) + materialname.append(str[loc+len(nom):].strip()) + + # Assume that the formula is always next + nom='formula' + str=hdr[i+2];loc=string.lower(str).find(nom) + formula.append(str[loc+len(nom):].strip()) + + # Assume that the info is always after the formula... + nom='info' + str=hdr[i+3];loc=string.lower(str).find(nom) + info.append(str[loc+len(nom):].strip()) + + # ... and then the symmetry + nom='symmetry' + str=hdr[i+4];loc=string.lower(str).find(nom) + symmetry.append(str[loc+len(nom):].strip()) + + # ... and then the lattice constants + nom='latticeconstants' + str=hdr[i+5];loc=string.lower(str).find(nom) + latticeconstants.append(str[loc+len(nom):].strip()) + + # ... and then the number of families + nom='numberfamilies' + str=hdr[i+6];loc=string.lower(str).find(nom) + numberfamilies.append(float(str[loc+len(nom):].strip())) + + # store the line location of the beginning of the families + famloc.append(i+7) + + # We should obtain the grid, the x step, the y step, the numbers of + # columns, and the number of rows from the x and y data themselves, + # because WE CAN... just in case the header was copied and pasted + # from a different file. Then compare to what the header says and + # print something if there is a discrepancy + + pb = progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') + # Parse the data into lists + + eul1=np.zeros(np.shape(dat)) + eul2=np.zeros(np.shape(dat)) + eul3=np.zeros(np.shape(dat)) + x=np.zeros(np.shape(dat)) + y=np.zeros(np.shape(dat)) + iq=np.zeros(np.shape(dat)) + ci=np.zeros(np.shape(dat)) + phase=np.zeros(np.shape(dat)) + other = [] # XXX: other = np.array([]) # see comments below + + if alldata==True: + + ndex = 0 + for line in dat: + + s= map(float, line.split()) + + eul1[ndex] = s[0] + eul2[ndex] = s[1] + eul3[ndex] = s[2] + x[ndex] = s[3] + y[ndex] = s[4] + iq[ndex] = s[5] + ci[ndex] = s[6] + phase[ndex] = s[7] + pb.update(ndex) + ndex += 1 + + # Throw the other data into an "other" group of flexible size + for i in range(8, len(s)): + #other = np.hstack([other, np.array([np.float(s[i])])]).T # FIXME: This adds a LOT of time!!! There must be a faster way to do this. + other.append(s[i]) + + else: + + ndex = 0 + for line in dat: + + s= map(float, line.split()) + + eul1[ndex] = s[0] + eul2[ndex] = s[1] + eul3[ndex] = s[2] + x[ndex] = s[3] + y[ndex] = s[4] + iq[ndex] = s[5] + ci[ndex] = s[6] + phase[ndex] = s[7] + pb.update(ndex) + ndex += 1 + + numangcols = len(s) + + # Cast data lists into arrays + phase=np.array(phase)+1 # because tsl prints 0 for the phase when there is + # only one phase. But what about when there are + # more than one? + + s = np.zeros(np.shape(phase)) + index = 0 + for item in phaseidnums: + loc = phase == item + s[loc] = interpret_point_group_name(symmetry[index], 'tsl') + index += 1 + # Start working with what we've interpreted now + o = orientation(quaternions = quat.from_bunge(bunge(eul1,eul2,eul3)),\ + pointgroupnumbers = np.squeeze(s.astype(np.int))) + pb.update(-1) + + ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are TSL-specific, and that two of + # the fields (fit and ...) have not been added yet. + ebsd_data.iq = iq + ebsd_data.ci = ci + + ebsd_data._anghdr = u''.join(['# {0}\n'.format(line) for line in (hdr)]) + ebsd_data._originalheaderdata = hdr + ebsd_data._other = other + ebsd_data._oimversion_numangcols = numangcols + + return ebsd_data diff --git a/cryspy/cio/loadctf.py b/cryspy/cio/loadctf.py index b84a9ad..e164619 100755 --- a/cryspy/cio/loadctf.py +++ b/cryspy/cio/loadctf.py @@ -1,123 +1,121 @@ -# -*- coding: utf-8 -*- - -def loadctf(filepath): - - import numpy as np - import cryspy.ebsd as ebsd - import cryspy.rot as rot - import righthand as rh - import cryspy.xtal as xtal - - # open the file and separate out the header from the data - hdr=[]; dat=[]; hdr_flag = True - with open(filepath,'r') as f: - for line in f: - ll = line.strip() - if hdr_flag: - hdr.append(ll) - if not hdr_flag: - dat.append(line) - if ''.join(ll[0:9].lower().split())=='phasexy': # assume users will - # never call their - # data 'phasexy' - hdr_flag = False - - # Find the locations of information to extract from header - phaseloc=[]; - for i,str in enumerate(hdr): - if (str.lower().find('phases\t') != -1): - phaseloc.append(i) - phaseloc = phaseloc[-1] # assume the last usage is the correct one, in case - # the user named their project something including - #'phases\t' - - # Parse phase data - phaseidnums=[]; materialname=[]; symmetry=[];#formula=[];info=[]; - latticeconstants=[]; #numberfamilies=[];famloc=[]; - for i in range(phaseloc+1, np.shape(hdr)[0]-1): - - strdat = hdr[i].split() - - # get the lattice constants - latticeconstants.append((strdat[0].split(';')+strdat[1].split(';'))) - - # Get the phase number - phaseidnums.append(i-phaseloc) - - # Assume that the material name always follows - materialname.append(strdat[2]) - - # Symmetry - symmetry.append( - xtal.point_group_number_from_space_group_number(\ - int(strdat[4].strip()))) - - - pb = rh.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') - - # Parse the data into lists - shdat = np.shape(dat) - eul1=np.zeros(shdat) - eul2=np.zeros(shdat) - eul3=np.zeros(shdat) - x=np.zeros(shdat) - y=np.zeros(shdat) - bc=np.zeros(shdat) - bs=np.zeros(shdat) - nbands=np.zeros(shdat) - error=np.zeros(shdat) - mad=np.zeros(shdat) - phase=np.zeros(shdat) - - ndex = 0 - for line in dat: - - s = [float(item) for item in line.split()] - - phase[ndex] = s[ 0] - 1 - x[ndex] = s[ 1] - y[ndex] = s[ 2] - nbands[ndex] = s[ 3] - error[ndex] = s[ 4] - eul1[ndex] = s[ 5] * np.pi/180.0 - eul2[ndex] = s[ 6] * np.pi/180.0 - eul3[ndex] = s[ 7] * np.pi/180.0 - mad[ndex] = s[ 8] - bc[ndex] = s[ 9] - bs[ndex] = s[10] - pb.update(ndex) - ndex += 1 - - # Cast data lists into arrays - phase=np.array(phase)+1 # because 0 printed for the phase when there is - # only one phase. but what about when there are - # more than one? EJP! Needs check. - - s = np.zeros(np.shape(phase)) - index = 0 - for item in phaseidnums: - loc = phase == item - s[loc] = symmetry[index] - index += 1 - #return s, eul1, eul2, eul3 - # Start working with what we've interpreted now - o = xtal.orientation(quaternions = rot.quat.from_bunge( - rot.bunge(eul1,eul2,eul3)), - pointgroupnumbers = np.atleast_1d(s.astype(np.int))) - pb.update(-1) - - ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are CTF-specific. - ebsd_data.bc = bc - ebsd_data.bs = bs - ebsd_data.mad = mad - ebsd_data.error = error - ebsd_data.nbands = nbands - ebsd_data._original_header = hdr - ebsd_data._original_phase = phase - 1 - ebsd_data._original_euler = np.vstack([eul1, eul2, eul3]).T - +# -*- coding: utf-8 -*- + +def loadctf(filepath): + + import numpy as np + import cryspy.ebsd as ebsd + import cryspy.rot as rot + import cryspy.util as util + import cryspy.xtal as xtal + + # open the file and separate out the header from the data + hdr=[]; dat=[]; hdr_flag = True + with open(filepath,'r') as f: + for line in f: + ll = line.strip() + if hdr_flag: + hdr.append(ll) + if not hdr_flag: + dat.append(line) + if ''.join(ll[0:9].lower().split())=='phasexy': # assume users will + # never call their + # data 'phasexy' + hdr_flag = False + + # Find the locations of information to extract from header + phaseloc=[]; + for i,str in enumerate(hdr): + if (str.lower().find('phases\t') != -1): + phaseloc.append(i) + phaseloc = phaseloc[-1] # assume the last usage is the correct one, in case + # the user named their project something including + #'phases\t' + + # Parse phase data + phaseidnums=[]; materialname=[]; symmetry=[];#formula=[];info=[]; + latticeconstants=[]; #numberfamilies=[];famloc=[]; + for i in range(phaseloc+1, np.shape(hdr)[0]-1): + + strdat = hdr[i].split() + + # get the lattice constants + latticeconstants.append((strdat[0].split(';')+strdat[1].split(';'))) + + # Get the phase number + phaseidnums.append(i-phaseloc) + + # Assume that the material name always follows + materialname.append(strdat[2]) + + # Symmetry + symmetry.append( + xtal.point_group_number_from_space_group_number(\ + int(strdat[4].strip()))) + + + pb = util.progbar(finalcount=np.size(dat), message='LOADING EBSD DATA') + + # Parse the data into lists + shdat = np.shape(dat) + eul1=np.zeros(shdat) + eul2=np.zeros(shdat) + eul3=np.zeros(shdat) + x=np.zeros(shdat) + y=np.zeros(shdat) + bc=np.zeros(shdat) + bs=np.zeros(shdat) + nbands=np.zeros(shdat) + error=np.zeros(shdat) + mad=np.zeros(shdat) + phase=np.zeros(shdat) + + ndex = 0 + for line in dat: + + s= map(float, line.split()) + + phase[ndex] = s[ 0] + x[ndex] = s[ 1] + y[ndex] = s[ 2] + nbands[ndex] = s[ 3] + error[ndex] = s[ 4] + eul1[ndex] = s[ 5] + eul2[ndex] = s[ 6] + eul3[ndex] = s[ 7] + mad[ndex] = s[ 8] + bc[ndex] = s[ 9] + bs[ndex] = s[10] + pb.update(ndex) + ndex += 1 + + # Cast data lists into arrays + phase=np.array(phase)+1 # because 0 printed for the phase when there is + # only one phase. but what about when there are + # more than one? EJP! Needs check. + + s = np.zeros(np.shape(phase)) + index = 0 + for item in phaseidnums: + loc = phase == item + s[loc] = symmetry[index] + index += 1 + #return s, eul1, eul2, eul3 + # Start working with what we've interpreted now + o = xtal.orientation(quaternions = rot.quat.from_bunge( + rot.bunge(eul1,eul2,eul3)), + pointgroupnumbers = np.atleast_1d(s.astype(np.int))) + pb.update(-1) + + ebsd_data = ebsd(orientations=o, x=x, y=y, phaseid=phase) + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are CTF-specific. + ebsd_data.bc = bc + ebsd_data.bs = bs + ebsd_data.mad = mad + ebsd_data.error = error + ebsd_data.nbands = nbands + ebsd_data._original_header = hdr + return ebsd_data \ No newline at end of file diff --git a/cryspy/cio/loadebsd.py b/cryspy/cio/loadebsd.py index ea87bea..ecbcb4f 100755 --- a/cryspy/cio/loadebsd.py +++ b/cryspy/cio/loadebsd.py @@ -1,22 +1,25 @@ -# -*- coding: utf-8 -*- - -def loadebsd(filepath): - - from cryspy import cio - ''' - Load EBSD data based on filename extension. - ''' - test = filepath.split('.')[-1] - if test == 'ctf': - ovdat = cio.loadctf(filepath) - elif test == 'ang': - ovdat = cio.loadang(filepath) - elif test == 'osc': - ovdat = cio.loadosc(filepath, create_ebsd_object = True, \ - ang_output = False, ang_output_filename = None) - else: - 'EBSD data can be read in ctf, ang, and osc formats.' - - return ovdat - +# -*- coding: utf-8 -*- + + +def loadebsd(filepath): + + ''' + Load EBSD data based on filename extension. + ''' + test = filepath.split('.')[-1] + if test == 'ctf': + from cryspy.io import loadctf + ovdat = loadctf(filepath) + elif test == 'ang': + from cryspy.io import loadang + ovdat = loadang(filepath) + elif test == 'osc': + from cryspy.io import loadosc + ovdat = loadosc(filepath, create_ebsd_object = True, \ + ang_output = False, ang_output_filename = None) + else: + 'EBSD data can be read in ctf, ang, and osc formats.' + + return ovdat + \ No newline at end of file diff --git a/cryspy/cio/loadosc.py b/cryspy/cio/loadosc.py index c63d680..cdc6a93 100755 --- a/cryspy/cio/loadosc.py +++ b/cryspy/cio/loadosc.py @@ -1,1703 +1,1703 @@ -# -*- coding: utf-8 -*- - - -#filename = '8816-1_01_center.osc' -#fnom = '8816-1.osc' -#fnom = '8816-1_0_prevScan.osc' -#fnom = '8816-1_02_nearEdge.osc' - - -def loadosc(filename, create_ebsd_object = True, ang_output = False, \ - ang_output_filename = None): - """ loads osc files and converts to ang format, if desired - - This Python code performs the OSC to ANG conversion approximately 25% faster - than the previous script in Matlab [#TODO: add reference to matlab], with - some corrections in output and a potential improvement in memory usage - for opening very large OSC files. - - NOTES - ----- - - There are at least two Matlab versions of this floating around. One is from - Adam Shiveley (AFRL/RXCM) and the other is in the MTEX Matlab Toolbox. The - MTEX version is a partially rewritten version of Shiveley's by Florian - Bachmann. I was unable to get it to work in my testing. One problem is was that - the buffer size was set too small. - - Adam Shiveley's version from 9 Feb 2012 for Matlab is still in use at AFRL as - of 21 June 2016. My translation includes all of his original code as comments. - All Matlab code will be indicated by a % sign at the beginning of the comment - line. - - NOTES SPECIFIC TO THIS PYTHON VERSION: - - 1. HKL_COL_4 for first phase is wrong - 2. TEM_PX_PER_MICRON not yet correctly located in data file. We put in a dummy - when we write to ang. - 3. Not yet tested for more than 2 phases. - 4. We don't locate the elastic constants or "categories" in the osc file. - ANG reading works without the elastic constants, and we put in a dummy - for the categories. - 5. Some items that appear as zeroes to 5 or so decimal places appear as very - small floats ~1E-38 when I import them. - - PERFORMANCE TESTS: - A. 8234 kB OSC file: 3.75 s Python version; 5.05 s Matlab version - (~25% improvement) - B. 277635 kB OSC file: 98.9 s Python version; 132.8 s Matlab version - (~25% improvement) - - #------------------------------------------------------------------------------ - ADAM SHIVELEY'S ORIGINAL HEADER: - %function TSL_version = Decode_OSC_Header(OscFile, fileWriteID) - % Decode_Osc_Header(OscFile, fileWriteID) - % - % Inputs: OscFile is a string containing the path and file name of the OSC - % file to decode - % fileWriteID is an integer resulting from fileWriteID = - fopen('output.ang','w'); - % - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - % coded by: Adam Shiveley 9 Feb 12, AFRL/RXCM - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - %The OSC file is basically structured like this: - %Phase 1 name - %Phase 1 symmetry - %Phase 1 LatticeConstants - %Phase 1 Number of Families - %Phase 1 hklFamilies (read in every third one) - %Phase 1 Formula name - %This repeats for each phase in the scan - %sounds simple, right? Wrong the values are stored in decimal - %because of the way I choose to read the file in. - %This means data(10) = 70 which is actually the letter - %F as explained in this example: - %This is in decimal!!!! - %Example: data(10) = 70 - %char(data(10)) = F Look at ASCII table, Dec 70 = F - %This is where the fun begins. Most of the header information - %can be extracted directly using the decimal values and running - %the char command, however, the Euler angles must be extracted - %and converted into columns of length 4 then typecasted to a single - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - """ - - from cryspy.ebsd import ebsd - from cryspy.rot import bunge, quat - from cryspy.xtal import orientation, interpret_point_group_name - import struct - import numpy as np - import mmap - import time - - def _unpack_from_mmap(mmap_id, location_start): - - mmap_id.seek(location_start) - check = mmap_id.read_byte() - mmap_id.seek(location_start) # go back - counter = 0 - - if check != '\x00': - - temp_variable = '\x01' - - while temp_variable != 0: - - temp_variable = mmap_id.read_byte() - counter += 1 - - counter -= 1 - - mmap_id.seek(location_start) - result = mmap_id.read(counter) - - else: - - result = "" - - return result - - - #------------------------------------------------------------------------------ - - # So we can measure the time it takes to read the data - t = time.clock() - - """ - % %Now let's get the handle to open the scan file - % fid=fopen(OscFile); - % - % %This is in decimal!!!! - % %Example: data(10) = 70 - % %char(data(10)) = F Look at ASCII table, Dec 70 = F - % data = transpose( fread( fid, '*bit8' ) ); - - - ------------------------------------------ - - We have a couple options for opening the file in Python, e.g.: - # with open(fnom, mode='rb') as file: # b is important -> binary - # bindat = file.read() - - I have chosen to proceed with memory mapping rather than reading the whole file - into memory because it's supposed to offer benefits for large files. - - My translation of Adam Shiveley's original code takes advantage of this for - some functions but memory management could still use future improvement. - - Shiveley reads in the entire binary file as 8 bit int and then casts the - integers into other data types. I am generally trying to selectively read the - portions we need as binary and unpack them as the correct data type. - """ - - with open(filename, "r+b") as f: - # memory-map the file, size 0 means whole file - mm = mmap.mmap(f.fileno(), 0) - - """ The expected locations of header information in the data array""" - info_location_start = 72 - operator_location_start = 1095 - sample_ID_location_start = 1350 - scan_ID_location_start = 1605 - calibration_location_start = 1860 - - """ - % %Let's locate if there is any info the user typed in - % %There might be an issue here depending on how long the user types the - % %comments. Will need further testing to see if it breaks the hard-coded - % %index locations of the SampleID, Operator, and ScanID - % if(data(73) ~= 0) - % - % info_temp_variable = 1; - % info_counter = 73; - % - % while info_temp_variable ~= 0 - % - % info_temp_variable = data(info_counter); - % info_counter = info_counter +1; - % - % end - % - % %Convert the data into characters - % info = char(data(73:info_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear info_temp - % - % else - % - % info = ''; - % - % end - % - % %Now let's parse out the operator name - % if(data(1096) ~= 0) - % - % operator_temp_variable = 1; - % operator_counter = 1096; - % - % while operator_temp_variable ~= 0 - % - % operator_temp_variable = data(operator_counter); - % operator_counter = operator_counter +1; - % - % end - % - % %Convert the data into characters - % operator = char(data(1096:operator_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear operator_temp - % - % else - % - % operator = ' '; - % - % end - % - % - % %Now let's parse out the Sample ID. The index location could change if - % %a long info string is typed in. Seems to not change on the test I have - % %ran - % if(data(1351) ~= 0) - % - % sample_ID_temp_variable = 1; - % sample_ID_counter = 1351; - % - % while sample_ID_temp_variable ~= 0 - % - % sample_ID_temp_variable = data(sample_ID_counter); - % sample_ID_counter = sample_ID_counter +1; - % - % end - % - % %Convert the data into characters - % Sample_ID = char(data(1351:sample_ID_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear sample_ID_temp - % - % - % else - % - % Sample_ID = ' '; - % - % end - % - % %Now let's parse out the Scan ID. Again, the index could change if the - % %info is really long - % if(data(1606) ~= 0) - % - % scan_ID_temp_variable = 1; - % scan_ID_counter = 1606; - % - % while scan_ID_temp_variable ~= 0 - % - % scan_ID_temp_variable = data(scan_ID_counter); - % scan_ID_counter = scan_ID_counter +1; - % - % end - % - % %Convert the data into characters - % Scan_ID = char(data(1606:scan_ID_counter-1)); - % - % %Clear the vareiable to free up some memory - % clear scan_ID_temp - % - % else - % - % Scan_ID =' '; - % - % end - - """ - info = _unpack_from_mmap(mm, info_location_start) # FIXME: write this info data somewhere to be used for writing ang files - operator_name = _unpack_from_mmap(mm, operator_location_start) - sample_ID = _unpack_from_mmap(mm, sample_ID_location_start) - scan_ID = _unpack_from_mmap(mm, scan_ID_location_start) - - """ - % Now let's check the file for the TEM_PIXperUm, x-star, y-star, z-star - % and working distance - - % Not sure where the TEM_PIXperUM is located because I don't have the TEM - % version, so I'll just hard code it for now - """ - TEM_PIXperUM = 1 - - """ - % %This extracts the calibration info - % calibration_encoded = data(1861:1876); - % - % %Now we need to reshape the calibration info into groups of 4 - % calibration_reshaped = reshape(calibration_encoded, - [4 length(calibration_encoded)/4]); - % - % %Let's typecast the data into a single representation - % calibration_decoded = typecast(calibration_reshaped(:), 'single'); - % - % %Here's the final calibration info - % x_star = calibration_decoded(1); - % - % y_star = calibration_decoded(2); - % - % z_star = calibration_decoded(3); - % - % working_distance = calibration_decoded(4); - """ - mm.seek(calibration_location_start) - calibration_encoded = mm.read(16) - calibration_decoded = struct.unpack('<4f', calibration_encoded) - del calibration_encoded - - x_star = calibration_decoded[0] - y_star = calibration_decoded[1] - z_star = calibration_decoded[2] - working_distance = calibration_decoded[3] - del calibration_decoded - - """ - % %Find the starting location for phase 1. This never seems to change - % %so far it stays constant, but I would like to find a more secure way - % %of finding the 1st phase. Seems like the command: - % %strfind(data, [-71 11 -17 -1 1]) would work... - % %maybe something like: - % start_phase_location = strfind(data, [-71 11 -17 -1 1 0 0 0]); - % phase_one_temp = data(start_phase_location:end); - % begin_phase_one = strfind(phase_one_temp, [0 0 0]); - % start_phase_1 = begin_phase_one(1) + 3 + start_phase_location(1); - % EJP! same as start_phase_location + 8??? - % phase_1_data = data(start_phase_1(1):end); - % EJP! this and the previous several lines are - a complicated way of getting to the end of - the -71 11 ... 0 0 0 match string! - % %something like that might work, but it needs further testing to verify - % - % %%%%%%%%%%%%%%%%%%%%%%%%%% - % %Added 3 Feb 2012 - % %This will locate the begining of the actual phase - % - % %Alright, so it now appears that the number after -128 43 is the number - % %of phases in the sample. - % begin_phase_1_check = strfind(phase_1_data, [-128 63]); - % - % %get the number of phases from the scan - % number_of_phases = phase_1_data(begin_phase_1_check(1)+2); - % - % %This will extract the begining of the acutal phase to the end - % phase_1_data = phase_1_data(begin_phase_1_check(1)+3:end); - % - % %So, now we need to ignore all the zeros until we hit the actual phase - % %name - % phase_1_zero_check = 0; - % phase_1_zero_check_counter = 1; - % - % %locate the begining of the actual phase 1 name - % %The way this loop is structured, you have to subtract 2 from the final - % %number in phase_1_zero_check_counter. Since the loop starts at 1 and - % %increments after it gets the current position, this will add 2 extra - % %iteration counts, 1 because it starts at 1 and 1 because it will - % %increment after the non-zero number is found - % while phase_1_zero_check == 0 - % - % phase_1_zero_check = phase_1_data(phase_1_zero_check_counter); - % phase_1_zero_check_counter = phase_1_zero_check_counter + 1; - % - % end - % %%%%%%%%%%%%%%%%%%%%%%%%%%% - % - % - % %Find the ending location for phase 1 search reigon - % %This is used until I write a code to begin searching - % %at a given offset. Then you can just say: - % %end_phase_1 = strfind(data(1945), 0); - % %This would mean begin searching data from position 1945 - % %until you find a "0" - % end_phase_1 = strfind(phase_1_data, [110 0 0]); - % % end_phase_1 = strfind(phase_1_data(begin_phase_1_check+3+ ... - % % phase_1_zero_check_counter), 0); - % - % - % if length(end_phase_1) > 1 - % - % end_phase_1 = end_phase_1(1); - % - % end - % - % %Extract phase 1 from the file using the 1989 index plus the ending index - % %since we searched for the ending_phase in an array starting at 1945 from - % %the original data set. The -1 here is because since we start at 1989 - % %we calculated everything else according to starting at 0. - % phase_1_location = data(start_phase_1-1+begin_phase_1_check(1) + 3 + ... - % phase_1_zero_check_counter-2:start_phase_1+end_phase_1); - % - % %new let's clear the phase_1_data array - % %clear phase_1_data - % - % %Find the end location of phase 1 - % end_phase_1_temp = strfind(phase_1_location, 0); - % - % %Convert the decimal values into characters using ASCII - % final_phase_1 = char(phase_1_location(1:end_phase_1_temp-1)); - """ - - # First Phase -- There must be at least one! - # It appears that each value is padded by a byte. - start_phase_location = mm.find(struct.pack(\ - '<8B', 185, 11, 239, 255, 1, 0, 0, 0)) - - tmpini = start_phase_location + 16 - end_phase_1 = mm[tmpini:].find(struct.pack('<3b', 110, 0, 0)) + 4 - - tmpini = start_phase_location+16 - tmpfin = start_phase_location+16+end_phase_1-3 - temp = mm[tmpini:tmpfin].split(b'\x00') - number_of_phases = struct.unpack('2d} {1: >2d} {2: >2d} '+\ - '{3:d} {4:.6f} {5:d}\n' - for i in np.arange(0, num_hkl_families_out[phase_num-1]): - fo.write(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ - hkl4[i], hkl5[i], hklfin[i])) - - # Shiveley puts this only for v6, but it seems to also be output - # for v7. We will write dummy numbers for now. - if start_phase_location == 1988 or start_phase_location == 1992: - for i in np.arange(0, 6): - fo.write(u'# ElasticConstants \t-1.000000 -1.000000 \ - -1.000000 -1.000000 -1.000000 -1.000000\n') - - # Writing dummy numbers... - fo.write(u'# Categories0 0 0 0 0 \n') - fo.write(u'#\n') - - fo.write(u'# GRID: {0}\n'.format(grid)) - fo.write(u'# XSTEP: {0:.6f}\n'.format(xstep)) - fo.write(u'# YSTEP: {0:.6f}\n'.format(ystep)) - fo.write(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) - fo.write(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) - fo.write(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) - fo.write(u'#\n') - fo.write(u'# OPERATOR: \t{0}\n'.format(operator_name)) - fo.write(u'#\n') - fo.write(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) - fo.write(u'#\n') - fo.write(u'# SCANID: \t{0}\n'.format(scan_ID)) - fo.write(u'#\n') - - if start_phase_location == 1992: # TSL OIM v7 - for i in np.arange(0, data_length): - fmt_str =u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+\ - u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} '+ \ - u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} '+\ - u'{12: >9.6f} {13: >9.6f} \n' - p = out_data[i, :] - fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ - p[6], p[7].astype(np.int), \ - p[8].astype(np.int), p[9], p[10], \ - p[11], p[12], p[13])) - elif start_phase_location == 1988 or start_phase_location == 1928: # TSL OIM v5.31 thru 6 - for i in np.arange(0, data_length): - fmt_str = u'{0: > 9.5f} {1: > 9.5f} {2: > 9.5f} {3: > 12.5f} \ - {4: > 12.5f} {5: > 8.1f} {6: > 6.3f} {7: > 2d} \ - {8: > 6d} {9: > 6.3f} \n' - p = out_data[i, :] - fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ - p[6], p[7].astype(np.int), \ - p[8].astype(np.int), p[9])) - - # NOW, create an ebsd object if the user wants one - if create_ebsd_object == True: - - phase = out_data[:,7].astype(np.int) - phaseidnums = np.arange(np.shape(phase_out)[0], 0, -1) - 1 - - s = np.zeros(np.shape(phase)) - index = np.amax(phaseidnums) - for item in phaseidnums: - loc = phase == item - s[loc] = interpret_point_group_name(np.str(phase_symmetry_out[index]),'tsl') - index -= 1 - - # Start working with what we've interpreted now - o = orientation(quaternions = quat.from_bunge(bunge(out_data[:,0], out_data[:,1], out_data[:,2])),\ - pointgroupnumbers = np.squeeze(s.astype(np.int))) - - ebsd_data = ebsd(orientations=o, x=out_data[:,3], y=out_data[:,4], phaseid=phase) # FIXME: need eventually to fix confusing variable naming here - - ebsd_data.calc_scanstepsize() - ebsd_data.prepare_for_plotting() - - # Add other fields. Note that some fields are TSL-specific, and that two of - # the fields (fit and ...) have not been added yet. - ebsd_data.iq = out_data[:,5] - ebsd_data.ci = out_data[:,6] - - # Write header stuff - hdr = [] - hdr.append(u'# TEM_PIXperUM {0:.6f}\n'.format(TEM_PIXperUM)) - hdr.append(u'# x-star {0:.6f}\n'.format(x_star)) - hdr.append(u'# y-star {0:.6f}\n'.format(y_star)) - hdr.append(u'# z-star {0:.6f}\n'.format(z_star)) - hdr.append(u'# WorkingDistance {0:.6f}\n'.format(working_distance)) - hdr.append(u'#\n') - - for phase_num in np.arange(number_of_phases, 0, -1): - hdr.append(u'# Phase {0:d}\n'.format(phase_num)) - hdr.append(u'# MaterialName \t{0}\n'.format(phase_out[phase_num-1])) - hdr.append(u'# Formula \t{0}\n'.format(formula_out[phase_num-1])) - hdr.append(u'# Info \t\t\n') - hdr.append(u'# Symmetry {0:d}\n'.format(\ - phase_symmetry_out[phase_num-1])) - hdr.append(u'# LatticeConstants \ - {0:5.3f} {1:5.3f} {2:5.3f} {3:7.3f} {4:7.3f} {5:7.3f}\n'.format(\ - lattice_constants_out[phase_num-1][0], \ - lattice_constants_out[phase_num-1][1], \ - lattice_constants_out[phase_num-1][2], \ - lattice_constants_out[phase_num-1][3], \ - lattice_constants_out[phase_num-1][4], \ - lattice_constants_out[phase_num-1][5])) - hdr.append(u'# NumberFamilies {0}\n'.format(\ - num_hkl_families_out[phase_num-1])) - - hklfam = hkl_families_out[phase_num-1] - hkl4 = hkl_4_col_out[phase_num-1] - hkl5 = hkl_5_col_out[phase_num-1] - hklfin = hkl_last_col_out[phase_num-1] - tmpstr = u'# hklFamilies \t{0: >2d} {1: >2d} {2: >2d} '+\ - '{3:d} {4:.6f} {5:d}\n' - for i in np.arange(0, num_hkl_families_out[phase_num-1]): - hdr.append(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ - hkl4[i], hkl5[i], hklfin[i])) - - # Shiveley puts this only for v6, but it seems to also be output - # for v7. We will write dummy numbers for now. - if start_phase_location == 1988 or start_phase_location == 1992: - for i in np.arange(0, 6): - hdr.append(u'# ElasticConstants \t-1.000000 -1.000000 \ - -1.000000 -1.000000 -1.000000 -1.000000\n') - - # Writing dummy numbers... - hdr.append(u'# Categories0 0 0 0 0 \n') - hdr.append(u'#\n') - - hdr.append(u'# GRID: {0}\n'.format(grid)) - hdr.append(u'# XSTEP: {0:.6f}\n'.format(xstep)) - hdr.append(u'# YSTEP: {0:.6f}\n'.format(ystep)) - hdr.append(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) - hdr.append(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) - hdr.append(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) - hdr.append(u'#\n') - hdr.append(u'# OPERATOR: \t{0}\n'.format(operator_name)) - hdr.append(u'#\n') - hdr.append(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) - hdr.append(u'#\n') - hdr.append(u'# SCANID: \t{0}\n'.format(scan_ID)) - hdr.append(u'#\n') - ebsd_data._anghdr = "".join(hdr) - - return ebsd_data - - # This is it. - print('Elapsed time = {0}'.format(time.clock() -t)) \ No newline at end of file +# -*- coding: utf-8 -*- + + +#filename = '8816-1_01_center.osc' +#fnom = '8816-1.osc' +#fnom = '8816-1_0_prevScan.osc' +#fnom = '8816-1_02_nearEdge.osc' + + +from cryspy3.ebsd import ebsd +from cryspy3.rot import bunge, quat +from cryspy3.xtal import orientation, interpret_point_group_name +import struct +import numpy as np + +def loadosc(filename, create_ebsd_object = True, ang_output = False, \ + ang_output_filename = None): + """ loads osc files and converts to ang format, if desired + + This Python code performs the OSC to ANG conversion approximately 25% faster + than the previous script in Matlab [#TODO: add reference to matlab], with + some corrections in output and a potential improvement in memory usage + for opening very large OSC files. + + NOTES + ----- + + There are at least two Matlab versions of this floating around. One is from + Adam Shiveley (AFRL/RXCM) and the other is in the MTEX Matlab Toolbox. The + MTEX version is a partially rewritten version of Shiveley's by Florian + Bachmann. I was unable to get it to work in my testing. One problem is was that + the buffer size was set too small. + + Adam Shiveley's version from 9 Feb 2012 for Matlab is still in use at AFRL as + of 21 June 2016. My translation includes all of his original code as comments. + All Matlab code will be indicated by a % sign at the beginning of the comment + line. + + NOTES SPECIFIC TO THIS PYTHON VERSION: + + 1. HKL_COL_4 for first phase is wrong + 2. TEM_PX_PER_MICRON not yet correctly located in data file. We put in a dummy + when we write to ang. + 3. Not yet tested for more than 2 phases. + 4. We don't locate the elastic constants or "categories" in the osc file. + ANG reading works without the elastic constants, and we put in a dummy + for the categories. + 5. Some items that appear as zeroes to 5 or so decimal places appear as very + small floats ~1E-38 when I import them. + + PERFORMANCE TESTS: + A. 8234 kB OSC file: 3.75 s Python version; 5.05 s Matlab version + (~25% improvement) + B. 277635 kB OSC file: 98.9 s Python version; 132.8 s Matlab version + (~25% improvement) + + #------------------------------------------------------------------------------ + ADAM SHIVELEY'S ORIGINAL HEADER: + %function TSL_version = Decode_OSC_Header(OscFile, fileWriteID) + % Decode_Osc_Header(OscFile, fileWriteID) + % + % Inputs: OscFile is a string containing the path and file name of the OSC + % file to decode + % fileWriteID is an integer resulting from fileWriteID = + fopen('output.ang','w'); + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % coded by: Adam Shiveley 9 Feb 12, AFRL/RXCM + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %The OSC file is basically structured like this: + %Phase 1 name + %Phase 1 symmetry + %Phase 1 LatticeConstants + %Phase 1 Number of Families + %Phase 1 hklFamilies (read in every third one) + %Phase 1 Formula name + %This repeats for each phase in the scan + %sounds simple, right? Wrong the values are stored in decimal + %because of the way I choose to read the file in. + %This means data(10) = 70 which is actually the letter + %F as explained in this example: + %This is in decimal!!!! + %Example: data(10) = 70 + %char(data(10)) = F Look at ASCII table, Dec 70 = F + %This is where the fun begins. Most of the header information + %can be extracted directly using the decimal values and running + %the char command, however, the Euler angles must be extracted + %and converted into columns of length 4 then typecasted to a single + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + """ + + def _unpack_from_mmap(mmap_id, location_start): + + mmap_id.seek(location_start) + check = mmap_id.read_byte() + mmap_id.seek(location_start) # go back + counter = 0 + + if check != '\x00': + + temp_variable = '\x01' + + while temp_variable != '\x00': + + temp_variable = mmap_id.read_byte() + counter += 1 + + counter -= 1 + + mmap_id.seek(location_start) + result = mmap_id.read(counter) + + else: + + result = "" + + return result + + + #------------------------------------------------------------------------------ + + # So we can measure the time it takes to read the data + import time + t = time.clock() + + """ + % %Now let's get the handle to open the scan file + % fid=fopen(OscFile); + % + % %This is in decimal!!!! + % %Example: data(10) = 70 + % %char(data(10)) = F Look at ASCII table, Dec 70 = F + % data = transpose( fread( fid, '*bit8' ) ); + + + ------------------------------------------ + + We have a couple options for opening the file in Python, e.g.: + # with open(fnom, mode='rb') as file: # b is important -> binary + # bindat = file.read() + + I have chosen to proceed with memory mapping rather than reading the whole file + into memory because it's supposed to offer benefits for large files. + + My translation of Adam Shiveley's original code takes advantage of this for + some functions but memory management could still use future improvement. + + Shiveley reads in the entire binary file as 8 bit int and then casts the + integers into other data types. I am generally trying to selectively read the + portions we need as binary and unpack them as the correct data type. + """ + + import mmap + with open(filename, "r+b") as f: + # memory-map the file, size 0 means whole file + mm = mmap.mmap(f.fileno(), 0) + + """ The expected locations of header information in the data array""" + info_location_start = 72 + operator_location_start = 1095 + sample_ID_location_start = 1350 + scan_ID_location_start = 1605 + calibration_location_start = 1860 + + """ + % %Let's locate if there is any info the user typed in + % %There might be an issue here depending on how long the user types the + % %comments. Will need further testing to see if it breaks the hard-coded + % %index locations of the SampleID, Operator, and ScanID + % if(data(73) ~= 0) + % + % info_temp_variable = 1; + % info_counter = 73; + % + % while info_temp_variable ~= 0 + % + % info_temp_variable = data(info_counter); + % info_counter = info_counter +1; + % + % end + % + % %Convert the data into characters + % info = char(data(73:info_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear info_temp + % + % else + % + % info = ''; + % + % end + % + % %Now let's parse out the operator name + % if(data(1096) ~= 0) + % + % operator_temp_variable = 1; + % operator_counter = 1096; + % + % while operator_temp_variable ~= 0 + % + % operator_temp_variable = data(operator_counter); + % operator_counter = operator_counter +1; + % + % end + % + % %Convert the data into characters + % operator = char(data(1096:operator_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear operator_temp + % + % else + % + % operator = ' '; + % + % end + % + % + % %Now let's parse out the Sample ID. The index location could change if + % %a long info string is typed in. Seems to not change on the test I have + % %ran + % if(data(1351) ~= 0) + % + % sample_ID_temp_variable = 1; + % sample_ID_counter = 1351; + % + % while sample_ID_temp_variable ~= 0 + % + % sample_ID_temp_variable = data(sample_ID_counter); + % sample_ID_counter = sample_ID_counter +1; + % + % end + % + % %Convert the data into characters + % Sample_ID = char(data(1351:sample_ID_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear sample_ID_temp + % + % + % else + % + % Sample_ID = ' '; + % + % end + % + % %Now let's parse out the Scan ID. Again, the index could change if the + % %info is really long + % if(data(1606) ~= 0) + % + % scan_ID_temp_variable = 1; + % scan_ID_counter = 1606; + % + % while scan_ID_temp_variable ~= 0 + % + % scan_ID_temp_variable = data(scan_ID_counter); + % scan_ID_counter = scan_ID_counter +1; + % + % end + % + % %Convert the data into characters + % Scan_ID = char(data(1606:scan_ID_counter-1)); + % + % %Clear the vareiable to free up some memory + % clear scan_ID_temp + % + % else + % + % Scan_ID =' '; + % + % end + + """ + info = _unpack_from_mmap(mm, info_location_start) # FIXME: write this info data somewhere to be used for writing ang files + operator_name = _unpack_from_mmap(mm, operator_location_start) + sample_ID = _unpack_from_mmap(mm, sample_ID_location_start) + scan_ID = _unpack_from_mmap(mm, scan_ID_location_start) + + """ + % Now let's check the file for the TEM_PIXperUm, x-star, y-star, z-star + % and working distance + + % Not sure where the TEM_PIXperUM is located because I don't have the TEM + % version, so I'll just hard code it for now + """ + TEM_PIXperUM = 1 + + """ + % %This extracts the calibration info + % calibration_encoded = data(1861:1876); + % + % %Now we need to reshape the calibration info into groups of 4 + % calibration_reshaped = reshape(calibration_encoded, + [4 length(calibration_encoded)/4]); + % + % %Let's typecast the data into a single representation + % calibration_decoded = typecast(calibration_reshaped(:), 'single'); + % + % %Here's the final calibration info + % x_star = calibration_decoded(1); + % + % y_star = calibration_decoded(2); + % + % z_star = calibration_decoded(3); + % + % working_distance = calibration_decoded(4); + """ + mm.seek(calibration_location_start) + calibration_encoded = mm.read(16) + calibration_decoded = struct.unpack('<4f', calibration_encoded) + del calibration_encoded + + x_star = calibration_decoded[0] + y_star = calibration_decoded[1] + z_star = calibration_decoded[2] + working_distance = calibration_decoded[3] + del calibration_decoded + + """ + % %Find the starting location for phase 1. This never seems to change + % %so far it stays constant, but I would like to find a more secure way + % %of finding the 1st phase. Seems like the command: + % %strfind(data, [-71 11 -17 -1 1]) would work... + % %maybe something like: + % start_phase_location = strfind(data, [-71 11 -17 -1 1 0 0 0]); + % phase_one_temp = data(start_phase_location:end); + % begin_phase_one = strfind(phase_one_temp, [0 0 0]); + % start_phase_1 = begin_phase_one(1) + 3 + start_phase_location(1); + % EJP! same as start_phase_location + 8??? + % phase_1_data = data(start_phase_1(1):end); + % EJP! this and the previous several lines are + a complicated way of getting to the end of + the -71 11 ... 0 0 0 match string! + % %something like that might work, but it needs further testing to verify + % + % %%%%%%%%%%%%%%%%%%%%%%%%%% + % %Added 3 Feb 2012 + % %This will locate the begining of the actual phase + % + % %Alright, so it now appears that the number after -128 43 is the number + % %of phases in the sample. + % begin_phase_1_check = strfind(phase_1_data, [-128 63]); + % + % %get the number of phases from the scan + % number_of_phases = phase_1_data(begin_phase_1_check(1)+2); + % + % %This will extract the begining of the acutal phase to the end + % phase_1_data = phase_1_data(begin_phase_1_check(1)+3:end); + % + % %So, now we need to ignore all the zeros until we hit the actual phase + % %name + % phase_1_zero_check = 0; + % phase_1_zero_check_counter = 1; + % + % %locate the begining of the actual phase 1 name + % %The way this loop is structured, you have to subtract 2 from the final + % %number in phase_1_zero_check_counter. Since the loop starts at 1 and + % %increments after it gets the current position, this will add 2 extra + % %iteration counts, 1 because it starts at 1 and 1 because it will + % %increment after the non-zero number is found + % while phase_1_zero_check == 0 + % + % phase_1_zero_check = phase_1_data(phase_1_zero_check_counter); + % phase_1_zero_check_counter = phase_1_zero_check_counter + 1; + % + % end + % %%%%%%%%%%%%%%%%%%%%%%%%%%% + % + % + % %Find the ending location for phase 1 search reigon + % %This is used until I write a code to begin searching + % %at a given offset. Then you can just say: + % %end_phase_1 = strfind(data(1945), 0); + % %This would mean begin searching data from position 1945 + % %until you find a "0" + % end_phase_1 = strfind(phase_1_data, [110 0 0]); + % % end_phase_1 = strfind(phase_1_data(begin_phase_1_check+3+ ... + % % phase_1_zero_check_counter), 0); + % + % + % if length(end_phase_1) > 1 + % + % end_phase_1 = end_phase_1(1); + % + % end + % + % %Extract phase 1 from the file using the 1989 index plus the ending index + % %since we searched for the ending_phase in an array starting at 1945 from + % %the original data set. The -1 here is because since we start at 1989 + % %we calculated everything else according to starting at 0. + % phase_1_location = data(start_phase_1-1+begin_phase_1_check(1) + 3 + ... + % phase_1_zero_check_counter-2:start_phase_1+end_phase_1); + % + % %new let's clear the phase_1_data array + % %clear phase_1_data + % + % %Find the end location of phase 1 + % end_phase_1_temp = strfind(phase_1_location, 0); + % + % %Convert the decimal values into characters using ASCII + % final_phase_1 = char(phase_1_location(1:end_phase_1_temp-1)); + """ + + # First Phase -- There must be at least one! + # It appears that each value is padded by a byte. + start_phase_location = mm.find(struct.pack(\ + '<8B', 185, 11, 239, 255, 1, 0, 0, 0)) + + tmpini = start_phase_location + 16 + end_phase_1 = mm[tmpini:].find(struct.pack('<3b', 110, 0, 0)) + 4 + + tmpini = start_phase_location+16 + tmpfin = start_phase_location+16+end_phase_1-3 + temp = mm[tmpini:tmpfin].split('\x00') + number_of_phases = struct.unpack('2d} {1: >2d} {2: >2d} '+\ + '{3:d} {4:.6f} {5:d}\n' + for i in np.arange(0, num_hkl_families_out[phase_num-1]): + fo.write(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ + hkl4[i], hkl5[i], hklfin[i])) + + # Shiveley puts this only for v6, but it seems to also be output + # for v7. We will write dummy numbers for now. + if start_phase_location == 1988 or start_phase_location == 1992: + for i in np.arange(0, 6): + fo.write(u'# ElasticConstants \t-1.000000 -1.000000 \ + -1.000000 -1.000000 -1.000000 -1.000000\n') + + # Writing dummy numbers... + fo.write(u'# Categories0 0 0 0 0 \n') + fo.write(u'#\n') + + fo.write(u'# GRID: {0}\n'.format(grid)) + fo.write(u'# XSTEP: {0:.6f}\n'.format(xstep)) + fo.write(u'# YSTEP: {0:.6f}\n'.format(ystep)) + fo.write(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) + fo.write(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) + fo.write(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) + fo.write(u'#\n') + fo.write(u'# OPERATOR: \t{0}\n'.format(operator_name)) + fo.write(u'#\n') + fo.write(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) + fo.write(u'#\n') + fo.write(u'# SCANID: \t{0}\n'.format(scan_ID)) + fo.write(u'#\n') + + if start_phase_location == 1992: # TSL OIM v7 + for i in np.arange(0, data_length): + fmt_str =u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+\ + u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} '+ \ + u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} '+\ + u'{12: >9.6f} {13: >9.6f} \n' + p = out_data[i, :] + fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ + p[6], p[7].astype(np.int), \ + p[8].astype(np.int), p[9], p[10], \ + p[11], p[12], p[13])) + elif start_phase_location == 1988 or start_phase_location == 1928: # TSL OIM v5.31 thru 6 + for i in np.arange(0, data_length): + fmt_str = u'{0: > 9.5f} {1: > 9.5f} {2: > 9.5f} {3: > 12.5f} \ + {4: > 12.5f} {5: > 8.1f} {6: > 6.3f} {7: > 2d} \ + {8: > 6d} {9: > 6.3f} \n' + p = out_data[i, :] + fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ + p[6], p[7].astype(np.int), \ + p[8].astype(np.int), p[9])) + + # NOW, create an ebsd object if the user wants one + if create_ebsd_object == True: + + phase = out_data[:,7].astype(np.int) + phaseidnums = np.arange(np.shape(phase_out)[0], 0, -1) - 1 + + s = np.zeros(np.shape(phase)) + index = np.amax(phaseidnums) + for item in phaseidnums: + loc = phase == item + s[loc] = interpret_point_group_name(np.str(phase_symmetry_out[index]),'tsl') + index -= 1 + + # Start working with what we've interpreted now + o = orientation(quaternions = quat.from_bunge(bunge(out_data[:,0], out_data[:,1], out_data[:,2])),\ + pointgroupnumbers = np.squeeze(s.astype(np.int))) + + ebsd_data = ebsd(orientations=o, x=out_data[:,3], y=out_data[:,4], phaseid=phase) # FIXME: need eventually to fix confusing variable naming here + + ebsd_data.calc_scanstepsize() + ebsd_data.prepare_for_plotting() + + # Add other fields. Note that some fields are TSL-specific, and that two of + # the fields (fit and ...) have not been added yet. + ebsd_data.iq = out_data[:,5] + ebsd_data.ci = out_data[:,6] + + # Write header stuff + hdr = [] + hdr.append(u'# TEM_PIXperUM {0:.6f}\n'.format(TEM_PIXperUM)) + hdr.append(u'# x-star {0:.6f}\n'.format(x_star)) + hdr.append(u'# y-star {0:.6f}\n'.format(y_star)) + hdr.append(u'# z-star {0:.6f}\n'.format(z_star)) + hdr.append(u'# WorkingDistance {0:.6f}\n'.format(working_distance)) + hdr.append(u'#\n') + + for phase_num in np.arange(number_of_phases, 0, -1): + hdr.append(u'# Phase {0:d}\n'.format(phase_num)) + hdr.append(u'# MaterialName \t{0}\n'.format(phase_out[phase_num-1])) + hdr.append(u'# Formula \t{0}\n'.format(formula_out[phase_num-1])) + hdr.append(u'# Info \t\t\n') + hdr.append(u'# Symmetry {0:d}\n'.format(\ + phase_symmetry_out[phase_num-1])) + hdr.append(u'# LatticeConstants \ + {0:5.3f} {1:5.3f} {2:5.3f} {3:7.3f} {4:7.3f} {5:7.3f}\n'.format(\ + lattice_constants_out[phase_num-1][0], \ + lattice_constants_out[phase_num-1][1], \ + lattice_constants_out[phase_num-1][2], \ + lattice_constants_out[phase_num-1][3], \ + lattice_constants_out[phase_num-1][4], \ + lattice_constants_out[phase_num-1][5])) + hdr.append(u'# NumberFamilies {0}\n'.format(\ + num_hkl_families_out[phase_num-1])) + + hklfam = hkl_families_out[phase_num-1] + hkl4 = hkl_4_col_out[phase_num-1] + hkl5 = hkl_5_col_out[phase_num-1] + hklfin = hkl_last_col_out[phase_num-1] + tmpstr = u'# hklFamilies \t{0: >2d} {1: >2d} {2: >2d} '+\ + '{3:d} {4:.6f} {5:d}\n' + for i in np.arange(0, num_hkl_families_out[phase_num-1]): + hdr.append(tmpstr.format(hklfam[i,0], hklfam[i,1], hklfam[i,2],\ + hkl4[i], hkl5[i], hklfin[i])) + + # Shiveley puts this only for v6, but it seems to also be output + # for v7. We will write dummy numbers for now. + if start_phase_location == 1988 or start_phase_location == 1992: + for i in np.arange(0, 6): + hdr.append(u'# ElasticConstants \t-1.000000 -1.000000 \ + -1.000000 -1.000000 -1.000000 -1.000000\n') + + # Writing dummy numbers... + hdr.append(u'# Categories0 0 0 0 0 \n') + hdr.append(u'#\n') + + hdr.append(u'# GRID: {0}\n'.format(grid)) + hdr.append(u'# XSTEP: {0:.6f}\n'.format(xstep)) + hdr.append(u'# YSTEP: {0:.6f}\n'.format(ystep)) + hdr.append(u'# NCOLS_ODD: {0:d}\n'.format(ncols_odd.astype(np.int))) + hdr.append(u'# NCOLS_EVEN: {0:d}\n'.format(ncols_even.astype(np.int))) + hdr.append(u'# NROWS: {0:d}\n'.format(nrows.astype(np.int))) + hdr.append(u'#\n') + hdr.append(u'# OPERATOR: \t{0}\n'.format(operator_name)) + hdr.append(u'#\n') + hdr.append(u'# SAMPLEID: \t{0}\n'.format(sample_ID)) + hdr.append(u'#\n') + hdr.append(u'# SCANID: \t{0}\n'.format(scan_ID)) + hdr.append(u'#\n') + ebsd_data._anghdr = "".join(hdr) + + return ebsd_data + + # This is it. + print('Elapsed time = {0}'.format(time.clock() - t)) \ No newline at end of file diff --git a/cryspy/cio/writeang.py b/cryspy/cio/writeang.py index 594c2ee..cfd7584 100755 --- a/cryspy/cio/writeang.py +++ b/cryspy/cio/writeang.py @@ -1,62 +1,62 @@ -# -*- coding: utf-8 -*- -# Created on Fri Feb 17 17:41:38 2017 by paytonej - -def writeang(ebsd_object, filename): - - import cryspy.util as util - import cryspy.rot as rot - import io - import numpy as np - - pb = util.progbar(finalcount=ebsd_object.nr, message='Writing to ANG') - with io.open(filename, 'w', newline='\r\n') as fo: - - # FIXME: We need to fill in the appropriate stuff for the header from what - # is stored internally in the ebsd_data object. Until then, this works for - # data taken from *.ang files. - for item in ebsd_object._anghdr: - fo.write(u'# {0:s}\n'.format(item)) - - euler = rot.bunge.from_quat( - ebsd_object.orientations.rotations).to_array() - - xy = np.vstack([ebsd_object.x, ebsd_object.y]).T - - # TODO: Check if we are saving image quality and ci in this way from a - # ctf import. If we are not, what should we write here? - iqciphase = np.vstack([ebsd_object.iq, ebsd_object.ci, - ebsd_object.phaseid-1]).T - - num_other_cols = ebsd_object._oimversion_numangcols - 8 - - # FIXME: What if there is no ebsd_data._other because it didn't come from a previous TSL dataset? - oth = np.reshape(np.asanyarray(ebsd_object._other), - [len(ebsd_object._other) / num_other_cols, - num_other_cols]) - - out_data = np.hstack([euler, xy, iqciphase, oth]) - - if out_data.shape[1] == 14: # see loadosc - fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+ \ - u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ - u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} ' + \ - u'{12: >9.6f} {13: >9.6f} \n' - elif out_data.shape[1] == 10: # see loadosc - fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f}' + \ - u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ - u'{8: >6d} {9: >6.3f} \n' - elif out_data.shape[1] == 9: # Taken from my old writeang.m 2010-03-06 EJP - fmt_str = u'{0: >9.3f} {1: >9.3f} {2: >9.3f} {3: >12.3f}' + \ - u'{4: >12.3f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ - u'{8: >6d} \n' - - for i in np.arange(0, ebsd_object.nr): - - p = out_data[i, :] - fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ - p[6], p[7].astype(np.int), \ - p[8].astype(np.int), p[9], p[10], \ - p[11], p[12], p[13])) - pb.update(i) - +# -*- coding: utf-8 -*- +# Created on Fri Feb 17 17:41:38 2017 by paytonej + +def writeang(ebsd_object, filename): + + import cryspy.util as util + import cryspy.rot as rot + import io + import numpy as np + + pb = util.progbar(finalcount=ebsd_object.nr, message='Writing to ANG') + with io.open(filename, 'w', newline='\r\n') as fo: + + # FIXME: We need to fill in the appropriate stuff for the header from what + # is stored internally in the ebsd_data object. Until then, this works for + # data taken from *.ang files. + for item in ebsd_object._anghdr: + fo.write(u'# {0:s}\n'.format(item)) + + euler = rot.bunge.from_quat( + ebsd_object.orientations.rotations).to_array() + + xy = np.vstack([ebsd_object.x, ebsd_object.y]).T + + # TODO: Check if we are saving image quality and ci in this way from a + # ctf import. If we are not, what should we write here? + iqciphase = np.vstack([ebsd_object.iq, ebsd_object.ci, + ebsd_object.phaseid-1]).T + + num_other_cols = ebsd_object._oimversion_numangcols - 8 + + # FIXME: What if there is no ebsd_data._other because it didn't come from a previous TSL dataset? + oth = np.reshape(np.asanyarray(ebsd_object._other), + [len(ebsd_object._other) / num_other_cols, + num_other_cols]) + + out_data = np.hstack([euler, xy, iqciphase, oth]) + + if out_data.shape[1] == 14: # see loadosc + fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f} '+ \ + u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ + u'{8: >6d} {9: >6.3f} {10: >9.6f} {11: >9.6f} ' + \ + u'{12: >9.6f} {13: >9.6f} \n' + elif out_data.shape[1] == 10: # see loadosc + fmt_str = u'{0: >9.5f} {1: >9.5f} {2: >9.5f} {3: >12.5f}' + \ + u'{4: >12.5f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ + u'{8: >6d} {9: >6.3f} \n' + elif out_data.shape[1] == 9: # Taken from my old writeang.m 2010-03-06 EJP + fmt_str = u'{0: >9.3f} {1: >9.3f} {2: >9.3f} {3: >12.3f}' + \ + u'{4: >12.3f} {5: >6.1f} {6: >6.3f} {7: >2d} ' + \ + u'{8: >6d} \n' + + for i in np.arange(0, ebsd_object.nr): + + p = out_data[i, :] + fo.write(fmt_str.format(p[0], p[1], p[2], p[3], p[4], p[5], \ + p[6], p[7].astype(np.int), \ + p[8].astype(np.int), p[9], p[10], \ + p[11], p[12], p[13])) + pb.update(i) + pb.update(-1) \ No newline at end of file diff --git a/cryspy/colormaps.py b/cryspy/colormaps.py new file mode 100755 index 0000000..9ac4603 --- /dev/null +++ b/cryspy/colormaps.py @@ -0,0 +1,1317 @@ +# New matplotlib colormaps by Nathaniel J. Smith, Stefan van der Walt, +# and (in the case of viridis) Eric Firing. +# +# This file and the colormaps in it are released under the CC0 license / +# public domain dedication. We would appreciate credit if you use or +# redistribute these colormaps, but do not impose any legal restrictions. +# +# To the extent possible under law, the persons who associated CC0 with +# mpl-colormaps have waived all copyright and related or neighboring rights +# to mpl-colormaps. +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . + +__all__ = ['magma', 'inferno', 'plasma', 'viridis', 'zerodis'] + +_magma_data = [[0.001462, 0.000466, 0.013866], + [0.002258, 0.001295, 0.018331], + [0.003279, 0.002305, 0.023708], + [0.004512, 0.003490, 0.029965], + [0.005950, 0.004843, 0.037130], + [0.007588, 0.006356, 0.044973], + [0.009426, 0.008022, 0.052844], + [0.011465, 0.009828, 0.060750], + [0.013708, 0.011771, 0.068667], + [0.016156, 0.013840, 0.076603], + [0.018815, 0.016026, 0.084584], + [0.021692, 0.018320, 0.092610], + [0.024792, 0.020715, 0.100676], + [0.028123, 0.023201, 0.108787], + [0.031696, 0.025765, 0.116965], + [0.035520, 0.028397, 0.125209], + [0.039608, 0.031090, 0.133515], + [0.043830, 0.033830, 0.141886], + [0.048062, 0.036607, 0.150327], + [0.052320, 0.039407, 0.158841], + [0.056615, 0.042160, 0.167446], + [0.060949, 0.044794, 0.176129], + [0.065330, 0.047318, 0.184892], + [0.069764, 0.049726, 0.193735], + [0.074257, 0.052017, 0.202660], + [0.078815, 0.054184, 0.211667], + [0.083446, 0.056225, 0.220755], + [0.088155, 0.058133, 0.229922], + [0.092949, 0.059904, 0.239164], + [0.097833, 0.061531, 0.248477], + [0.102815, 0.063010, 0.257854], + [0.107899, 0.064335, 0.267289], + [0.113094, 0.065492, 0.276784], + [0.118405, 0.066479, 0.286321], + [0.123833, 0.067295, 0.295879], + [0.129380, 0.067935, 0.305443], + [0.135053, 0.068391, 0.315000], + [0.140858, 0.068654, 0.324538], + [0.146785, 0.068738, 0.334011], + [0.152839, 0.068637, 0.343404], + [0.159018, 0.068354, 0.352688], + [0.165308, 0.067911, 0.361816], + [0.171713, 0.067305, 0.370771], + [0.178212, 0.066576, 0.379497], + [0.184801, 0.065732, 0.387973], + [0.191460, 0.064818, 0.396152], + [0.198177, 0.063862, 0.404009], + [0.204935, 0.062907, 0.411514], + [0.211718, 0.061992, 0.418647], + [0.218512, 0.061158, 0.425392], + [0.225302, 0.060445, 0.431742], + [0.232077, 0.059889, 0.437695], + [0.238826, 0.059517, 0.443256], + [0.245543, 0.059352, 0.448436], + [0.252220, 0.059415, 0.453248], + [0.258857, 0.059706, 0.457710], + [0.265447, 0.060237, 0.461840], + [0.271994, 0.060994, 0.465660], + [0.278493, 0.061978, 0.469190], + [0.284951, 0.063168, 0.472451], + [0.291366, 0.064553, 0.475462], + [0.297740, 0.066117, 0.478243], + [0.304081, 0.067835, 0.480812], + [0.310382, 0.069702, 0.483186], + [0.316654, 0.071690, 0.485380], + [0.322899, 0.073782, 0.487408], + [0.329114, 0.075972, 0.489287], + [0.335308, 0.078236, 0.491024], + [0.341482, 0.080564, 0.492631], + [0.347636, 0.082946, 0.494121], + [0.353773, 0.085373, 0.495501], + [0.359898, 0.087831, 0.496778], + [0.366012, 0.090314, 0.497960], + [0.372116, 0.092816, 0.499053], + [0.378211, 0.095332, 0.500067], + [0.384299, 0.097855, 0.501002], + [0.390384, 0.100379, 0.501864], + [0.396467, 0.102902, 0.502658], + [0.402548, 0.105420, 0.503386], + [0.408629, 0.107930, 0.504052], + [0.414709, 0.110431, 0.504662], + [0.420791, 0.112920, 0.505215], + [0.426877, 0.115395, 0.505714], + [0.432967, 0.117855, 0.506160], + [0.439062, 0.120298, 0.506555], + [0.445163, 0.122724, 0.506901], + [0.451271, 0.125132, 0.507198], + [0.457386, 0.127522, 0.507448], + [0.463508, 0.129893, 0.507652], + [0.469640, 0.132245, 0.507809], + [0.475780, 0.134577, 0.507921], + [0.481929, 0.136891, 0.507989], + [0.488088, 0.139186, 0.508011], + [0.494258, 0.141462, 0.507988], + [0.500438, 0.143719, 0.507920], + [0.506629, 0.145958, 0.507806], + [0.512831, 0.148179, 0.507648], + [0.519045, 0.150383, 0.507443], + [0.525270, 0.152569, 0.507192], + [0.531507, 0.154739, 0.506895], + [0.537755, 0.156894, 0.506551], + [0.544015, 0.159033, 0.506159], + [0.550287, 0.161158, 0.505719], + [0.556571, 0.163269, 0.505230], + [0.562866, 0.165368, 0.504692], + [0.569172, 0.167454, 0.504105], + [0.575490, 0.169530, 0.503466], + [0.581819, 0.171596, 0.502777], + [0.588158, 0.173652, 0.502035], + [0.594508, 0.175701, 0.501241], + [0.600868, 0.177743, 0.500394], + [0.607238, 0.179779, 0.499492], + [0.613617, 0.181811, 0.498536], + [0.620005, 0.183840, 0.497524], + [0.626401, 0.185867, 0.496456], + [0.632805, 0.187893, 0.495332], + [0.639216, 0.189921, 0.494150], + [0.645633, 0.191952, 0.492910], + [0.652056, 0.193986, 0.491611], + [0.658483, 0.196027, 0.490253], + [0.664915, 0.198075, 0.488836], + [0.671349, 0.200133, 0.487358], + [0.677786, 0.202203, 0.485819], + [0.684224, 0.204286, 0.484219], + [0.690661, 0.206384, 0.482558], + [0.697098, 0.208501, 0.480835], + [0.703532, 0.210638, 0.479049], + [0.709962, 0.212797, 0.477201], + [0.716387, 0.214982, 0.475290], + [0.722805, 0.217194, 0.473316], + [0.729216, 0.219437, 0.471279], + [0.735616, 0.221713, 0.469180], + [0.742004, 0.224025, 0.467018], + [0.748378, 0.226377, 0.464794], + [0.754737, 0.228772, 0.462509], + [0.761077, 0.231214, 0.460162], + [0.767398, 0.233705, 0.457755], + [0.773695, 0.236249, 0.455289], + [0.779968, 0.238851, 0.452765], + [0.786212, 0.241514, 0.450184], + [0.792427, 0.244242, 0.447543], + [0.798608, 0.247040, 0.444848], + [0.804752, 0.249911, 0.442102], + [0.810855, 0.252861, 0.439305], + [0.816914, 0.255895, 0.436461], + [0.822926, 0.259016, 0.433573], + [0.828886, 0.262229, 0.430644], + [0.834791, 0.265540, 0.427671], + [0.840636, 0.268953, 0.424666], + [0.846416, 0.272473, 0.421631], + [0.852126, 0.276106, 0.418573], + [0.857763, 0.279857, 0.415496], + [0.863320, 0.283729, 0.412403], + [0.868793, 0.287728, 0.409303], + [0.874176, 0.291859, 0.406205], + [0.879464, 0.296125, 0.403118], + [0.884651, 0.300530, 0.400047], + [0.889731, 0.305079, 0.397002], + [0.894700, 0.309773, 0.393995], + [0.899552, 0.314616, 0.391037], + [0.904281, 0.319610, 0.388137], + [0.908884, 0.324755, 0.385308], + [0.913354, 0.330052, 0.382563], + [0.917689, 0.335500, 0.379915], + [0.921884, 0.341098, 0.377376], + [0.925937, 0.346844, 0.374959], + [0.929845, 0.352734, 0.372677], + [0.933606, 0.358764, 0.370541], + [0.937221, 0.364929, 0.368567], + [0.940687, 0.371224, 0.366762], + [0.944006, 0.377643, 0.365136], + [0.947180, 0.384178, 0.363701], + [0.950210, 0.390820, 0.362468], + [0.953099, 0.397563, 0.361438], + [0.955849, 0.404400, 0.360619], + [0.958464, 0.411324, 0.360014], + [0.960949, 0.418323, 0.359630], + [0.963310, 0.425390, 0.359469], + [0.965549, 0.432519, 0.359529], + [0.967671, 0.439703, 0.359810], + [0.969680, 0.446936, 0.360311], + [0.971582, 0.454210, 0.361030], + [0.973381, 0.461520, 0.361965], + [0.975082, 0.468861, 0.363111], + [0.976690, 0.476226, 0.364466], + [0.978210, 0.483612, 0.366025], + [0.979645, 0.491014, 0.367783], + [0.981000, 0.498428, 0.369734], + [0.982279, 0.505851, 0.371874], + [0.983485, 0.513280, 0.374198], + [0.984622, 0.520713, 0.376698], + [0.985693, 0.528148, 0.379371], + [0.986700, 0.535582, 0.382210], + [0.987646, 0.543015, 0.385210], + [0.988533, 0.550446, 0.388365], + [0.989363, 0.557873, 0.391671], + [0.990138, 0.565296, 0.395122], + [0.990871, 0.572706, 0.398714], + [0.991558, 0.580107, 0.402441], + [0.992196, 0.587502, 0.406299], + [0.992785, 0.594891, 0.410283], + [0.993326, 0.602275, 0.414390], + [0.993834, 0.609644, 0.418613], + [0.994309, 0.616999, 0.422950], + [0.994738, 0.624350, 0.427397], + [0.995122, 0.631696, 0.431951], + [0.995480, 0.639027, 0.436607], + [0.995810, 0.646344, 0.441361], + [0.996096, 0.653659, 0.446213], + [0.996341, 0.660969, 0.451160], + [0.996580, 0.668256, 0.456192], + [0.996775, 0.675541, 0.461314], + [0.996925, 0.682828, 0.466526], + [0.997077, 0.690088, 0.471811], + [0.997186, 0.697349, 0.477182], + [0.997254, 0.704611, 0.482635], + [0.997325, 0.711848, 0.488154], + [0.997351, 0.719089, 0.493755], + [0.997351, 0.726324, 0.499428], + [0.997341, 0.733545, 0.505167], + [0.997285, 0.740772, 0.510983], + [0.997228, 0.747981, 0.516859], + [0.997138, 0.755190, 0.522806], + [0.997019, 0.762398, 0.528821], + [0.996898, 0.769591, 0.534892], + [0.996727, 0.776795, 0.541039], + [0.996571, 0.783977, 0.547233], + [0.996369, 0.791167, 0.553499], + [0.996162, 0.798348, 0.559820], + [0.995932, 0.805527, 0.566202], + [0.995680, 0.812706, 0.572645], + [0.995424, 0.819875, 0.579140], + [0.995131, 0.827052, 0.585701], + [0.994851, 0.834213, 0.592307], + [0.994524, 0.841387, 0.598983], + [0.994222, 0.848540, 0.605696], + [0.993866, 0.855711, 0.612482], + [0.993545, 0.862859, 0.619299], + [0.993170, 0.870024, 0.626189], + [0.992831, 0.877168, 0.633109], + [0.992440, 0.884330, 0.640099], + [0.992089, 0.891470, 0.647116], + [0.991688, 0.898627, 0.654202], + [0.991332, 0.905763, 0.661309], + [0.990930, 0.912915, 0.668481], + [0.990570, 0.920049, 0.675675], + [0.990175, 0.927196, 0.682926], + [0.989815, 0.934329, 0.690198], + [0.989434, 0.941470, 0.697519], + [0.989077, 0.948604, 0.704863], + [0.988717, 0.955742, 0.712242], + [0.988367, 0.962878, 0.719649], + [0.988033, 0.970012, 0.727077], + [0.987691, 0.977154, 0.734536], + [0.987387, 0.984288, 0.742002], + [0.987053, 0.991438, 0.749504]] + +_inferno_data = [[0.001462, 0.000466, 0.013866], + [0.002267, 0.001270, 0.018570], + [0.003299, 0.002249, 0.024239], + [0.004547, 0.003392, 0.030909], + [0.006006, 0.004692, 0.038558], + [0.007676, 0.006136, 0.046836], + [0.009561, 0.007713, 0.055143], + [0.011663, 0.009417, 0.063460], + [0.013995, 0.011225, 0.071862], + [0.016561, 0.013136, 0.080282], + [0.019373, 0.015133, 0.088767], + [0.022447, 0.017199, 0.097327], + [0.025793, 0.019331, 0.105930], + [0.029432, 0.021503, 0.114621], + [0.033385, 0.023702, 0.123397], + [0.037668, 0.025921, 0.132232], + [0.042253, 0.028139, 0.141141], + [0.046915, 0.030324, 0.150164], + [0.051644, 0.032474, 0.159254], + [0.056449, 0.034569, 0.168414], + [0.061340, 0.036590, 0.177642], + [0.066331, 0.038504, 0.186962], + [0.071429, 0.040294, 0.196354], + [0.076637, 0.041905, 0.205799], + [0.081962, 0.043328, 0.215289], + [0.087411, 0.044556, 0.224813], + [0.092990, 0.045583, 0.234358], + [0.098702, 0.046402, 0.243904], + [0.104551, 0.047008, 0.253430], + [0.110536, 0.047399, 0.262912], + [0.116656, 0.047574, 0.272321], + [0.122908, 0.047536, 0.281624], + [0.129285, 0.047293, 0.290788], + [0.135778, 0.046856, 0.299776], + [0.142378, 0.046242, 0.308553], + [0.149073, 0.045468, 0.317085], + [0.155850, 0.044559, 0.325338], + [0.162689, 0.043554, 0.333277], + [0.169575, 0.042489, 0.340874], + [0.176493, 0.041402, 0.348111], + [0.183429, 0.040329, 0.354971], + [0.190367, 0.039309, 0.361447], + [0.197297, 0.038400, 0.367535], + [0.204209, 0.037632, 0.373238], + [0.211095, 0.037030, 0.378563], + [0.217949, 0.036615, 0.383522], + [0.224763, 0.036405, 0.388129], + [0.231538, 0.036405, 0.392400], + [0.238273, 0.036621, 0.396353], + [0.244967, 0.037055, 0.400007], + [0.251620, 0.037705, 0.403378], + [0.258234, 0.038571, 0.406485], + [0.264810, 0.039647, 0.409345], + [0.271347, 0.040922, 0.411976], + [0.277850, 0.042353, 0.414392], + [0.284321, 0.043933, 0.416608], + [0.290763, 0.045644, 0.418637], + [0.297178, 0.047470, 0.420491], + [0.303568, 0.049396, 0.422182], + [0.309935, 0.051407, 0.423721], + [0.316282, 0.053490, 0.425116], + [0.322610, 0.055634, 0.426377], + [0.328921, 0.057827, 0.427511], + [0.335217, 0.060060, 0.428524], + [0.341500, 0.062325, 0.429425], + [0.347771, 0.064616, 0.430217], + [0.354032, 0.066925, 0.430906], + [0.360284, 0.069247, 0.431497], + [0.366529, 0.071579, 0.431994], + [0.372768, 0.073915, 0.432400], + [0.379001, 0.076253, 0.432719], + [0.385228, 0.078591, 0.432955], + [0.391453, 0.080927, 0.433109], + [0.397674, 0.083257, 0.433183], + [0.403894, 0.085580, 0.433179], + [0.410113, 0.087896, 0.433098], + [0.416331, 0.090203, 0.432943], + [0.422549, 0.092501, 0.432714], + [0.428768, 0.094790, 0.432412], + [0.434987, 0.097069, 0.432039], + [0.441207, 0.099338, 0.431594], + [0.447428, 0.101597, 0.431080], + [0.453651, 0.103848, 0.430498], + [0.459875, 0.106089, 0.429846], + [0.466100, 0.108322, 0.429125], + [0.472328, 0.110547, 0.428334], + [0.478558, 0.112764, 0.427475], + [0.484789, 0.114974, 0.426548], + [0.491022, 0.117179, 0.425552], + [0.497257, 0.119379, 0.424488], + [0.503493, 0.121575, 0.423356], + [0.509730, 0.123769, 0.422156], + [0.515967, 0.125960, 0.420887], + [0.522206, 0.128150, 0.419549], + [0.528444, 0.130341, 0.418142], + [0.534683, 0.132534, 0.416667], + [0.540920, 0.134729, 0.415123], + [0.547157, 0.136929, 0.413511], + [0.553392, 0.139134, 0.411829], + [0.559624, 0.141346, 0.410078], + [0.565854, 0.143567, 0.408258], + [0.572081, 0.145797, 0.406369], + [0.578304, 0.148039, 0.404411], + [0.584521, 0.150294, 0.402385], + [0.590734, 0.152563, 0.400290], + [0.596940, 0.154848, 0.398125], + [0.603139, 0.157151, 0.395891], + [0.609330, 0.159474, 0.393589], + [0.615513, 0.161817, 0.391219], + [0.621685, 0.164184, 0.388781], + [0.627847, 0.166575, 0.386276], + [0.633998, 0.168992, 0.383704], + [0.640135, 0.171438, 0.381065], + [0.646260, 0.173914, 0.378359], + [0.652369, 0.176421, 0.375586], + [0.658463, 0.178962, 0.372748], + [0.664540, 0.181539, 0.369846], + [0.670599, 0.184153, 0.366879], + [0.676638, 0.186807, 0.363849], + [0.682656, 0.189501, 0.360757], + [0.688653, 0.192239, 0.357603], + [0.694627, 0.195021, 0.354388], + [0.700576, 0.197851, 0.351113], + [0.706500, 0.200728, 0.347777], + [0.712396, 0.203656, 0.344383], + [0.718264, 0.206636, 0.340931], + [0.724103, 0.209670, 0.337424], + [0.729909, 0.212759, 0.333861], + [0.735683, 0.215906, 0.330245], + [0.741423, 0.219112, 0.326576], + [0.747127, 0.222378, 0.322856], + [0.752794, 0.225706, 0.319085], + [0.758422, 0.229097, 0.315266], + [0.764010, 0.232554, 0.311399], + [0.769556, 0.236077, 0.307485], + [0.775059, 0.239667, 0.303526], + [0.780517, 0.243327, 0.299523], + [0.785929, 0.247056, 0.295477], + [0.791293, 0.250856, 0.291390], + [0.796607, 0.254728, 0.287264], + [0.801871, 0.258674, 0.283099], + [0.807082, 0.262692, 0.278898], + [0.812239, 0.266786, 0.274661], + [0.817341, 0.270954, 0.270390], + [0.822386, 0.275197, 0.266085], + [0.827372, 0.279517, 0.261750], + [0.832299, 0.283913, 0.257383], + [0.837165, 0.288385, 0.252988], + [0.841969, 0.292933, 0.248564], + [0.846709, 0.297559, 0.244113], + [0.851384, 0.302260, 0.239636], + [0.855992, 0.307038, 0.235133], + [0.860533, 0.311892, 0.230606], + [0.865006, 0.316822, 0.226055], + [0.869409, 0.321827, 0.221482], + [0.873741, 0.326906, 0.216886], + [0.878001, 0.332060, 0.212268], + [0.882188, 0.337287, 0.207628], + [0.886302, 0.342586, 0.202968], + [0.890341, 0.347957, 0.198286], + [0.894305, 0.353399, 0.193584], + [0.898192, 0.358911, 0.188860], + [0.902003, 0.364492, 0.184116], + [0.905735, 0.370140, 0.179350], + [0.909390, 0.375856, 0.174563], + [0.912966, 0.381636, 0.169755], + [0.916462, 0.387481, 0.164924], + [0.919879, 0.393389, 0.160070], + [0.923215, 0.399359, 0.155193], + [0.926470, 0.405389, 0.150292], + [0.929644, 0.411479, 0.145367], + [0.932737, 0.417627, 0.140417], + [0.935747, 0.423831, 0.135440], + [0.938675, 0.430091, 0.130438], + [0.941521, 0.436405, 0.125409], + [0.944285, 0.442772, 0.120354], + [0.946965, 0.449191, 0.115272], + [0.949562, 0.455660, 0.110164], + [0.952075, 0.462178, 0.105031], + [0.954506, 0.468744, 0.099874], + [0.956852, 0.475356, 0.094695], + [0.959114, 0.482014, 0.089499], + [0.961293, 0.488716, 0.084289], + [0.963387, 0.495462, 0.079073], + [0.965397, 0.502249, 0.073859], + [0.967322, 0.509078, 0.068659], + [0.969163, 0.515946, 0.063488], + [0.970919, 0.522853, 0.058367], + [0.972590, 0.529798, 0.053324], + [0.974176, 0.536780, 0.048392], + [0.975677, 0.543798, 0.043618], + [0.977092, 0.550850, 0.039050], + [0.978422, 0.557937, 0.034931], + [0.979666, 0.565057, 0.031409], + [0.980824, 0.572209, 0.028508], + [0.981895, 0.579392, 0.026250], + [0.982881, 0.586606, 0.024661], + [0.983779, 0.593849, 0.023770], + [0.984591, 0.601122, 0.023606], + [0.985315, 0.608422, 0.024202], + [0.985952, 0.615750, 0.025592], + [0.986502, 0.623105, 0.027814], + [0.986964, 0.630485, 0.030908], + [0.987337, 0.637890, 0.034916], + [0.987622, 0.645320, 0.039886], + [0.987819, 0.652773, 0.045581], + [0.987926, 0.660250, 0.051750], + [0.987945, 0.667748, 0.058329], + [0.987874, 0.675267, 0.065257], + [0.987714, 0.682807, 0.072489], + [0.987464, 0.690366, 0.079990], + [0.987124, 0.697944, 0.087731], + [0.986694, 0.705540, 0.095694], + [0.986175, 0.713153, 0.103863], + [0.985566, 0.720782, 0.112229], + [0.984865, 0.728427, 0.120785], + [0.984075, 0.736087, 0.129527], + [0.983196, 0.743758, 0.138453], + [0.982228, 0.751442, 0.147565], + [0.981173, 0.759135, 0.156863], + [0.980032, 0.766837, 0.166353], + [0.978806, 0.774545, 0.176037], + [0.977497, 0.782258, 0.185923], + [0.976108, 0.789974, 0.196018], + [0.974638, 0.797692, 0.206332], + [0.973088, 0.805409, 0.216877], + [0.971468, 0.813122, 0.227658], + [0.969783, 0.820825, 0.238686], + [0.968041, 0.828515, 0.249972], + [0.966243, 0.836191, 0.261534], + [0.964394, 0.843848, 0.273391], + [0.962517, 0.851476, 0.285546], + [0.960626, 0.859069, 0.298010], + [0.958720, 0.866624, 0.310820], + [0.956834, 0.874129, 0.323974], + [0.954997, 0.881569, 0.337475], + [0.953215, 0.888942, 0.351369], + [0.951546, 0.896226, 0.365627], + [0.950018, 0.903409, 0.380271], + [0.948683, 0.910473, 0.395289], + [0.947594, 0.917399, 0.410665], + [0.946809, 0.924168, 0.426373], + [0.946392, 0.930761, 0.442367], + [0.946403, 0.937159, 0.458592], + [0.946903, 0.943348, 0.474970], + [0.947937, 0.949318, 0.491426], + [0.949545, 0.955063, 0.507860], + [0.951740, 0.960587, 0.524203], + [0.954529, 0.965896, 0.540361], + [0.957896, 0.971003, 0.556275], + [0.961812, 0.975924, 0.571925], + [0.966249, 0.980678, 0.587206], + [0.971162, 0.985282, 0.602154], + [0.976511, 0.989753, 0.616760], + [0.982257, 0.994109, 0.631017], + [0.988362, 0.998364, 0.644924]] + +_plasma_data = [[0.050383, 0.029803, 0.527975], + [0.063536, 0.028426, 0.533124], + [0.075353, 0.027206, 0.538007], + [0.086222, 0.026125, 0.542658], + [0.096379, 0.025165, 0.547103], + [0.105980, 0.024309, 0.551368], + [0.115124, 0.023556, 0.555468], + [0.123903, 0.022878, 0.559423], + [0.132381, 0.022258, 0.563250], + [0.140603, 0.021687, 0.566959], + [0.148607, 0.021154, 0.570562], + [0.156421, 0.020651, 0.574065], + [0.164070, 0.020171, 0.577478], + [0.171574, 0.019706, 0.580806], + [0.178950, 0.019252, 0.584054], + [0.186213, 0.018803, 0.587228], + [0.193374, 0.018354, 0.590330], + [0.200445, 0.017902, 0.593364], + [0.207435, 0.017442, 0.596333], + [0.214350, 0.016973, 0.599239], + [0.221197, 0.016497, 0.602083], + [0.227983, 0.016007, 0.604867], + [0.234715, 0.015502, 0.607592], + [0.241396, 0.014979, 0.610259], + [0.248032, 0.014439, 0.612868], + [0.254627, 0.013882, 0.615419], + [0.261183, 0.013308, 0.617911], + [0.267703, 0.012716, 0.620346], + [0.274191, 0.012109, 0.622722], + [0.280648, 0.011488, 0.625038], + [0.287076, 0.010855, 0.627295], + [0.293478, 0.010213, 0.629490], + [0.299855, 0.009561, 0.631624], + [0.306210, 0.008902, 0.633694], + [0.312543, 0.008239, 0.635700], + [0.318856, 0.007576, 0.637640], + [0.325150, 0.006915, 0.639512], + [0.331426, 0.006261, 0.641316], + [0.337683, 0.005618, 0.643049], + [0.343925, 0.004991, 0.644710], + [0.350150, 0.004382, 0.646298], + [0.356359, 0.003798, 0.647810], + [0.362553, 0.003243, 0.649245], + [0.368733, 0.002724, 0.650601], + [0.374897, 0.002245, 0.651876], + [0.381047, 0.001814, 0.653068], + [0.387183, 0.001434, 0.654177], + [0.393304, 0.001114, 0.655199], + [0.399411, 0.000859, 0.656133], + [0.405503, 0.000678, 0.656977], + [0.411580, 0.000577, 0.657730], + [0.417642, 0.000564, 0.658390], + [0.423689, 0.000646, 0.658956], + [0.429719, 0.000831, 0.659425], + [0.435734, 0.001127, 0.659797], + [0.441732, 0.001540, 0.660069], + [0.447714, 0.002080, 0.660240], + [0.453677, 0.002755, 0.660310], + [0.459623, 0.003574, 0.660277], + [0.465550, 0.004545, 0.660139], + [0.471457, 0.005678, 0.659897], + [0.477344, 0.006980, 0.659549], + [0.483210, 0.008460, 0.659095], + [0.489055, 0.010127, 0.658534], + [0.494877, 0.011990, 0.657865], + [0.500678, 0.014055, 0.657088], + [0.506454, 0.016333, 0.656202], + [0.512206, 0.018833, 0.655209], + [0.517933, 0.021563, 0.654109], + [0.523633, 0.024532, 0.652901], + [0.529306, 0.027747, 0.651586], + [0.534952, 0.031217, 0.650165], + [0.540570, 0.034950, 0.648640], + [0.546157, 0.038954, 0.647010], + [0.551715, 0.043136, 0.645277], + [0.557243, 0.047331, 0.643443], + [0.562738, 0.051545, 0.641509], + [0.568201, 0.055778, 0.639477], + [0.573632, 0.060028, 0.637349], + [0.579029, 0.064296, 0.635126], + [0.584391, 0.068579, 0.632812], + [0.589719, 0.072878, 0.630408], + [0.595011, 0.077190, 0.627917], + [0.600266, 0.081516, 0.625342], + [0.605485, 0.085854, 0.622686], + [0.610667, 0.090204, 0.619951], + [0.615812, 0.094564, 0.617140], + [0.620919, 0.098934, 0.614257], + [0.625987, 0.103312, 0.611305], + [0.631017, 0.107699, 0.608287], + [0.636008, 0.112092, 0.605205], + [0.640959, 0.116492, 0.602065], + [0.645872, 0.120898, 0.598867], + [0.650746, 0.125309, 0.595617], + [0.655580, 0.129725, 0.592317], + [0.660374, 0.134144, 0.588971], + [0.665129, 0.138566, 0.585582], + [0.669845, 0.142992, 0.582154], + [0.674522, 0.147419, 0.578688], + [0.679160, 0.151848, 0.575189], + [0.683758, 0.156278, 0.571660], + [0.688318, 0.160709, 0.568103], + [0.692840, 0.165141, 0.564522], + [0.697324, 0.169573, 0.560919], + [0.701769, 0.174005, 0.557296], + [0.706178, 0.178437, 0.553657], + [0.710549, 0.182868, 0.550004], + [0.714883, 0.187299, 0.546338], + [0.719181, 0.191729, 0.542663], + [0.723444, 0.196158, 0.538981], + [0.727670, 0.200586, 0.535293], + [0.731862, 0.205013, 0.531601], + [0.736019, 0.209439, 0.527908], + [0.740143, 0.213864, 0.524216], + [0.744232, 0.218288, 0.520524], + [0.748289, 0.222711, 0.516834], + [0.752312, 0.227133, 0.513149], + [0.756304, 0.231555, 0.509468], + [0.760264, 0.235976, 0.505794], + [0.764193, 0.240396, 0.502126], + [0.768090, 0.244817, 0.498465], + [0.771958, 0.249237, 0.494813], + [0.775796, 0.253658, 0.491171], + [0.779604, 0.258078, 0.487539], + [0.783383, 0.262500, 0.483918], + [0.787133, 0.266922, 0.480307], + [0.790855, 0.271345, 0.476706], + [0.794549, 0.275770, 0.473117], + [0.798216, 0.280197, 0.469538], + [0.801855, 0.284626, 0.465971], + [0.805467, 0.289057, 0.462415], + [0.809052, 0.293491, 0.458870], + [0.812612, 0.297928, 0.455338], + [0.816144, 0.302368, 0.451816], + [0.819651, 0.306812, 0.448306], + [0.823132, 0.311261, 0.444806], + [0.826588, 0.315714, 0.441316], + [0.830018, 0.320172, 0.437836], + [0.833422, 0.324635, 0.434366], + [0.836801, 0.329105, 0.430905], + [0.840155, 0.333580, 0.427455], + [0.843484, 0.338062, 0.424013], + [0.846788, 0.342551, 0.420579], + [0.850066, 0.347048, 0.417153], + [0.853319, 0.351553, 0.413734], + [0.856547, 0.356066, 0.410322], + [0.859750, 0.360588, 0.406917], + [0.862927, 0.365119, 0.403519], + [0.866078, 0.369660, 0.400126], + [0.869203, 0.374212, 0.396738], + [0.872303, 0.378774, 0.393355], + [0.875376, 0.383347, 0.389976], + [0.878423, 0.387932, 0.386600], + [0.881443, 0.392529, 0.383229], + [0.884436, 0.397139, 0.379860], + [0.887402, 0.401762, 0.376494], + [0.890340, 0.406398, 0.373130], + [0.893250, 0.411048, 0.369768], + [0.896131, 0.415712, 0.366407], + [0.898984, 0.420392, 0.363047], + [0.901807, 0.425087, 0.359688], + [0.904601, 0.429797, 0.356329], + [0.907365, 0.434524, 0.352970], + [0.910098, 0.439268, 0.349610], + [0.912800, 0.444029, 0.346251], + [0.915471, 0.448807, 0.342890], + [0.918109, 0.453603, 0.339529], + [0.920714, 0.458417, 0.336166], + [0.923287, 0.463251, 0.332801], + [0.925825, 0.468103, 0.329435], + [0.928329, 0.472975, 0.326067], + [0.930798, 0.477867, 0.322697], + [0.933232, 0.482780, 0.319325], + [0.935630, 0.487712, 0.315952], + [0.937990, 0.492667, 0.312575], + [0.940313, 0.497642, 0.309197], + [0.942598, 0.502639, 0.305816], + [0.944844, 0.507658, 0.302433], + [0.947051, 0.512699, 0.299049], + [0.949217, 0.517763, 0.295662], + [0.951344, 0.522850, 0.292275], + [0.953428, 0.527960, 0.288883], + [0.955470, 0.533093, 0.285490], + [0.957469, 0.538250, 0.282096], + [0.959424, 0.543431, 0.278701], + [0.961336, 0.548636, 0.275305], + [0.963203, 0.553865, 0.271909], + [0.965024, 0.559118, 0.268513], + [0.966798, 0.564396, 0.265118], + [0.968526, 0.569700, 0.261721], + [0.970205, 0.575028, 0.258325], + [0.971835, 0.580382, 0.254931], + [0.973416, 0.585761, 0.251540], + [0.974947, 0.591165, 0.248151], + [0.976428, 0.596595, 0.244767], + [0.977856, 0.602051, 0.241387], + [0.979233, 0.607532, 0.238013], + [0.980556, 0.613039, 0.234646], + [0.981826, 0.618572, 0.231287], + [0.983041, 0.624131, 0.227937], + [0.984199, 0.629718, 0.224595], + [0.985301, 0.635330, 0.221265], + [0.986345, 0.640969, 0.217948], + [0.987332, 0.646633, 0.214648], + [0.988260, 0.652325, 0.211364], + [0.989128, 0.658043, 0.208100], + [0.989935, 0.663787, 0.204859], + [0.990681, 0.669558, 0.201642], + [0.991365, 0.675355, 0.198453], + [0.991985, 0.681179, 0.195295], + [0.992541, 0.687030, 0.192170], + [0.993032, 0.692907, 0.189084], + [0.993456, 0.698810, 0.186041], + [0.993814, 0.704741, 0.183043], + [0.994103, 0.710698, 0.180097], + [0.994324, 0.716681, 0.177208], + [0.994474, 0.722691, 0.174381], + [0.994553, 0.728728, 0.171622], + [0.994561, 0.734791, 0.168938], + [0.994495, 0.740880, 0.166335], + [0.994355, 0.746995, 0.163821], + [0.994141, 0.753137, 0.161404], + [0.993851, 0.759304, 0.159092], + [0.993482, 0.765499, 0.156891], + [0.993033, 0.771720, 0.154808], + [0.992505, 0.777967, 0.152855], + [0.991897, 0.784239, 0.151042], + [0.991209, 0.790537, 0.149377], + [0.990439, 0.796859, 0.147870], + [0.989587, 0.803205, 0.146529], + [0.988648, 0.809579, 0.145357], + [0.987621, 0.815978, 0.144363], + [0.986509, 0.822401, 0.143557], + [0.985314, 0.828846, 0.142945], + [0.984031, 0.835315, 0.142528], + [0.982653, 0.841812, 0.142303], + [0.981190, 0.848329, 0.142279], + [0.979644, 0.854866, 0.142453], + [0.977995, 0.861432, 0.142808], + [0.976265, 0.868016, 0.143351], + [0.974443, 0.874622, 0.144061], + [0.972530, 0.881250, 0.144923], + [0.970533, 0.887896, 0.145919], + [0.968443, 0.894564, 0.147014], + [0.966271, 0.901249, 0.148180], + [0.964021, 0.907950, 0.149370], + [0.961681, 0.914672, 0.150520], + [0.959276, 0.921407, 0.151566], + [0.956808, 0.928152, 0.152409], + [0.954287, 0.934908, 0.152921], + [0.951726, 0.941671, 0.152925], + [0.949151, 0.948435, 0.152178], + [0.946602, 0.955190, 0.150328], + [0.944152, 0.961916, 0.146861], + [0.941896, 0.968590, 0.140956], + [0.940015, 0.975158, 0.131326]] + +_viridis_data = [[0.267004, 0.004874, 0.329415], + [0.268510, 0.009605, 0.335427], + [0.269944, 0.014625, 0.341379], + [0.271305, 0.019942, 0.347269], + [0.272594, 0.025563, 0.353093], + [0.273809, 0.031497, 0.358853], + [0.274952, 0.037752, 0.364543], + [0.276022, 0.044167, 0.370164], + [0.277018, 0.050344, 0.375715], + [0.277941, 0.056324, 0.381191], + [0.278791, 0.062145, 0.386592], + [0.279566, 0.067836, 0.391917], + [0.280267, 0.073417, 0.397163], + [0.280894, 0.078907, 0.402329], + [0.281446, 0.084320, 0.407414], + [0.281924, 0.089666, 0.412415], + [0.282327, 0.094955, 0.417331], + [0.282656, 0.100196, 0.422160], + [0.282910, 0.105393, 0.426902], + [0.283091, 0.110553, 0.431554], + [0.283197, 0.115680, 0.436115], + [0.283229, 0.120777, 0.440584], + [0.283187, 0.125848, 0.444960], + [0.283072, 0.130895, 0.449241], + [0.282884, 0.135920, 0.453427], + [0.282623, 0.140926, 0.457517], + [0.282290, 0.145912, 0.461510], + [0.281887, 0.150881, 0.465405], + [0.281412, 0.155834, 0.469201], + [0.280868, 0.160771, 0.472899], + [0.280255, 0.165693, 0.476498], + [0.279574, 0.170599, 0.479997], + [0.278826, 0.175490, 0.483397], + [0.278012, 0.180367, 0.486697], + [0.277134, 0.185228, 0.489898], + [0.276194, 0.190074, 0.493001], + [0.275191, 0.194905, 0.496005], + [0.274128, 0.199721, 0.498911], + [0.273006, 0.204520, 0.501721], + [0.271828, 0.209303, 0.504434], + [0.270595, 0.214069, 0.507052], + [0.269308, 0.218818, 0.509577], + [0.267968, 0.223549, 0.512008], + [0.266580, 0.228262, 0.514349], + [0.265145, 0.232956, 0.516599], + [0.263663, 0.237631, 0.518762], + [0.262138, 0.242286, 0.520837], + [0.260571, 0.246922, 0.522828], + [0.258965, 0.251537, 0.524736], + [0.257322, 0.256130, 0.526563], + [0.255645, 0.260703, 0.528312], + [0.253935, 0.265254, 0.529983], + [0.252194, 0.269783, 0.531579], + [0.250425, 0.274290, 0.533103], + [0.248629, 0.278775, 0.534556], + [0.246811, 0.283237, 0.535941], + [0.244972, 0.287675, 0.537260], + [0.243113, 0.292092, 0.538516], + [0.241237, 0.296485, 0.539709], + [0.239346, 0.300855, 0.540844], + [0.237441, 0.305202, 0.541921], + [0.235526, 0.309527, 0.542944], + [0.233603, 0.313828, 0.543914], + [0.231674, 0.318106, 0.544834], + [0.229739, 0.322361, 0.545706], + [0.227802, 0.326594, 0.546532], + [0.225863, 0.330805, 0.547314], + [0.223925, 0.334994, 0.548053], + [0.221989, 0.339161, 0.548752], + [0.220057, 0.343307, 0.549413], + [0.218130, 0.347432, 0.550038], + [0.216210, 0.351535, 0.550627], + [0.214298, 0.355619, 0.551184], + [0.212395, 0.359683, 0.551710], + [0.210503, 0.363727, 0.552206], + [0.208623, 0.367752, 0.552675], + [0.206756, 0.371758, 0.553117], + [0.204903, 0.375746, 0.553533], + [0.203063, 0.379716, 0.553925], + [0.201239, 0.383670, 0.554294], + [0.199430, 0.387607, 0.554642], + [0.197636, 0.391528, 0.554969], + [0.195860, 0.395433, 0.555276], + [0.194100, 0.399323, 0.555565], + [0.192357, 0.403199, 0.555836], + [0.190631, 0.407061, 0.556089], + [0.188923, 0.410910, 0.556326], + [0.187231, 0.414746, 0.556547], + [0.185556, 0.418570, 0.556753], + [0.183898, 0.422383, 0.556944], + [0.182256, 0.426184, 0.557120], + [0.180629, 0.429975, 0.557282], + [0.179019, 0.433756, 0.557430], + [0.177423, 0.437527, 0.557565], + [0.175841, 0.441290, 0.557685], + [0.174274, 0.445044, 0.557792], + [0.172719, 0.448791, 0.557885], + [0.171176, 0.452530, 0.557965], + [0.169646, 0.456262, 0.558030], + [0.168126, 0.459988, 0.558082], + [0.166617, 0.463708, 0.558119], + [0.165117, 0.467423, 0.558141], + [0.163625, 0.471133, 0.558148], + [0.162142, 0.474838, 0.558140], + [0.160665, 0.478540, 0.558115], + [0.159194, 0.482237, 0.558073], + [0.157729, 0.485932, 0.558013], + [0.156270, 0.489624, 0.557936], + [0.154815, 0.493313, 0.557840], + [0.153364, 0.497000, 0.557724], + [0.151918, 0.500685, 0.557587], + [0.150476, 0.504369, 0.557430], + [0.149039, 0.508051, 0.557250], + [0.147607, 0.511733, 0.557049], + [0.146180, 0.515413, 0.556823], + [0.144759, 0.519093, 0.556572], + [0.143343, 0.522773, 0.556295], + [0.141935, 0.526453, 0.555991], + [0.140536, 0.530132, 0.555659], + [0.139147, 0.533812, 0.555298], + [0.137770, 0.537492, 0.554906], + [0.136408, 0.541173, 0.554483], + [0.135066, 0.544853, 0.554029], + [0.133743, 0.548535, 0.553541], + [0.132444, 0.552216, 0.553018], + [0.131172, 0.555899, 0.552459], + [0.129933, 0.559582, 0.551864], + [0.128729, 0.563265, 0.551229], + [0.127568, 0.566949, 0.550556], + [0.126453, 0.570633, 0.549841], + [0.125394, 0.574318, 0.549086], + [0.124395, 0.578002, 0.548287], + [0.123463, 0.581687, 0.547445], + [0.122606, 0.585371, 0.546557], + [0.121831, 0.589055, 0.545623], + [0.121148, 0.592739, 0.544641], + [0.120565, 0.596422, 0.543611], + [0.120092, 0.600104, 0.542530], + [0.119738, 0.603785, 0.541400], + [0.119512, 0.607464, 0.540218], + [0.119423, 0.611141, 0.538982], + [0.119483, 0.614817, 0.537692], + [0.119699, 0.618490, 0.536347], + [0.120081, 0.622161, 0.534946], + [0.120638, 0.625828, 0.533488], + [0.121380, 0.629492, 0.531973], + [0.122312, 0.633153, 0.530398], + [0.123444, 0.636809, 0.528763], + [0.124780, 0.640461, 0.527068], + [0.126326, 0.644107, 0.525311], + [0.128087, 0.647749, 0.523491], + [0.130067, 0.651384, 0.521608], + [0.132268, 0.655014, 0.519661], + [0.134692, 0.658636, 0.517649], + [0.137339, 0.662252, 0.515571], + [0.140210, 0.665859, 0.513427], + [0.143303, 0.669459, 0.511215], + [0.146616, 0.673050, 0.508936], + [0.150148, 0.676631, 0.506589], + [0.153894, 0.680203, 0.504172], + [0.157851, 0.683765, 0.501686], + [0.162016, 0.687316, 0.499129], + [0.166383, 0.690856, 0.496502], + [0.170948, 0.694384, 0.493803], + [0.175707, 0.697900, 0.491033], + [0.180653, 0.701402, 0.488189], + [0.185783, 0.704891, 0.485273], + [0.191090, 0.708366, 0.482284], + [0.196571, 0.711827, 0.479221], + [0.202219, 0.715272, 0.476084], + [0.208030, 0.718701, 0.472873], + [0.214000, 0.722114, 0.469588], + [0.220124, 0.725509, 0.466226], + [0.226397, 0.728888, 0.462789], + [0.232815, 0.732247, 0.459277], + [0.239374, 0.735588, 0.455688], + [0.246070, 0.738910, 0.452024], + [0.252899, 0.742211, 0.448284], + [0.259857, 0.745492, 0.444467], + [0.266941, 0.748751, 0.440573], + [0.274149, 0.751988, 0.436601], + [0.281477, 0.755203, 0.432552], + [0.288921, 0.758394, 0.428426], + [0.296479, 0.761561, 0.424223], + [0.304148, 0.764704, 0.419943], + [0.311925, 0.767822, 0.415586], + [0.319809, 0.770914, 0.411152], + [0.327796, 0.773980, 0.406640], + [0.335885, 0.777018, 0.402049], + [0.344074, 0.780029, 0.397381], + [0.352360, 0.783011, 0.392636], + [0.360741, 0.785964, 0.387814], + [0.369214, 0.788888, 0.382914], + [0.377779, 0.791781, 0.377939], + [0.386433, 0.794644, 0.372886], + [0.395174, 0.797475, 0.367757], + [0.404001, 0.800275, 0.362552], + [0.412913, 0.803041, 0.357269], + [0.421908, 0.805774, 0.351910], + [0.430983, 0.808473, 0.346476], + [0.440137, 0.811138, 0.340967], + [0.449368, 0.813768, 0.335384], + [0.458674, 0.816363, 0.329727], + [0.468053, 0.818921, 0.323998], + [0.477504, 0.821444, 0.318195], + [0.487026, 0.823929, 0.312321], + [0.496615, 0.826376, 0.306377], + [0.506271, 0.828786, 0.300362], + [0.515992, 0.831158, 0.294279], + [0.525776, 0.833491, 0.288127], + [0.535621, 0.835785, 0.281908], + [0.545524, 0.838039, 0.275626], + [0.555484, 0.840254, 0.269281], + [0.565498, 0.842430, 0.262877], + [0.575563, 0.844566, 0.256415], + [0.585678, 0.846661, 0.249897], + [0.595839, 0.848717, 0.243329], + [0.606045, 0.850733, 0.236712], + [0.616293, 0.852709, 0.230052], + [0.626579, 0.854645, 0.223353], + [0.636902, 0.856542, 0.216620], + [0.647257, 0.858400, 0.209861], + [0.657642, 0.860219, 0.203082], + [0.668054, 0.861999, 0.196293], + [0.678489, 0.863742, 0.189503], + [0.688944, 0.865448, 0.182725], + [0.699415, 0.867117, 0.175971], + [0.709898, 0.868751, 0.169257], + [0.720391, 0.870350, 0.162603], + [0.730889, 0.871916, 0.156029], + [0.741388, 0.873449, 0.149561], + [0.751884, 0.874951, 0.143228], + [0.762373, 0.876424, 0.137064], + [0.772852, 0.877868, 0.131109], + [0.783315, 0.879285, 0.125405], + [0.793760, 0.880678, 0.120005], + [0.804182, 0.882046, 0.114965], + [0.814576, 0.883393, 0.110347], + [0.824940, 0.884720, 0.106217], + [0.835270, 0.886029, 0.102646], + [0.845561, 0.887322, 0.099702], + [0.855810, 0.888601, 0.097452], + [0.866013, 0.889868, 0.095953], + [0.876168, 0.891125, 0.095250], + [0.886271, 0.892374, 0.095374], + [0.896320, 0.893616, 0.096335], + [0.906311, 0.894855, 0.098125], + [0.916242, 0.896091, 0.100717], + [0.926106, 0.897330, 0.104071], + [0.935904, 0.898570, 0.108131], + [0.945636, 0.899815, 0.112838], + [0.955300, 0.901065, 0.118128], + [0.964894, 0.902323, 0.123941], + [0.974417, 0.903590, 0.130215], + [0.983868, 0.904867, 0.136897], + [0.993248, 0.906157, 0.143936]] + +_zerodis_data = [[1.000000, 1.000000, 1.000000], + [0.268510, 0.009605, 0.335427], + [0.269944, 0.014625, 0.341379], + [0.271305, 0.019942, 0.347269], + [0.272594, 0.025563, 0.353093], + [0.273809, 0.031497, 0.358853], + [0.274952, 0.037752, 0.364543], + [0.276022, 0.044167, 0.370164], + [0.277018, 0.050344, 0.375715], + [0.277941, 0.056324, 0.381191], + [0.278791, 0.062145, 0.386592], + [0.279566, 0.067836, 0.391917], + [0.280267, 0.073417, 0.397163], + [0.280894, 0.078907, 0.402329], + [0.281446, 0.084320, 0.407414], + [0.281924, 0.089666, 0.412415], + [0.282327, 0.094955, 0.417331], + [0.282656, 0.100196, 0.422160], + [0.282910, 0.105393, 0.426902], + [0.283091, 0.110553, 0.431554], + [0.283197, 0.115680, 0.436115], + [0.283229, 0.120777, 0.440584], + [0.283187, 0.125848, 0.444960], + [0.283072, 0.130895, 0.449241], + [0.282884, 0.135920, 0.453427], + [0.282623, 0.140926, 0.457517], + [0.282290, 0.145912, 0.461510], + [0.281887, 0.150881, 0.465405], + [0.281412, 0.155834, 0.469201], + [0.280868, 0.160771, 0.472899], + [0.280255, 0.165693, 0.476498], + [0.279574, 0.170599, 0.479997], + [0.278826, 0.175490, 0.483397], + [0.278012, 0.180367, 0.486697], + [0.277134, 0.185228, 0.489898], + [0.276194, 0.190074, 0.493001], + [0.275191, 0.194905, 0.496005], + [0.274128, 0.199721, 0.498911], + [0.273006, 0.204520, 0.501721], + [0.271828, 0.209303, 0.504434], + [0.270595, 0.214069, 0.507052], + [0.269308, 0.218818, 0.509577], + [0.267968, 0.223549, 0.512008], + [0.266580, 0.228262, 0.514349], + [0.265145, 0.232956, 0.516599], + [0.263663, 0.237631, 0.518762], + [0.262138, 0.242286, 0.520837], + [0.260571, 0.246922, 0.522828], + [0.258965, 0.251537, 0.524736], + [0.257322, 0.256130, 0.526563], + [0.255645, 0.260703, 0.528312], + [0.253935, 0.265254, 0.529983], + [0.252194, 0.269783, 0.531579], + [0.250425, 0.274290, 0.533103], + [0.248629, 0.278775, 0.534556], + [0.246811, 0.283237, 0.535941], + [0.244972, 0.287675, 0.537260], + [0.243113, 0.292092, 0.538516], + [0.241237, 0.296485, 0.539709], + [0.239346, 0.300855, 0.540844], + [0.237441, 0.305202, 0.541921], + [0.235526, 0.309527, 0.542944], + [0.233603, 0.313828, 0.543914], + [0.231674, 0.318106, 0.544834], + [0.229739, 0.322361, 0.545706], + [0.227802, 0.326594, 0.546532], + [0.225863, 0.330805, 0.547314], + [0.223925, 0.334994, 0.548053], + [0.221989, 0.339161, 0.548752], + [0.220057, 0.343307, 0.549413], + [0.218130, 0.347432, 0.550038], + [0.216210, 0.351535, 0.550627], + [0.214298, 0.355619, 0.551184], + [0.212395, 0.359683, 0.551710], + [0.210503, 0.363727, 0.552206], + [0.208623, 0.367752, 0.552675], + [0.206756, 0.371758, 0.553117], + [0.204903, 0.375746, 0.553533], + [0.203063, 0.379716, 0.553925], + [0.201239, 0.383670, 0.554294], + [0.199430, 0.387607, 0.554642], + [0.197636, 0.391528, 0.554969], + [0.195860, 0.395433, 0.555276], + [0.194100, 0.399323, 0.555565], + [0.192357, 0.403199, 0.555836], + [0.190631, 0.407061, 0.556089], + [0.188923, 0.410910, 0.556326], + [0.187231, 0.414746, 0.556547], + [0.185556, 0.418570, 0.556753], + [0.183898, 0.422383, 0.556944], + [0.182256, 0.426184, 0.557120], + [0.180629, 0.429975, 0.557282], + [0.179019, 0.433756, 0.557430], + [0.177423, 0.437527, 0.557565], + [0.175841, 0.441290, 0.557685], + [0.174274, 0.445044, 0.557792], + [0.172719, 0.448791, 0.557885], + [0.171176, 0.452530, 0.557965], + [0.169646, 0.456262, 0.558030], + [0.168126, 0.459988, 0.558082], + [0.166617, 0.463708, 0.558119], + [0.165117, 0.467423, 0.558141], + [0.163625, 0.471133, 0.558148], + [0.162142, 0.474838, 0.558140], + [0.160665, 0.478540, 0.558115], + [0.159194, 0.482237, 0.558073], + [0.157729, 0.485932, 0.558013], + [0.156270, 0.489624, 0.557936], + [0.154815, 0.493313, 0.557840], + [0.153364, 0.497000, 0.557724], + [0.151918, 0.500685, 0.557587], + [0.150476, 0.504369, 0.557430], + [0.149039, 0.508051, 0.557250], + [0.147607, 0.511733, 0.557049], + [0.146180, 0.515413, 0.556823], + [0.144759, 0.519093, 0.556572], + [0.143343, 0.522773, 0.556295], + [0.141935, 0.526453, 0.555991], + [0.140536, 0.530132, 0.555659], + [0.139147, 0.533812, 0.555298], + [0.137770, 0.537492, 0.554906], + [0.136408, 0.541173, 0.554483], + [0.135066, 0.544853, 0.554029], + [0.133743, 0.548535, 0.553541], + [0.132444, 0.552216, 0.553018], + [0.131172, 0.555899, 0.552459], + [0.129933, 0.559582, 0.551864], + [0.128729, 0.563265, 0.551229], + [0.127568, 0.566949, 0.550556], + [0.126453, 0.570633, 0.549841], + [0.125394, 0.574318, 0.549086], + [0.124395, 0.578002, 0.548287], + [0.123463, 0.581687, 0.547445], + [0.122606, 0.585371, 0.546557], + [0.121831, 0.589055, 0.545623], + [0.121148, 0.592739, 0.544641], + [0.120565, 0.596422, 0.543611], + [0.120092, 0.600104, 0.542530], + [0.119738, 0.603785, 0.541400], + [0.119512, 0.607464, 0.540218], + [0.119423, 0.611141, 0.538982], + [0.119483, 0.614817, 0.537692], + [0.119699, 0.618490, 0.536347], + [0.120081, 0.622161, 0.534946], + [0.120638, 0.625828, 0.533488], + [0.121380, 0.629492, 0.531973], + [0.122312, 0.633153, 0.530398], + [0.123444, 0.636809, 0.528763], + [0.124780, 0.640461, 0.527068], + [0.126326, 0.644107, 0.525311], + [0.128087, 0.647749, 0.523491], + [0.130067, 0.651384, 0.521608], + [0.132268, 0.655014, 0.519661], + [0.134692, 0.658636, 0.517649], + [0.137339, 0.662252, 0.515571], + [0.140210, 0.665859, 0.513427], + [0.143303, 0.669459, 0.511215], + [0.146616, 0.673050, 0.508936], + [0.150148, 0.676631, 0.506589], + [0.153894, 0.680203, 0.504172], + [0.157851, 0.683765, 0.501686], + [0.162016, 0.687316, 0.499129], + [0.166383, 0.690856, 0.496502], + [0.170948, 0.694384, 0.493803], + [0.175707, 0.697900, 0.491033], + [0.180653, 0.701402, 0.488189], + [0.185783, 0.704891, 0.485273], + [0.191090, 0.708366, 0.482284], + [0.196571, 0.711827, 0.479221], + [0.202219, 0.715272, 0.476084], + [0.208030, 0.718701, 0.472873], + [0.214000, 0.722114, 0.469588], + [0.220124, 0.725509, 0.466226], + [0.226397, 0.728888, 0.462789], + [0.232815, 0.732247, 0.459277], + [0.239374, 0.735588, 0.455688], + [0.246070, 0.738910, 0.452024], + [0.252899, 0.742211, 0.448284], + [0.259857, 0.745492, 0.444467], + [0.266941, 0.748751, 0.440573], + [0.274149, 0.751988, 0.436601], + [0.281477, 0.755203, 0.432552], + [0.288921, 0.758394, 0.428426], + [0.296479, 0.761561, 0.424223], + [0.304148, 0.764704, 0.419943], + [0.311925, 0.767822, 0.415586], + [0.319809, 0.770914, 0.411152], + [0.327796, 0.773980, 0.406640], + [0.335885, 0.777018, 0.402049], + [0.344074, 0.780029, 0.397381], + [0.352360, 0.783011, 0.392636], + [0.360741, 0.785964, 0.387814], + [0.369214, 0.788888, 0.382914], + [0.377779, 0.791781, 0.377939], + [0.386433, 0.794644, 0.372886], + [0.395174, 0.797475, 0.367757], + [0.404001, 0.800275, 0.362552], + [0.412913, 0.803041, 0.357269], + [0.421908, 0.805774, 0.351910], + [0.430983, 0.808473, 0.346476], + [0.440137, 0.811138, 0.340967], + [0.449368, 0.813768, 0.335384], + [0.458674, 0.816363, 0.329727], + [0.468053, 0.818921, 0.323998], + [0.477504, 0.821444, 0.318195], + [0.487026, 0.823929, 0.312321], + [0.496615, 0.826376, 0.306377], + [0.506271, 0.828786, 0.300362], + [0.515992, 0.831158, 0.294279], + [0.525776, 0.833491, 0.288127], + [0.535621, 0.835785, 0.281908], + [0.545524, 0.838039, 0.275626], + [0.555484, 0.840254, 0.269281], + [0.565498, 0.842430, 0.262877], + [0.575563, 0.844566, 0.256415], + [0.585678, 0.846661, 0.249897], + [0.595839, 0.848717, 0.243329], + [0.606045, 0.850733, 0.236712], + [0.616293, 0.852709, 0.230052], + [0.626579, 0.854645, 0.223353], + [0.636902, 0.856542, 0.216620], + [0.647257, 0.858400, 0.209861], + [0.657642, 0.860219, 0.203082], + [0.668054, 0.861999, 0.196293], + [0.678489, 0.863742, 0.189503], + [0.688944, 0.865448, 0.182725], + [0.699415, 0.867117, 0.175971], + [0.709898, 0.868751, 0.169257], + [0.720391, 0.870350, 0.162603], + [0.730889, 0.871916, 0.156029], + [0.741388, 0.873449, 0.149561], + [0.751884, 0.874951, 0.143228], + [0.762373, 0.876424, 0.137064], + [0.772852, 0.877868, 0.131109], + [0.783315, 0.879285, 0.125405], + [0.793760, 0.880678, 0.120005], + [0.804182, 0.882046, 0.114965], + [0.814576, 0.883393, 0.110347], + [0.824940, 0.884720, 0.106217], + [0.835270, 0.886029, 0.102646], + [0.845561, 0.887322, 0.099702], + [0.855810, 0.888601, 0.097452], + [0.866013, 0.889868, 0.095953], + [0.876168, 0.891125, 0.095250], + [0.886271, 0.892374, 0.095374], + [0.896320, 0.893616, 0.096335], + [0.906311, 0.894855, 0.098125], + [0.916242, 0.896091, 0.100717], + [0.926106, 0.897330, 0.104071], + [0.935904, 0.898570, 0.108131], + [0.945636, 0.899815, 0.112838], + [0.955300, 0.901065, 0.118128], + [0.964894, 0.902323, 0.123941], + [0.974417, 0.903590, 0.130215], + [0.983868, 0.904867, 0.136897], + [0.993248, 0.906157, 0.143936]] + +import matplotlib as mpl + +cmaps = {} +for (name, data) in (('magma', _magma_data), + ('inferno', _inferno_data), + ('plasma', _plasma_data), + ('viridis', _viridis_data), + ('zerodis', _zerodis_data)): + + cmaps[name] = mpl.colors.ListedColormap(data, name=name) + +magma = cmaps['magma'] +inferno = cmaps['inferno'] +plasma = cmaps['plasma'] +viridis = cmaps['viridis'] +zerodis = cmaps['zerodis'] \ No newline at end of file diff --git a/cryspy/ebsd.py b/cryspy/ebsd.py index 4b55235..2cb0dc3 100755 --- a/cryspy/ebsd.py +++ b/cryspy/ebsd.py @@ -1,536 +1,536 @@ -# -*- coding: utf-8 -*- -""" -ovlib.ebsd: EBSD data analysis -=============================================================================== -""" - -class ebsd(object): - ''' - EBSD data object - - Parameters - ---------- - orientations : - - cryspy.xtal.orientation object - - x : numpy array of floats - - x locations of orientations - - y : numpy array of floats - - y locations of orientations - - phaseid : numpy array of ints - - phase identifiers - - Notes - ----- - - At minimum, an EBSD object is an orientation object with a phase and - x and y positions associated with each individual orientation. - - ''' - def __init__(self, orientations=[], x=[], y=[], phaseid=[]): - # initialize the minimum amount of information in order to have EBSD - # data. We need to have x and y coordinates and an orientation measured - # at those coordinates. - self.orientations = orientations - self.x = x - self.y = y - self.phaseid = phaseid - - #def __repr__(self): - # TODO: Add __repr__ method - # Needs to show number of phases, number of data points, etc. - - def calc_scanstepsize(self): - ''' adds step and scan size data to the ebsd object - - EBSD class method that uses x and y location information to add step - size and scan size data to the ebsd object. - ''' - import numpy as np - nr = np.size(self.x) - rows, cols = scansize(self.x, self.y) - xstp, ystp = scanstep(self.x, self.y, rows, cols) - idx = scanrowlims(self.x, self.y, rows, cols) - self.rows = rows - self.cols = cols - self.xstp = xstp - self.ystp = ystp - self.idx = idx - self.nr = nr - - def prepare_for_plotting(self): - ''' adds information to ebsd object required for plotting - - class method that adds information necessary for plotting to the object - ''' - import numpy as np - - if not hasattr(self, 'x') or not hasattr(self, 'y'): - print('x and y coordinates are needed for plotting ebsd data.') #TODO: Use warnings package? - - if not hasattr(self, 'rows') or not hasattr(self, 'cols'): - rows, cols = scansize(self.x, self.y) - self.rows = rows - self.cols = cols - del rows, cols - - if not hasattr(self, 'xstp') or not hasattr(self, 'ystp'): - xstp, ystp = scanstep(self.x, self.y, self.rows, self.cols) - self.xstp = xstp - self.ystp = ystp - del xstp, ystp - - if not hasattr(self, 'idx'): - idx = scanrowlims(self.x, self.y, self.rows, self.cols) - self.idx = idx - del idx - - if not hasattr(self, 'nr'): - nr = np.size(self.x) - self.nr = nr - del nr - - # Kernel vertices and faces are needed for plotting similar to matlab's - # patch function using matplotlib - v, p = kernverts(self.x, self.y, self.rows, self.cols, - self.xstp, self.ystp, self.idx) - f = kernfaces(v, p, self.idx, self.rows, self.cols, self.nr) - self.v = v - self.p = p - self.f = f - - # For plotting using opengl, we need a different format of vertices and - # faces - tv, vf = triverts(self.nr, self.v, self.f, self.cols) - self.tv = tv - self.vf = vf - -#------------------------------------------------------------------------------- - -def scansize(x, y): - """ - scansize returns the number of rows and columns in oimdata on either - a square or hexagonal grid. Two values for the # columns are - returned in the case of a hex grid: the first for odd and the - second for even. See hdrload for correct importing of the - data matrix. - - Notes - ----- - - This one isn't as straightforward as you might assume. - The data can come on either a hex grid or a square - grid. When on a hex grid, the data is staggered between - rows, so the number of data points differs between rows, - and the x-coordinate varies while the y-coordinate is - constant within each row. - - If there is no header for the data, or if the header doesn't match the - file contents, we can figure out this info from looking at the XY data. - - Ultimately, the old-school way I am using here to count - the number of vertical pixels below isn't efficient but - doesn't take very long. - - To get the oimdata input variable, see hdrload. - - If the input file is on a hex grid, columns returns first the odd - then the even number. - """ - import numpy as np - - if np.size(x) != np.size(y): - print('scansize: the lengths of x and y are not equal. this may result\ - in errors.') #TODO: use warnings package? - - # count the number of pixels in the horizontal direction - # i.e, the maximum number of columns - # (even numbered rows have fewer columns in the scan) and - # the first row (which is always ==0 in my TSL scans) is - # by definition an odd row. - cols1=np.sum(y==0) # cols in first row - - # if the first entry for the x-coordinate value in the first row - # doesn't match the first entry for the x-coordinate value in the - # second row, then it's hexagonal. This could maybe also be done - # using the 'unique' function. - if x[cols1] != x[0]: - rows = np.sum(x==0) + np.sum(x==x[cols1]) - cols = np.array([1,2], dtype=np.uint32) - cols[0]=cols1 - cols[1]=cols[0]-1 - else: - nr = np.size(x) - rows= np.uint32(nr / cols1) - cols= cols1 - - return rows, cols - -#------------------------------------------------------------------------------- - -def scanstep(x, y, nr, nc): - ''' finds the x and y step sizes of an ebsd scan - - Parameters - ---------- - x : array - x locations - y : array - y locations - nr : scalar - number of rows - nc : array - number of columns (can be 2 vector for hexagonal grid) - - Returns - ------- - xstep : scalar - step size in x direction - ystep : scalar - step size in y direction - ''' - import numpy as np - - if np.size(x) != np.size(y): - print('scanstep: the lengths of x and y are not equal. this may result\ - in errors.') # TODO: use warnings package? - - # Get the scan size - if np.size(nc)==2: # hexagonal scan - nc=nc[0]; - - # Get the hex data x step size - if nr<5: - # we need at least two points in the row to get the step size - # empirically (or we could read the header, but I think this method is - # generally better in case the header doesn't match the data). - # This means a hex scan size of at least 5 points is a reasonable - # minimum: 2 points in the first and third rows, 1 point in the middle - # row. - print('scanstep error: at least 5 data points required') # TODO: warnings? - else: - xstep = x[1]-x[0] - ystep = y[nc]-y[nc-1] - - return xstep, ystep - -#------------------------------------------------------------------------------- - -def scanrowlims(x, y, nrows, ncols): - ''' - - Notes - ----- - SCANROWLIMS finds the indices that correspond to the left and right - limits of each row. Assumes a regular hexagonal or square grid. - - The algorithm below is slow, but since it typically only needs to be run - once for a scan in a session, it seems ok for the time being. - ''' - import numpy as np - import righthand as rh - - if np.size(x) != np.size(y): - print('scanrowlims: the lengths of x and y are not equal. this may \ - result in errors.') # TODO: warnings? - - if np.size(ncols)==2: # then we have a hex grid - - # Find the indices corresponding to the left and right edges of each row - rowlims=np.zeros([nrows, 2], dtype=np.uint32) - - l = 0 - for i in range(1, nrows+1): - - b = ~rh.iseven(i) - if b: - r = l + ncols[0] - 1 - else: - r = l + ncols[0] - 2 - - rowlims[i-1, 0] = l - rowlims[i-1, 1] = r - - l = r + 1 - - elif np.size(ncols)==1: # then we have a square grid - - rowlims=np.zeros([nrows, 2], dtype=np.uint32) - - l = 0 - for i in range(1, nrows+1): - - r = l + ncols - 1 - - rowlims[i-1, 0] = l - rowlims[i-1, 1] = r - - l = r + 1 - - return rowlims - -#------------------------------------------------------------------------------- - -def kernverts(x, y, rows, cols, xstp, ystp, idx, clipverts=True): - ''' - KERNVERTS returns the grid of vertices corresponding to the boundaries of - the data point kernels in an ebsd scan, as well as the indices corresponding - to the edges of the rows in this grid. The results from the edges here - differ from scanrowlims: In scanrowlims, we return the limits of the rows - for the measurement grid. Here, we return the limits of the rows of the - vertices grid. - - inputs: x and y coordinates of scan - ''' - import numpy as np - import righthand as rh - - if np.size(cols)==2: - - # Make hex grid - xv1 = xstp / 2.0 - yv1 = ystp / np.sqrt(3.0) - yv2 = yv1 / 2.0 - - # first row, we need to cover the indices that are above the face nodes - v = np.zeros([(rows+1)*(2*cols[0]+1), 2], dtype=float) - p = np.zeros([2 * rows + 2, 2], dtype=np.uint32) - yy = y[[idx[0][0]]] - v1 = cols[0] - v2 = cols[0] + 1 - xvs1 = np.arange(1, v1+1) * xstp - xstp - xvs2 = np.arange(1, v2+1) * xstp - xstp - v[0:v1, 0] = xvs1 - v[0:v1, 1] = np.tile(yy - yv1, v1) - p[0, 0] = 0 - p[0, 1] = v1-1 - - v[v1:v1+v2, 0] = xvs2 - xv1 - v[v1:v1+v2, 1] = np.tile(yy - yv2, v2) - p[1, 0] = v1 - p[1, 1] = v1+v2-1 - - # All other nodes - nV=v1+v2 - j=2 - for i in range(1, rows+1): - yy = y[idx[i-1][0]] - v1 = cols[0] - v2 = cols[0] + 1 - xvs1 = np.arange(1, v1+1) * xstp - xstp - xvs2 = np.arange(1, v2+1) * xstp - xstp - - if ~rh.iseven(i): - - v[nV:nV+v2, 0] = xvs2-xv1 - v[nV:nV+v2, 1] = np.tile(yy+yv2, v2) - p[j, 0] = nV - p[j, 1] = nV+v2-1 - j += 1 - v[nV+v2:nV+v2+v1, 0] = xvs1 - v[nV+v2:nV+v2+v1, 1] = np.tile(yy+yv1, v1) - p[j, 0] = nV+v2 - p[j, 1] = nV+v2+v1-1 - j += 1 - nV=nV+v1+v2 - - else: - - v[nV:nV+v1, 0] = xvs1 - v[nV:nV+v1, 1] = np.tile(yy+yv2, v1) - p[j, 0] = nV - p[j, 1] = nV+v1-1 - j += 1 - v[nV+v1:nV+v1+v2, 0] = xvs2-xv1 - v[nV+v1:nV+v1+v2, 1] = np.tile(yy+yv1, v2) - p[j, 0] = nV+v1 - p[j, 1] = nV+v1+v2-1 - j += 1 - nV=nV+v1+v2 - - elif np.size(cols)==1: # square grid - - v = np.zeros([(cols+1)*(rows+1), 2], dtype=float) - p = np.zeros([rows+1, 2], dtype=np.uint32) - - v[:,0] = np.tile(xstp*np.arange(0,cols+1),rows+1)-xstp/2.0 - v[:,1] = np.reshape(np.tile(ystp*np.arange(0,rows+1), - (cols+1,1)).T-ystp/2.0, -1) - - p[:,0] = np.arange(1,(rows+1)*(cols+1),cols+1)-1 - p[:,1] = np.arange(1,(rows+1)*(cols+1),cols+1)-1+cols - - if clipverts==True and np.size(cols)==2: - # Fix the top row of points - v[p[0,0]:p[0,1]+1][:,1] = np.tile(y[idx[0,0]]-np.sqrt(np.spacing(1)), - (p[0,1]-p[0,0]+1)) - v[p[1,0]:p[1,1]+1][:,1] = np.tile(y[idx[0,0]], (p[1,1]-p[1,0]+1)) - - # Fix the bottom row of points - ploc = np.shape(p)[0]-1 - iloc = np.shape(idx)[0]-1 - v[p[ploc,0]:p[ploc,1]+1][:,1] = np.tile(y[idx[iloc,0]] + - np.sqrt(np.spacing(1)), - (p[ploc,1]-p[ploc,0]+1)) - v[p[ploc-1,0]:p[ploc-1,1]+1][:,1] = np.tile(y[idx[iloc,0]], - (p[ploc-1,1]-p[ploc-1,0]+1)) - - # Fix the left and right columns of points - for i in range(0,np.size(p[:,0])-1): - valmin = np.amin(x)-np.sqrt(np.spacing(1)) - valmax = np.amax(x)+np.sqrt(np.spacing(1)) - v[p[i,0]][0] = valmin - v[p[i,1]][0] = valmax - - - return v, p # v is the array of vertices, p is the array of edge indices - -#------------------------------------------------------------------------------ - -def kernfaces(v, p, idx, rows, cols, nr): - ''' - KERNFACES associates the faces of the kernels with their surrounding - vertices, for both hexagonal and square grid patterns. - ''' - import numpy as np - import righthand as rh - - # Associate faces with vertices - ndx=0; - - if np.size(cols)==2: # hex grid - - pb = rh.progbar(finalcount=nr, - message='ASSOCIATING FACES WITH VERTICES') - - f=np.zeros([nr,6], dtype=np.uint32) - for i in range(1,rows+1): - - j=(i-1)*2+1 - - if ~rh.iseven(i): # odd row - - tmp=np.array([ - np.arange( p[j-1,0] , p[j-1,1]+1), - np.arange( p[j ,0], p[j ,1] ), - np.arange( p[j ,0]+1, p[j ,1]+1), - np.arange( p[j+1,0], p[j+1,1] ), - np.arange( p[j+1,0]+1, p[j+1,1]+1), - np.arange( p[j+2,0], p[j+2,1]+1) ]).T - - else: # even row - - tmp=np.array([ - np.arange(p[j-1,0]+1, p[j-1,1] ), - np.arange(p[j ,0], p[j ,1] ), - np.arange(p[j ,0]+1, p[j ,1]+1), - np.arange(p[j+1,0], p[j+1,1] ), - np.arange(p[j+1,0]+1, p[j+1,1]+1), - np.arange(p[j+2,0]+1, p[j+2,1] ) ]).T - - - ntmp = np.size(tmp) / 6 - - for k in range(0, np.int(ntmp)): #XXX: Converting to int here could cause an issue if ntmp is not actually an int - f[ndx, 0:6] = [tmp[k,0], tmp[k,1], tmp[k,3], tmp[k,5], tmp[k,4], tmp[k,2]] - ndx += 1 - - pb.update(i) - - pb.update(-1) - - elif np.size(cols)==1: # square grid - - f = np.zeros([nr, 4], dtype=np.uint32) - f1 = np.arange(0, cols, dtype=np.uint32) - f2 = f1 + 1 - for i in range(0,rows): - f3 = np.arange(0, cols, dtype=np.uint32) + (cols + 1) * (i + 1) - f4 = f3 + 1 - f[idx[i,0]:idx[i,1]+1, 0] = f3 - f[idx[i,0]:idx[i,1]+1, 1] = f1 - f[idx[i,0]:idx[i,1]+1, 2] = f2 - f[idx[i,0]:idx[i,1]+1, 3] = f4 - f1 = f3 - f2 = f4 - - return f - -#------------------------------------------------------------------------------ - -def triverts(n, v, f, cols): - #TODO: Documentation! - - import numpy as np - - # Add third dimension to vertex data - tv = np.zeros([np.int(np.size(v)/2.0), 3]) # there are two columns, so size/2 will always be int - tv[:,0] = v[:,0] - tv[:,1] = v[:,1] - del v - - # Create the face array - if np.size(cols)==2: # hex grid - - vf = np.zeros([n*4,3], dtype=np.uint32) - k = 0 - for i in range(0,n): - - # triangle 1 - vf[k,0] = f[i,0] - vf[k,1] = f[i,1] - vf[k,2] = f[i,5] - k += 1 - - # triangle 2 - vf[k,0] = f[i,5] - vf[k,1] = f[i,1] - vf[k,2] = f[i,4] - k += 1 - - # triangle 3 - vf[k,0] = f[i,2] - vf[k,1] = f[i,4] - vf[k,2] = f[i,1] - k += 1 - - # triangle 4 - vf[k,0] = f[i,3] - vf[k,1] = f[i,4] - vf[k,2] = f[i,2] - k += 1 - - else: # square grid - - vf = np.zeros([n*2,3], dtype=np.uint32) - k = 0 - for i in range(0,n): - - # triangle 1 - vf[k,0] = f[i,1] - vf[k,1] = f[i,2] - vf[k,2] = f[i,0] - k += 1 - - # triangle 2 - vf[k,0] = f[i,2] - vf[k,1] = f[i,3] - vf[k,2] = f[i,0] - k += 1 - - return tv, vf - -#------------------------------------------------------------------------------ - - - - - - - - +# -*- coding: utf-8 -*- +""" +ovlib.ebsd: EBSD data analysis +=============================================================================== +""" + +class ebsd(object): + ''' + EBSD data object + + Parameters + ---------- + orientations : + + cryspy.xtal.orientation object + + x : numpy array of floats + + x locations of orientations + + y : numpy array of floats + + y locations of orientations + + phaseid : numpy array of ints + + phase identifiers + + Notes + ----- + - At minimum, an EBSD object is an orientation object with a phase and + x and y positions associated with each individual orientation. + + ''' + def __init__(self, orientations=[], x=[], y=[], phaseid=[]): + # initialize the minimum amount of information in order to have EBSD + # data. We need to have x and y coordinates and an orientation measured + # at those coordinates. + self.orientations = orientations + self.x = x + self.y = y + self.phaseid = phaseid + + #def __repr__(self): + # TODO: Add __repr__ method + # Needs to show number of phases, number of data points, etc. + + def calc_scanstepsize(self): + ''' adds step and scan size data to the ebsd object + + EBSD class method that uses x and y location information to add step + size and scan size data to the ebsd object. + ''' + import numpy as np + nr = np.size(self.x) + rows, cols = scansize(self.x, self.y) + xstp, ystp = scanstep(self.x, self.y, rows, cols) + idx = scanrowlims(self.x, self.y, rows, cols) + self.rows = rows + self.cols = cols + self.xstp = xstp + self.ystp = ystp + self.idx = idx + self.nr = nr + + def prepare_for_plotting(self): + ''' adds information to ebsd object required for plotting + + class method that adds information necessary for plotting to the object + ''' + import numpy as np + + if not hasattr(self, 'x') or not hasattr(self, 'y'): + print('x and y coordinates are needed for plotting ebsd data.') + + if not hasattr(self, 'rows') or not hasattr(self, 'cols'): + rows, cols = scansize(self.x, self.y) + self.rows = rows + self.cols = cols + del rows, cols + + if not hasattr(self, 'xstp') or not hasattr(self, 'ystp'): + xstp, ystp = scanstep(self.x, self.y, self.rows, self.cols) + self.xstp = xstp + self.ystp = ystp + del xstp, ystp + + if not hasattr(self, 'idx'): + idx = scanrowlims(self.x, self.y, self.rows, self.cols) + self.idx = idx + del idx + + if not hasattr(self, 'nr'): + nr = np.size(self.x) + self.nr = nr + del nr + + # Kernel vertices and faces are needed for plotting similar to matlab's + # patch function using matplotlib + v, p = kernverts(self.x, self.y, self.rows, self.cols, + self.xstp, self.ystp, self.idx) + f = kernfaces(v, p, self.idx, self.rows, self.cols, self.nr) + self.v = v + self.p = p + self.f = f + + # For plotting using opengl, we need a different format of vertices and + # faces + tv, vf = triverts(self.nr, self.v, self.f, self.cols) + self.tv = tv + self.vf = vf + +#------------------------------------------------------------------------------- + +def scansize(x, y): + """ + scansize returns the number of rows and columns in oimdata on either + a square or hexagonal grid. Two values for the # columns are + returned in the case of a hex grid: the first for odd and the + second for even. See hdrload for correct importing of the + data matrix. + + Notes + ----- + + This one isn't as straightforward as you might assume. + The data can come on either a hex grid or a square + grid. When on a hex grid, the data is staggered between + rows, so the number of data points differs between rows, + and the x-coordinate varies while the y-coordinate is + constant within each row. + + If there is no header for the data, or if the header doesn't match the + file contents, we can figure out this info from looking at the XY data. + + Ultimately, the old-school way I am using here to count + the number of vertical pixels below isn't efficient but + doesn't take very long. + + To get the oimdata input variable, see hdrload. + + If the input file is on a hex grid, columns returns first the odd + then the even number. + """ + import numpy as np + + if np.size(x) != np.size(y): + print('scansize: the lengths of x and y are not equal. this may result\ + in errors.') + + # count the number of pixels in the horizontal direction + # i.e, the maximum number of columns + # (even numbered rows have fewer columns in the scan) and + # the first row (which is always ==0 in my TSL scans) is + # by definition an odd row. + cols1=np.sum(y==0) # cols in first row + + # if the first entry for the x-coordinate value in the first row + # doesn't match the first entry for the x-coordinate value in the + # second row, then it's hexagonal. This could maybe also be done + # using the 'unique' function. + if x[cols1] != x[0]: + rows = np.sum(x==0) + np.sum(x==x[cols1]) + cols = np.array([1,2], dtype=np.uint32) + cols[0]=cols1 + cols[1]=cols[0]-1 + else: + nr = np.size(x) + rows= np.uint32(nr / cols1) + cols= cols1 + + return rows, cols + +#------------------------------------------------------------------------------- + +def scanstep(x, y, nr, nc): + ''' finds the x and y step sizes of an ebsd scan + + Parameters + ---------- + x : array + x locations + y : array + y locations + nr : scalar + number of rows + nc : array + number of columns (can be 2 vector for hexagonal grid) + + Returns + ------- + xstep : scalar + step size in x direction + ystep : scalar + step size in y direction + ''' + import numpy as np + + if np.size(x) != np.size(y): + print('scanstep: the lengths of x and y are not equal. this may result\ + in errors.') + + # Get the scan size + if np.size(nc)==2: # hexagonal scan + nc=nc[0]; + + # Get the hex data x step size + if nr<5: + # we need at least two points in the row to get the step size + # empirically (or we could read the header, but I think this method is + # generally better in case the header doesn't match the data). + # This means a hex scan size of at least 5 points is a reasonable + # minimum: 2 points in the first and third rows, 1 point in the middle + # row. + print('scanstep error: at least 5 data points required') + else: + xstep = x[1]-x[0] + ystep = y[nc]-y[nc-1] + + return xstep, ystep + +#------------------------------------------------------------------------------- + +def scanrowlims(x, y, nrows, ncols): + ''' + + Notes + ----- + SCANROWLIMS finds the indices that correspond to the left and right + limits of each row. Assumes a regular hexagonal or square grid. + + The algorithm below is slow, but since it typically only needs to be run + once for a scan in a session, it seems ok for the time being. + ''' + import numpy as np + import cryspy.util as util + + if np.size(x) != np.size(y): + print('scanrowlims: the lengths of x and y are not equal. this may \ + result in errors.') + + if np.size(ncols)==2: # then we have a hex grid + + # Find the indices corresponding to the left and right edges of each row + rowlims=np.zeros([nrows,2], dtype=np.uint32) + + l = 0 + for i in range(1, nrows+1): + + b = ~util.iseven(i) + if b[0]: + r = l + ncols[0] - 1 + else: + r = l + ncols[0] - 2 + + rowlims[i-1, 0] = l + rowlims[i-1, 1] = r + + l = r + 1 + + elif np.size(ncols)==1: # then we have a square grid + + rowlims=np.zeros([nrows,2]) + + l = 0 + for i in range(1, nrows+1): + + r = l + ncols - 1 + + rowlims[i-1, 0] = l + rowlims[i-1, 1] = r + + l = r + 1 + + return rowlims + +#------------------------------------------------------------------------------- + +def kernverts(x, y, rows, cols, xstp, ystp, idx, clipverts=True): + ''' + KERNVERTS returns the grid of vertices corresponding to the boundaries of + the data point kernels in an ebsd scan, as well as the indices corresponding + to the edges of the rows in this grid. The results from the edges here + differ from scanrowlims: In scanrowlims, we return the limits of the rows + for the measurement grid. Here, we return the limits of the rows of the + vertices grid. + + inputs: x and y coordinates of scan + ''' + import numpy as np + import cryspy.util as util + + if np.size(cols)==2: + + # Make hex grid + xv1 = xstp / 2.0 + yv1 = ystp / np.sqrt(3.0) + yv2 = yv1 / 2.0 + + # first row, we need to cover the indices that are above the face nodes + v = np.zeros([(rows+1)*(2*cols[0]+1), 2], dtype=float) + p = np.zeros([2 * rows + 2, 2], dtype=np.uint32) + yy = y[[idx[0][0]]] + v1 = cols[0] + v2 = cols[0] + 1 + xvs1 = np.arange(1, v1+1) * xstp - xstp + xvs2 = np.arange(1, v2+1) * xstp - xstp + v[0:v1, 0] = xvs1 + v[0:v1, 1] = np.tile(yy - yv1, v1) + p[0, 0] = 0 + p[0, 1] = v1-1 + + v[v1:v1+v2, 0] = xvs2 - xv1 + v[v1:v1+v2, 1] = np.tile(yy - yv2, v2) + p[1, 0] = v1 + p[1, 1] = v1+v2-1 + + # All other nodes + nV=v1+v2 + j=2 + for i in range(1, rows+1): + yy = y[idx[i-1][0]] + v1 = cols[0] + v2 = cols[0] + 1 + xvs1 = np.arange(1, v1+1) * xstp - xstp + xvs2 = np.arange(1, v2+1) * xstp - xstp + + if ~util.iseven(i): + + v[nV:nV+v2, 0] = xvs2-xv1 + v[nV:nV+v2, 1] = np.tile(yy+yv2, v2) + p[j, 0] = nV + p[j, 1] = nV+v2-1 + j += 1 + v[nV+v2:nV+v2+v1, 0] = xvs1 + v[nV+v2:nV+v2+v1, 1] = np.tile(yy+yv1, v1) + p[j, 0] = nV+v2 + p[j, 1] = nV+v2+v1-1 + j += 1 + nV=nV+v1+v2 + + else: + + v[nV:nV+v1, 0] = xvs1 + v[nV:nV+v1, 1] = np.tile(yy+yv2, v1) + p[j, 0] = nV + p[j, 1] = nV+v1-1 + j += 1 + v[nV+v1:nV+v1+v2, 0] = xvs2-xv1 + v[nV+v1:nV+v1+v2, 1] = np.tile(yy+yv1, v2) + p[j, 0] = nV+v1 + p[j, 1] = nV+v1+v2-1 + j += 1 + nV=nV+v1+v2 + + elif np.size(cols)==1: # square grid + + v = np.zeros([(cols+1)*(rows+1), 2], dtype=float) + p = np.zeros([rows+1, 2], dtype=np.uint32) + + v[:,0] = np.tile(xstp*np.arange(0,cols+1),rows+1)-xstp/2.0 + v[:,1] = np.reshape(np.tile(ystp*np.arange(0,rows+1), + (cols+1,1)).T-ystp/2.0, -1) + + p[:,0] = np.arange(1,(rows+1)*(cols+1),cols+1)-1 + p[:,1] = np.arange(1,(rows+1)*(cols+1),cols+1)-1+cols + + if clipverts==True and np.size(cols)==2: + # Fix the top row of points + v[p[0,0]:p[0,1]+1][:,1] = np.tile(y[idx[0,0]]-np.sqrt(np.spacing(1)), + (p[0,1]-p[0,0]+1)) + v[p[1,0]:p[1,1]+1][:,1] = np.tile(y[idx[0,0]], (p[1,1]-p[1,0]+1)) + + # Fix the bottom row of points + ploc = np.shape(p)[0]-1 + iloc = np.shape(idx)[0]-1 + v[p[ploc,0]:p[ploc,1]+1][:,1] = np.tile(y[idx[iloc,0]] + + np.sqrt(np.spacing(1)), + (p[ploc,1]-p[ploc,0]+1)) + v[p[ploc-1,0]:p[ploc-1,1]+1][:,1] = np.tile(y[idx[iloc,0]], + (p[ploc-1,1]-p[ploc-1,0]+1)) + + # Fix the left and right columns of points + for i in range(0,np.size(p[:,0])-1): + valmin = np.amin(x)-np.sqrt(np.spacing(1)) + valmax = np.amax(x)+np.sqrt(np.spacing(1)) + v[p[i,0]][0] = valmin + v[p[i,1]][0] = valmax + + + return v, p # v is the array of vertices, p is the array of edge indices + +#------------------------------------------------------------------------------ + +def kernfaces(v, p, idx, rows, cols, nr): + ''' + KERNFACES associates the faces of the kernels with their surrounding + vertices, for both hexagonal and square grid patterns. + ''' + import numpy as np + import cryspy.util as util + + # Associate faces with vertices + ndx=0; + + if np.size(cols)==2: # hex grid + + pb = util.progbar(finalcount=nr, + message='ASSOCIATING FACES WITH VERTICES') + + f=np.zeros([nr,6], dtype=np.uint32) + for i in range(1,rows+1): + + j=(i-1)*2+1 + + if ~util.iseven(i): # odd row + + tmp=np.array([ + np.arange( p[j-1,0] , p[j-1,1]+1), + np.arange( p[j ,0], p[j ,1] ), + np.arange( p[j ,0]+1, p[j ,1]+1), + np.arange( p[j+1,0], p[j+1,1] ), + np.arange( p[j+1,0]+1, p[j+1,1]+1), + np.arange( p[j+2,0], p[j+2,1]+1) ]).T + + else: # even row + + tmp=np.array([ + np.arange(p[j-1,0]+1, p[j-1,1] ), + np.arange(p[j ,0], p[j ,1] ), + np.arange(p[j ,0]+1, p[j ,1]+1), + np.arange(p[j+1,0], p[j+1,1] ), + np.arange(p[j+1,0]+1, p[j+1,1]+1), + np.arange(p[j+2,0]+1, p[j+2,1] ) ]).T + + + ntmp = np.size(tmp)/6 + + for k in range(0, ntmp): + f[ndx, 0:6] = [tmp[k,0], tmp[k,1], tmp[k,3], tmp[k,5], tmp[k,4], tmp[k,2]] + ndx += 1 + + pb.update(i) + + pb.update(-1) + + elif np.size(cols)==1: # square grid + + f = np.zeros([nr, 4], dtype=np.uint32) + f1 = np.arange(0, cols, dtype=np.uint32) + f2 = f1 + 1 + for i in range(0,rows): + f3 = np.arange(0, cols, dtype=np.uint32) + (cols + 1) * (i + 1) + f4 = f3 + 1 + f[idx[i,0]:idx[i,1]+1, 0] = f3 + f[idx[i,0]:idx[i,1]+1, 1] = f1 + f[idx[i,0]:idx[i,1]+1, 2] = f2 + f[idx[i,0]:idx[i,1]+1, 3] = f4 + f1 = f3 + f2 = f4 + + return f + +#------------------------------------------------------------------------------ + +def triverts(n, v, f, cols): + #TODO: Documentation! + + import numpy as np + + # Add third dimension to vertex data + tv = np.zeros([np.size(v)/2, 3]) + tv[:,0] = v[:,0] + tv[:,1] = v[:,1] + del v + + # Create the face array + if np.size(cols)==2: # hex grid + + vf = np.zeros([n*4,3], dtype=np.uint32) + k = 0 + for i in range(0,n): + + # triangle 1 + vf[k,0] = f[i,0] + vf[k,1] = f[i,1] + vf[k,2] = f[i,5] + k += 1 + + # triangle 2 + vf[k,0] = f[i,5] + vf[k,1] = f[i,1] + vf[k,2] = f[i,4] + k += 1 + + # triangle 3 + vf[k,0] = f[i,2] + vf[k,1] = f[i,4] + vf[k,2] = f[i,1] + k += 1 + + # triangle 4 + vf[k,0] = f[i,3] + vf[k,1] = f[i,4] + vf[k,2] = f[i,2] + k += 1 + + else: # square grid + + vf = np.zeros([n*2,3], dtype=np.uint32) + k = 0 + for i in range(0,n): + + # triangle 1 + vf[k,0] = f[i,1] + vf[k,1] = f[i,2] + vf[k,2] = f[i,0] + k += 1 + + # triangle 2 + vf[k,0] = f[i,2] + vf[k,1] = f[i,3] + vf[k,2] = f[i,0] + k += 1 + + return tv, vf + +#------------------------------------------------------------------------------ + + + + + + + + diff --git a/cryspy/empirics.py b/cryspy/empirics.py new file mode 100755 index 0000000..a307422 --- /dev/null +++ b/cryspy/empirics.py @@ -0,0 +1,1021 @@ +def CSLcrit(name='Brandon', sigmas=None): + ''' calculate CSL criteria + + **CSLcrit** calculates the angular deviation from the ideal misorientation + relationship allowed by different *named* coincident site lattice (CSL) + criteria of the form: + + ..math :: + \theta_c=\theta_0 / sigma^eta + + Parameters + ---------- + name : {'Brandon', 'Pumphrey', 'Ishida-McLean', 'Palumbo-Aust'} + + CSL criteria name. Defaults to Brandon. Also accepts 'B' for Brandon, + 'P' for Pumphrey, 'IM' for Ishida-McLean, and 'PA' for Palumbo-Aust. + String argument is case insensitive. Dash can be replaced by '&' or + 'and' with or without spaces around it. + + sigmas : None + + Sigma values of interest. If None, returns all values from 1 to 51. + + Returns + ------- + thc : numpy float array + + Notes + ----- + This is a Python translation of a Matlab function of the same name written + by EJP 2011-05-02 while at Ruhr-Universitaet Bochum. This function was used + for the analysis in references [1]_ and [2]_. + + For general reference on Coincident Site Lattice theory, see + reference [3]_. The named CSL criteria used here from the following: + Brandon [4]_, Pumphrey [5]_, Palumbo & Aust [6]_, and Ishida & McLean [7]_. + + References + ---------- + .. [1] Otto et al, J Mater Sci 47 (2012) p2915-2927. + .. [2] Otto et al, Acta Mater 60 (2012) p2982-2998. + .. [3] Warrington & Boon, Acta Metall 23 (1975) p599 + .. [4] Brandon, Acta Metall 14 (1966) p1479 + .. [5] Pumphrey, in Grain Boundary Structure & Properties, London: Academic Press (1976) p139 + .. [6] Palumbo & Aust, Acta Metall 38 (1990) p2343 + .. [7] Ishida & McLean, Philos Mag 27 (1973) p1125 + ''' + import numpy as np + import warnings + + # Remove + name = name.replace('&', '').replace('and', '').replace('-','') + name = name.replace(' ', '').lower() + + if sigmas == None: + + sig = np.arange(1, 53, 2, dtype=np.float) + + else: + + sig = np.asanyarray(sigmas, dtype=np.float) + + if np.any(sig % 2): + + warnings.warn('Only odd values of sigma are valid. ' + \ + 'Odd values have been removed.') + + if (name == 'brandon') or (name == 'b'): + + th0 = 15.0 + eta = 1.0 / 2.0 + + elif (name == 'pumphrey') or (name =='p'): + + th0 = 15.0 + eta = 2.0 / 3.0 + + elif (name == 'palumboaust') or (name == 'pa'): + + th0 = 15.0 + eta = 5.0 / 6.0 + + elif (name == 'ishidamclean') or (name == 'im'): + + th0 = 8.0 + eta = 1.0 + + else: + + warnings.warn('CSLcrit: unrecognized named criterion. ' + \ + 'Proceeding with Brandon criterion.') + + th0 = 15.0 + eta = 1.0 / 2.0 + + # Convert theta to radians + th0 = th0 * np.pi / 180.0 + + # Calculate criteria + thc = th0 / sig**eta + + return thc + +# ---------------------------------------------------------------------------- + +#def genCSLs(sigmas=None): +#function [CSL,SQ,SV,SN]=GenCSL(varargin) +#% GenCSL generates the exact misorientations of the coincident site lattice +#% theory as rotation matrices and as quaternions. +#% +#% INPUTS: varargin can be a list of CSL values (as in, for example, +#% [3, 9, 27]). If left blank, it generates the CSLs up to Sigma49. +#% +#% OUTPUTS: the CSL in a structural format (CSL), or as lists of +#% quaternions (SQ), their corresponding sigma values (SV), and +#% their standard names (SN). +#% +#% REFERENCES: +#% [1] H. Grimmer, W. Bollman, D. H. Warrington. "Coincident-Site Lattices +#% and Complete Pattern-Shift Lattices in Cubic Crystals." Acta Cryst. A30 +#% (1974) p197. +#% [2] D. H. Warrington, P. Bufalini. "The Coincident Site Lattice and Grain +#% Boundaries." Scripta Metall. 5 (1971) p771. +#% +#% NOTES: Function exactly reproduces Table 1 from Grimmer et al, except for +#% Sigma31b, which appears to be in error in the paper. +#% +#% ------------------------------------------------------------------------- +#% 2011-05-01 | Eric Payton, Ruhr-Universitaet Bochum (payton.28[at]osu.edu) +#% ------------------------------------------------------------------------- +#% This program is provided without any guarantee of correctness. +#% If you modify it and/or improve it, please kindly share with me your new +#% and improved version to the email address above. Thanks! +#%---------------------------------------------------------------------------- + + + +# % Remove symmetric redundancies by putting into fundamental region +# O=RMat2Quat(SM);RR=zeros(length(SM),4); +# for ind=1:length(SM) +# q=QuatProd(repmat(O(ind,:),size(SYM,1),1),SYM); +# [an,ax]=Quat2AngAx(q); +# ax=sort(abs(ax(an==min(an),:)),2,'descend');an=min(an);ax=ax(1,:); +# RR(ind,:)=[an,ax]; +# end +# [~,b]=unique(sigdec(RR,5),'rows');RR=RR(b,:); +# +# % Sort the results by increasing angle in axis/angle description, such +# % that, in the event of multiple results, th.a1 +# for ind=1:size(RR,1) +# tmp=Quat2RMat(RR(ind,:)); +# eval(['CSL.S' num2str(S) char(96+ind) ... +# '=tmp{1};']); +# SN{ee}=['\Sigma' num2str(S) char(96+ind)];ee=ee+1; +# end +# else +# tmp=Quat2RMat(RR); +# eval(['CSL.S' num2str(S) '=tmp{1};']); +# SN{ee}=['\Sigma' num2str(S)];ee=ee+1; +# end +# +# % Store results as a list of quaternions corresponding Sigma values +# SQ=vertcat(SQ,RR); +# SV=vertcat(SV,repmat(S,size(RR,1),1)); +# +# clear SM O an ax RR b ind % clean up +#end % loop over all desired N +#clear Ndex S SYM TT X h1 k1 l1 h2 k2 l2 hkl1 hkl2 hkl3 q N ee % clean up +# +#% This is it. + +# ---------------------------------------------------------------------------- + +def genKitahara_KS(): + ''' KS OR matrices + + **genKitahara_KS** returns the rotation matrices for the 24 variants of the + Kurdjumov-Sachs orientation relationship in the order presented in Table 2 + of Kitahara [1]_. + + Parameters + ---------- + None + + Returns + ------- + V : 24 x 1 list of 3 x 3 numpy matrices + + Notes + ----- + Rounding the results of Kitahara_KS and removing redundancies gives the + list of Bain correspondence matrices. + + See also + -------- + genKitahara_NW, genBainCorrMatrices + + References + ---------- + .. [1] Kitahara et al, Acta Mater 54 (2006) p1279. + ''' + import numpy as np + + Pg = [0] * 24 + Pa = [0] * 24 + Dg = [0] * 24 + Da = [0] * 24 + + i = 0 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 1 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 2 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 3 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 4 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, -1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 5 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, -1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 6 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 7 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 8 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 9 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 10 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 11 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, 1.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 12 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 13 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 14 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 15 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 0.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 16 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 17 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 18 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 1.0, 0.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 19 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 1.0, 0.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 20 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, -1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 21 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 0.0, -1.0, -1.0] + Da[i] = [-1.0, 1.0, -1.0] + + i = 22 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, 1.0] + Da[i] = [-1.0, -1.0, 1.0] + + i = 23 + Pg[i] = [ 1.0, 1.0, -1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 0.0, 1.0] + Da[i] = [-1.0, 1.0, -1.0] + + V = [] + + for i in range(0, 24): + + a = Dg[i] / np.linalg.norm(Dg[i]) + b = np.cross(Dg[i], Pg[i]) / np.linalg.norm(np.cross(Dg[i], Pg[i])) + c = Pg[i] / np.linalg.norm(Pg[i]) + M = np.vstack([a, b, c]) + + a = Da[i] / np.linalg.norm(Da[i]) + b = np.cross(Da[i], Pa[i]) / np.linalg.norm(np.cross(Da[i], Pa[i])) + c = Pa[i] / np.linalg.norm(Pa[i]) + A = np.vstack([a, b, c]) + + V.append(np.linalg.solve(A, M)) + + return V + +# --------------------------------------------------------------------------- + +def genKitahara_NW(): + ''' NW OR matrices + + **genKitahara_NW** returns the rotation matrices for the 12 variants of the + Nishiyama-Wasserman orientation relationship in the order presented in + Table 2 of Kitahara [1]_. + + Parameters + ---------- + None + + Returns + ------- + V : 21 x 1 list of 3 x 3 numpy matrices + + Notes + ----- + Table in Reference [1]_ has an error in the gamma direction for + variant #10! For table 10 to agree with Table 6, the gamma direction must + be [ 2 -1 1] and not [-1 2 1]. + + See also + -------- + genKitahara_KS, genBainCorrMatrices + + References + ---------- + .. [1] Kitahara et al, Mater Charact (2006) p378. + ''' + import numpy as np + + Pg = [0] * 12 + Pa = [0] * 12 + Dg = [0] * 12 + Da = [0] * 12 + + i = 0 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 2.0, -1.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 1 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 2.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 2 + Pg[i] = [ 1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, 2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + + i = 3 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-2.0, -1.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 4 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, 2.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 5 + Pg[i] = [-1.0, 1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 1.0, -1.0, 2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 6 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 2.0, 1.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 7 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -2.0, -1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 8 + Pg[i] = [ 1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 1.0, 2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 9 + Pg[i] = [-1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [ 2.0, -1.0, 1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 10 + Pg[i] = [-1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, 2.0, 1.0] + Da[i] = [ 0.0, -1.0, 1.0] + + i = 11 + Pg[i] = [-1.0, -1.0, 1.0] + Pa[i] = [ 0.0, 1.0, 1.0] + Dg[i] = [-1.0, -1.0, -2.0] + Da[i] = [ 0.0, -1.0, 1.0] + + V = [] + + for i in range(0, 12): + + a = Dg[i] / np.linalg.norm(Dg[i]) + b = np.cross(Dg[i], Pg[i]) / np.linalg.norm(np.cross(Dg[i], Pg[i])) + c = Pg[i] / np.linalg.norm(Pg[i]) + M = np.vstack([a, b, c]) + + a = Da[i] / np.linalg.norm(Da[i]) + b = np.cross(Da[i], Pa[i]) / np.linalg.norm(np.cross(Da[i], Pa[i])) + c = Pa[i] / np.linalg.norm(Pa[i]) + A = np.vstack([a, b, c]) + + V.append(np.linalg.solve(A, M)) + + return V + +# --------------------------------------------------------------------------- + +def latticeParamAus_Onink(at_pct_carbon, temperature_degC): + ''' estimate austenite lattice parameter at temperature + + **latticeParamAus_Onink** estimates the lattice parameter of the + face-centered cubic austenite phase at high temperature as a function of + carbon content based on the best fit equations of Onink et al [1]_. + Lattice parameter is returned in angstroms. + + Parameters + ---------- + at_pct_carbon : float + atomic percent of carbon + + temperature_degC : float + temperature in degrees celsius + + Returns + ------- + a0 : float + lattice parameter of austenite (gamma) phase in angstroms + + Notes + ----- + Verified validity range varies with temperature and carbon content + (Ref [1]_, Table 2): + + ======= ====================== + at % C Temperature Range in K + ======= ====================== + 0.05 1080-1250 + 1.30 1080-1250 + 1.75 1060-1250 + 2.60 1030-1250 + 3.79 1000-1250 + ======= ====================== + + References + ---------- + .. [1] M Onink, C. M. Brakman, F. D. Tichelaar, E. J. Mittemeijer, + S. van der Zwaag. "The lattice parameters of austenite and ferrite + in Fe-C alloys as functions of carbon concentration and + temperature." Scripta Metall Mater 29 (1993) p1011-1016. + + Examples + -------- + >>> # Reproduce Figure 2 from Onink et al [1]_. The x-axis will be similar + >>> # but not *exactly* the same because we are plotting atomic percent + >>> # instead of number C atoms / 100 Fe atoms. + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from scipy import constants + >>> temperature_degC = constants.K2C(np.arange(1000.0, 1275.0, 25.0)) + >>> for at_pct_carbon in [0.0, 1.3, 1.75, 2.6, 3.65]: + >>> print(at_pct_carbon) + >>> a0 = latticeParamAus_Onink(at_pct_carbon, temperature_degC) + >>> # For consistency in cryspy, our inputs are in degC + >>> # and output is in angstroms. + >>> # Converting for comparison to the published plot... + >>> plt.plot(constants.C2K(temperature_degC), a0/10.0) + >>> plt.legend(['Fe', 'Fe-0.3C', 'Fe-0.4C', 'Fe-0.6C', 'Fe-0.8C']) + >>> plt.ylim([0.3630, 0.3690]) + >>> plt.xlim([950, 1350]) + >>> plt.xlabel('Temperature (K)') + >>> plt.ylabel('Lattice parameter of austenite (nm)') + >>> plt.show() + ''' + from scipy import constants + tempK = constants.convert_temperature(temperature_degC, 'Celsius', 'Kelvin') + a0 = (0.363067 + 0.000783 * at_pct_carbon) * \ + (1.0 + (24.92 - 0.51 * at_pct_carbon) * 1.0E-6 * (tempK - 1000.0)) + return a0 * 10.0 + +# --------------------------------------------------------------------------- + +def lattParams_RobertsCohen(c_at_pct): + ''' RTP Fe alpha & gamma prime parameters as function of at%C + + **lattParams_RobertsCohen** calculates room temperature lattice parameters + for austenite and martensite from carbon content. + + Parameters + ---------- + c_at_pct : float + atomic percent carbon + + Returns + ------- + a0 : float + austenite lattice parameter in angstroms + a : float + martensite a axis lattice parameter in angstroms + c : float + martensite c axis lattice parameter in angstroms + + Notes + ----- + Atomic pct equations come from Ref. [1]_ using the data obtained in + Ref. [2]_. + + References + ---------- + .. [1] M. Cohen, "The Strengthening of Steel." Transactions of the + Metallurgical Society of AIME 224 (1962) p637-657. + .. [2] C.S. Roberts, "Technical Note: Effect of Carbon on the Volume + Fractions and Lattice Parameters Of Retained Austenite and + Martensite." Journal of Metals, AIME Transactions (Institute of + Metals Division) 197 (1953). p203-204. + ''' + # c in atomic percent + + a0 = 3.548 + 0.0094 * c_at_pct # units of angstroms + a = 2.861 - 0.0028 * c_at_pct + c = 2.861 + 0.0247 * c_at_pct + + return a0, a, c + +# ---------------------------------------------------------------------------- + +def genBainCorrMatrices(): + ''' Bain corr matrices + + **genBainCorrMatrices** returns the rotation matrices for the 12 Bain + correspondence matrices, numbered according to parallelism of the c-axis + of the B phase. + + Parameters + ---------- + None + + Returns + ------- + V : 12 x 1 list of 3 x 3 numpy matrices + + Notes + ----- + Rounding the results of Kitahara_KS and removing redundancies gives the + list of Bain correspondence matrices. + + See also + -------- + genKitahara_NW, genBainCorrMatrices + + References + ---------- + .. [1] Kitahara et al, Acta Mater 54 (2006) p1279. + ''' + import numpy as np + + # Numbered according to parallelism of c-axis of B phase + c = 12 * [0] + + c[ 0] = np.mat([[ 1.0, -1.0, 0.0], + [ 1.0, 1.0, 0.0], + [ 0.0, 0.0, 1.0]]) + + c[ 1] = np.mat([[-1.0, -1.0, 0.0], + [ 1.0, -1.0, 0.0], + [ 0.0, 0.0, 1.0]]) + + c[ 2] = np.mat([[ 1.0, 1.0, 0.0], + [-1.0, 1.0, 0.0], + [ 0.0, 0.0, 1.0]]) + + c[ 3] = np.mat([[-1.0, 1.0, 0.0], + [ 1.0, 1.0, 0.0], + [ 0.0, 0.0, -1.0]]) + + c[ 4] = np.mat([[-1.0, 0.0, 1.0], + [ 1.0, 0.0, 1.0], + [ 0.0, 1.0, 0.0]]) + + c[ 5] = np.mat([[ 1.0, 0.0, -1.0], + [ 1.0, 0.0, 1.0], + [ 0.0, -1.0, 0.0]]) + + c[ 6] = np.mat([[-1.0, 0.0, -1.0], + [-1.0, 0.0, 1.0], + [ 0.0, 1.0, 0.0]]) + + c[ 7] = np.mat([[ 1.0, 0.0, 1.0], + [ 1.0, 0.0, -1.0], + [ 0.0, 1.0, 0.0]]) + + c[ 8] = np.mat([[ 0.0, 1.0, -1.0], + [ 0.0, 1.0, 1.0], + [ 1.0, 0.0, 0.0]]) + + c[ 9] = np.mat([[ 0.0, 1.0, 1.0], + [ 0.0, -1.0, 1.0], + [ 1.0, 0.0, 0.0]]) + + c[10] = np.mat([[ 0.0, -1.0, 1.0], + [ 0.0, 1.0, 1.0], + [-1.0, 0.0, 0.0]]) + + c[11] = np.mat([[ 0.0, -1.0, -1.0], + [ 0.0, 1.0, -1.0], + [ 1.0, 0.0, 0.0]]) + + return c + +# ---------------------------------------------------------------------------- + +def calcPTMT(a0, a, c): + ''' perform PTMT calculations + + **calcPTMT** performs calculations of the phenomenological theory of + martensitic transformations using inputs of the lattice parameters of the + face-centered cubic (gamma) and body-centered-tetragonal (alpha prime) + phases. + + Parameters + ---------- + a0 : float + lattice parameter of the FCC austenite phase in angstroms. + a : float + lattice parameter for the a and b axes of the BC(C/T) martensite phase + in angstroms. + c : float + lattice parameter for the c axis of the BC(C/T) martensite phase in + angstroms. + + Returns + ------- + ksivals : n x 3 numpy float array + ksi angles for the orientation relationship + + detailtuple : tuple of floats + detailed results of the ptmt calculation. In order, the returns are: + (0) invariantPlaneNormal, (1) displacementVector, + (2) shapeDeformationMatrix, (3) shapeDeformationMagnitude, + (4) complimentaryShearDirection, (5) complimentaryShearMagnitude, + (6) complimentaryShearAngleRadians + + References + ---------- + .. [1] Z. Nishiyama, Martensitic Transformation. New York: Academic Press (1978). + .. [2] J. S. Bowles and J. K. Mackenzie. "The crystallography of martensite + transformations I." Acta Metall 2 (1954). + .. [3] J. K. Mackenzie and J. S. Bowles. "The crystallography of martensite + transformations II." Acta Metall 2 (1954). + .. [4] H. K. D. H. Bhadeshia, Worked Examples in the Geometry of Crystals, + 2nd Ed. Brookfield, VT: The Institute of Metals, 1987. + + Notes + ----- + Provided for ease of copy-and-paste, due to the unweildy outputs + + #from cryspy import orrl + #ksivals, resultsTuple = orrl.calcPTMT(a0, a, c) + #invariantPlaneNormal = resultsTuple[0] + #displacementVector = resultsTuple[1] + #shapeDeformationMatrix = resultsTuple[2] + #shapeDeformationMagnitude = resultsTuple[3] + #complimentaryShearDirection = resultsTuple[4] + #complimentaryShearMagnitude = resultsTuple[5] + #complimentaryShearAngleRadians = resultsTuple[6] + + #from cryspy import orrl + #import numpy as np + #a0, a, c = orrl.lattParams_RobertsCohen(np.arange(0, 5, 0.5)) + #ksivals = [] + #invariantPlaneNormal = [] + #displacementVector = [] + #shapeDeformationMatrix = [] + #shapeDeformationMagnitude = [] + #complimentaryShearDirection = [] + #complimentaryShearMagnitude = [] + #complimentaryShearAngleRadians = [] + #for i in np.arange(0, np.shape(a0)[0]): + # tmp, resultsTuple = orrl.calcPTMT(a0[i], a[i], c[i]) + # ksivals.append(tmp) + # invariantPlaneNormal.append(resultsTuple[0]) + # displacementVector.append(resultsTuple[1]) + # shapeDeformationMatrix.append(resultsTuple[2]) + # shapeDeformationMagnitude.append(resultsTuple[3]) + # complimentaryShearDirection.append(resultsTuple[4]) + # complimentaryShearMagnitude.append(resultsTuple[5]) + # complimentaryShearAngleRadians.append(resultsTuple[6]) + + Examples + ------- + >>> # Reproduce example from Nishiyama (with lattice parameters from Wayman) + >>> a0 = 3.591 # angstroms, gamma + >>> a = 2.875 # angstroms, alpha prime + >>> c = 2.875 # angstroms, alpha prime + >>> ksivals, resultsTuple = calcPTMT(a0, a, c) + >>> invariantPlaneNormal = resultsTuple[0] + >>> displacementVector = resultsTuple[1] + >>> shapeDeformationMatrix = resultsTuple[2] + >>> shapeDeformationMagnitude = resultsTuple[3] + >>> complimentaryShearDirection = resultsTuple[4] + >>> complimentaryShearMagnitude = resultsTuple[5] + >>> complimentaryShearAngleRadians = resultsTuple[6] + + >>> from cryspy import orrl + >>> import numpy as np + >>> a0, a, c = orrl.lattParams_RobertsCohen(np.arange(0, 5, 0.5)) + >>> ksivals = [] + >>> invariantPlaneNormal = [] + >>> displacementVector = [] + >>> shapeDeformationMatrix = [] + >>> shapeDeformationMagnitude = [] + >>> complimentaryShearDirection = [] + >>> complimentaryShearMagnitude = [] + >>> complimentaryShearAngleRadians = [] + >>> for i in np.arange(0, np.shape(a0)[0]): + >>> tmp, resultsTuple = orrl.calcPTMT(a0, a, c) + >>> ksivals.append(tmp) + >>> invariantPlaneNormal.append(resultsTuple[0]) + >>> displacementVector.append(resultsTuple[1]) + >>> shapeDeformationMatrix.append(resultsTuple[2]) + >>> shapeDeformationMagnitude.append(resultsTuple[3]) + >>> complimentaryShearDirection.append(resultsTuple[4]) + >>> complimentaryShearMagnitude.append(resultsTuple[5]) + >>> complimentaryShearAngleRadians.append(resultsTuple[6]) + ''' + + import numpy as np + + # Using a0=3.591 and a=c=2.875 does not agree exactly with top of page 358 in + # Nishiyama (values from Wayman). Better with a=c=2.8747428847860585? + # Should we change both a and a0? + # a0 = 3.591 # angstroms, gamma + # a = 2.875 # angstroms, alpha prime + # c = 2.875 # angstroms, alpha prime + + slipPlaneFamilyBCT = [1, 1, 2] + slipDirectionFamilyBCT = [1, 1, -1] + + #%% Prepare inputs + cc = genBainCorrMatrices() + + slipPlaneFamilyBCT = np.mat(slipPlaneFamilyBCT / np.linalg.norm(slipPlaneFamilyBCT)) + slipDirectionFamilyBCT = np.mat(slipDirectionFamilyBCT / np.linalg.norm(slipDirectionFamilyBCT)) + + ## Below not used, but could probably be done... + ## Loop through Bain matrices + # O = [] # empty list to contain variant rotations + # for baindex in range(0, np.shape(cc)[0]): + cm = cc[0]#cc[baindex] + + #%% Create the Bain distortion matrix, bb + eta1 = np.sqrt(2.0) * a / a0 + eta3 = c / a0 + + bb = np.mat([[eta1, 0.0, 0.0], + [0.0, eta1, 0.0], + [0.0, 0.0, eta3]]) + + #%% Calculation of invariant lines. + # Resulting values agree with Equations 6' in Nishiyama + x1 = -np.sqrt((1.0 - eta1**2) / (eta1**2 + eta3**2 - 2*eta1**2)) + x2a = -np.sqrt(1.0 - 2.0 * x1**2) + x2b = -x2a + x3 = -x1 + + xin = [np.mat([x1, x2a, x3]), np.mat([x1, x2b, x3])] # two possibilities + + #%% Calculation of invariant normal + # Resulting values agree with Equations 7' in Nishiyama + eta1i = 1.0 / eta1**2 + eta3i = 1.0 / eta3**2 + n1 = np.sqrt((1.0 - eta1i) / (eta1i + eta3i - 2.0 * eta1i)) + n2a = np.sqrt(1.0 - 2.0 * n1**2) + n2b = -n2a + n3 = n1 + + nin = [np.mat([n1, n2a, n3]), np.mat([n1, n2b, n3])] # two possibilities + + #%% Lines after Bain distortion + # There are four possible combinations of x and n (top of page 359). + # Nishiyama only looks at one as an example in his textbook. + # Resulting value for xui agrees with equation 8, p359 + # Resulting value for pp2 agrees with equation 9, p359 + + # One could choose one of the four combinations this way + # for xindex in np.arange(0,2): + # for nindex in np.arange(0,2): + # I'm choosing not to do this here because we can get the ksi angles from + # just the first index, then populate the set of variants from the ksi angles. + xindex = 0 + nindex = 0 + + xi = xin[xindex] + ni = nin[nindex] + + # Convert BCT slip plane and family into FCC coordinates + slipPlaneFCC = slipPlaneFamilyBCT * cm + slipDirectionFCC = np.linalg.solve(cm, slipDirectionFamilyBCT.T) + + p2 = slipPlaneFCC / np.linalg.norm(slipPlaneFCC) + d2 = slipDirectionFCC / np.linalg.norm(slipPlaneFCC) + xui = xi * bb # x underscore sub i + pp2 = p2 * bb.I + pp2 = pp2 / np.linalg.norm(pp2) # p prime underscore sub 2 + + #%% Invariant line strain + # The numerical absolute values of u and v agree with bottom of page 359. + # The middle value of u is negative in the text but positive here. + # It appears that this is probably an error in the text. + u = np.cross(xi, p2) + v = np.cross(xui, pp2) + rr1 = np.mat(np.vstack([xi, p2, u]).T) + rr2 = np.mat(np.vstack([xui, pp2, v]).T) + + # iS_0i, Eq 13 + yy = rr2.T * bb * rr1 + + # Nishiyama Eq. 15. His result is obtained with ni[0] and xi[0] + zz = np.dot(ni, rr1).A1 + + # Now, to produce the solution, we can use the associative properties of + # the matrices to multiply (n'_i;i) by the matrix containing the rotation + # of beta about x_i (see Nishiyama Eqs 15 and 14, respectively.) + # When we multiply through and collect the necessary terms, we find: + tmp1 = np.array([[zz[1] * yy[1,0] + zz[2] * yy[2,0], + zz[2] * yy[1,0] - zz[1] * yy[2,0]], + [zz[1] * yy[1,1] + zz[2] * yy[2,1], + zz[2] * yy[1,1] - zz[1] * yy[2,1]], + [zz[1] * yy[1,2] + zz[2] * yy[2,2], + zz[2] * yy[1,2] - zz[1] * yy[2,2]]]) + + tmp2 = np.array([[zz[0] - zz[0] * yy[0, 0]], + [zz[1] - zz[0] * yy[0, 1]], # Note that the problem is + [zz[2] - zz[0] * yy[0, 2]]]) # now overspecified! + + + # Now we can solve the (overspecified) system for cosB and sinB + # If our system was not overspecified, we could use + # np.linalg.solve(tmp1, tmp2) as we would in Matlab. + # It appears that matlab must convert to a least squares method + # for an overspecified system + tmp3 = np.linalg.lstsq(tmp1, tmp2)[0] + cosB = tmp3[0][0] + sinB = tmp3[1][0] + + print(cosB, sinB) + + betamx = np.mat([[1.0, 0.0, 0.0], [0.0, cosB, -sinB], [0.0, sinB, cosB]]) + + # The results for ss here agree with Eq 17 + ss = rr1 * betamx * yy * rr1.T + + ## Check results. Should be <= round off error, approx 1E-15 + #print(np.mat(zz) * np.mat(betMX) * np.mat(yy) - np.mat(zz)) + + #%% Calculate the useful stuff + + # Calculate the invariant plane normal + p1 = p2 * np.linalg.inv(ss) - p2 + p1p = p1 / np.linalg.norm(p1) # agrees with Eq. 18 in Nishiyama + + # Calculate the displacement vector + d1 = (ss * d2 - d2) / (p1p * d2) # agrees with Eq. 19 + + # Magnitude of the shape deformation + m1 = np.linalg.norm(d1) # agrees with Eq. 20 + + # Direction of complimentary shear + y = np.mat(np.cross(p1p, np.mat([1.0, 0.0, 0.0]))).T # [1 0 0] in text p361 + d2 = (y - np.linalg.solve(ss, y)) / (p2 * y) + + # Magnitude of complimentary shear + m2 = np.linalg.norm(d2) + + # Angle of complimentary shear + alpha = np.arctan(0.5 * m2) + + # Shape deformation matrix (Bhadeshia) + delta = m1 * p1p * d1 + s = np.sqrt(m1**2 - delta**2) # FIXME: Refer back to Bhadeshia, where is this variable to be used? + pp = np.eye(3, 3) + m1 * d1 * p1p + + #%% Get orientation relationship between martensite and austenite + + # KSI is similarity-transformed version of J (i.e., expressed in martensite basis) + J = rr1 * betamx * rr2.T + gamma = cm.A / np.tile(np.linalg.norm(cm.A, axis=1), [3, 1]).T + KSI = gamma * J * gamma.T + + # individual ksi angles + ksi1 = np.arccos(KSI[0, 0]) * 180.0 / np.pi + ksi2 = np.arccos(KSI[1, 1]) * 180.0 / np.pi + ksi3 = np.arccos(KSI[2, 2]) * 180.0 / np.pi + + # TODO: Generate full set of variant matrices + OR = (a / a0) * cm * np.linalg.inv(ss) + # The above should be equivalent to the T matrix in Kitahara + + # Check: With the a/a0 in front, it gives a normalized matrix + # print(np.linalg.det(OR)) + + #%% Compile results together + ksivals = np.array([ksi1, ksi2, ksi3]) + invariantPlaneNormal = p1p + displacementVector = d1 + shapeDeformationMatrix = pp + shapeDeformationMagnitude = m1 + complimentaryShearDirection = d2 + complimentaryShearMagnitude = m2 + complimentaryShearAngleRadians = alpha + + return ksivals, \ + (invariantPlaneNormal, displacementVector, \ + shapeDeformationMatrix, shapeDeformationMagnitude, \ + complimentaryShearDirection, complimentaryShearMagnitude, \ + complimentaryShearAngleRadians) + +# ---------------------------------------------------------------------------- diff --git a/cryspy/orrl.py b/cryspy/orrl.py index f3ad4c4..2e7af32 100755 --- a/cryspy/orrl.py +++ b/cryspy/orrl.py @@ -1,3270 +1,1127 @@ -# -*- coding: utf-8 -*- -''' -ovlib.orrl: Orientation relationship characterization -=============================================================================== -''' - -def namedOR(name): - """ Returns ksi values for named orientation relationships - - Parameters - ---------- - name : {'ks', 'nw', 'bain'} - Orientation relationship name - - Returns - ------- - ksi_values : 1x3 numpy array of floats - ksi values in degrees - - Notes - ----- - TODO: Add plane parallel Greninger-Troiano, Kelly, etc. - TODO: Allow 'Kurdjumov-Sachs' as well as 'ks', etc. - """ - import numpy as np - - if isinstance(name, str): - - if name.lower() == 'ks': - - s6 = np.sqrt(6.0) - s3 = np.sqrt(3.0) - ksi1 = np.arccos((s6 + 1.0) / (2.0 * s3)) - ksi2 = np.arccos((s6 + 18.0) / (12.0 * s3)) - ksi3 = np.arccos((s6 + 12.0) / (6.0 * s6)) - ksi = np.array([ksi1, ksi2, ksi3]) - del s6, s3, ksi1, ksi2, ksi3 - - elif name.lower() == 'nw': - - s6 = np.sqrt(6) - s2 = np.sqrt(2) - ksi0 = np.arccos((s2 + 1.0) / s6) - ksi = np.array([0.0, ksi0, ksi0]) - - elif name.lower() == 'bain': - - ksi = np.array([0.0, 0.0, 0.0]) - - else: - - print('namedOR: Unrecognized named OR') # TODO: use warnings package - - else: - - print('namedOR requires a string input. Returning Bain.') # TODO: use warnings package - ksi = np.array([0.0, 0.0, 0.0]) - - return ksi * 180.0/np.pi - -def getRepresentativeVariant(ksi_values): - """ Returns quaternion of representative variant - - Parameters - ---------- - ksi_values : - - Returns - ------- - psiq : quat class - """ - import numpy as np - from cryspy.rot import quat - - if isinstance(ksi_values, str): - # Import named OR and convert to radians - orrl = namedOR(ksi_values) * np.pi / 180.0 - else: - # Convert OR specification into radians - orrl = ksi_values * np.pi / 180.0 - - csum = np.cos(orrl[0])+np.cos(orrl[1])+np.cos(orrl[2]) - x0 = 0.5 * np.sqrt(csum + 1.0) - x = 0.5 * np.sqrt(2. * np.cos(orrl) - csum + 1.0) - - ksiq = quat(x0, x[0], x[1], x[2]) - - # Calculate quaternion for normalized Bain correspondence. - - qw = 0.5 * np.sqrt(2.0 + np.sqrt(2.0)) - qz = (2.0 / np.sqrt(2.0)) / (4.0 * qw) - gammaq = quat(qw, 0.0, 0.0, qz); - - psiq = ksiq * gammaq - - return psiq - -# -------------------------------------------------------------------------- - -def generateVariants(ksi_values): - """ Generate variants from Kurdjumov-Sachs angles - - Returns matrices of an orientation relationship specified in Kurjumov-Sachs - angles. - - Parameters - ---------- - ksi_values : length 3 iterable OR {'KS', 'NW', 'Bain'} - - Returns - ------- - vv : rmat object - rotation matrices corresponding to variants - - """ - import numpy as np - import cryspy.rot as rot - import cryspy.util as util - import righthand as rh - import warnings - - if isinstance(ksi_values, str): - - ksi = namedOR(ksi_values) - - elif np.shape(ksi_values) == (3,): - - ksi = np.asanyarray(ksi_values) - - else: - - warnings.warn('ksi values must be a numeric iterable of length 3.') - - # convert ksi radians to rotation matrices - - ksi = ksi * np.pi / 180.0 - - mb = np.zeros([2, 9]) - - mb[0, 0] = np.cos(ksi[0]) - mb[0, 4] = np.cos(ksi[1]) - mb[0, 8] = np.cos(ksi[2]) - - costh = 0.5 * (np.sum(np.cos(ksi)) - 1.0) # sum(cos(ksi)) is the matrix trace - mosth = 1.0 - costh - sinth = np.sqrt(1.0 - costh**2.0) - - - r1 = np.sqrt((mb[0, 0] - costh) / mosth) - r2 = np.sqrt((mb[0, 4] - costh) / mosth) - r3 = np.sqrt((mb[0, 8] - costh) / mosth) - del costh - - r1r2 = r1 * r2 * mosth - r1r3 = r1 * r3 * mosth - r2r3 = r2 * r3 * mosth - r3st = r3 * sinth - r2st = r2 * sinth - r1st = r1 * sinth - del r1, r2, r3, mosth, sinth - - mb[0, 5] = r2r3 - r1st - mb[0, 7] = r2r3 + r1st - mb[1, :] = mb[0, :] - - mb[0, 1] = -r1r2 + r3st - mb[0, 2] = -r1r3 - r2st - mb[0, 3] = -r1r2 - r3st - mb[0, 6] = -r1r3 + r2st - del r1r2, r1r3, r2r3, r3st, r2st, r1st - - mb[1, 1] = -mb[0, 1] - mb[1, 2] = -mb[0, 2] - mb[1, 3] = -mb[0, 3] - mb[1, 6] = -mb[0, 6] - # mb[0] is the 'positive' solution; mb[1] is the 'negative' solution - - # create Bain correspondence matrices - bb = np.zeros([12, 9]) - bb[ 0, :] = [ 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0] - bb[ 1, :] = [ 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0] - bb[ 2, :] = [-1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - bb[ 3, :] = [ 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 0.0] - bb[ 4, :] = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0] - bb[ 5, :] = [ 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0] - bb[ 6, :] = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0] - bb[ 7, :] = [-1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - bb[ 8, :] = [ 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 0.0] - bb[ 9, :] = [ 1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0] - bb[10, :] = [ 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 0.0] - bb[11, :] = [-1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, -1.0] - - # normalize correspondence matrices - bb = rot.rmat.from_array(bb / util.vecarraynorm(bb)) - mb = rot.rmat.from_array(mb) - - # produce variants - vv = np.zeros([24, 9]) - tmp = mb[0] * bb - vv[np.arange(0, 24, 2), :] = tmp.to_array() - tmp = mb[1] * bb - vv[np.arange(1, 24, 2), :] = tmp.to_array() - - # reduce redundancies, if they exist (as they do, for example, in NW) - vv, ia, ic = rh.uniquerows(rh.sigdec(vv, 7)) - del ia, ic - - return rot.rmat.from_array(vv) - -#------------------------------------------------------------------------------ - -def _bg_ksi1(bincenters): - """ returns ksi1 background - - Parameters - ---------- - bincenters : numpy array - centers of bins for ksi1 background - - Returns - ------- - bg : numpy array - background number fractions for ksi1 - - Notes - ----- - Background is determined by cubic spline interpolation from a large - numerical simulation containing 17156424 ksi values measured from - random orientations. - - """ - import numpy as np - import scipy.interpolate as interp - - d = np.array([ [ 0.00000000e+00, 0.00000000e+00], - [ 1.50000000e-01, 8.04400000e+03], - [ 4.50000000e-01, 2.45720000e+04], - [ 7.50000000e-01, 4.06200000e+04], - [ 1.05000000e+00, 5.61680000e+04], - [ 1.35000000e+00, 7.13330000e+04], - [ 1.65000000e+00, 8.80860000e+04], - [ 1.95000000e+00, 1.02050000e+05], - [ 2.25000000e+00, 1.17010000e+05], - [ 2.55000000e+00, 1.31760000e+05], - [ 2.85000000e+00, 1.47720000e+05], - [ 3.15000000e+00, 1.60180000e+05], - [ 3.45000000e+00, 1.75440000e+05], - [ 3.75000000e+00, 1.88830000e+05], - [ 4.05000000e+00, 2.03510000e+05], - [ 4.35000000e+00, 2.16520000e+05], - [ 4.65000000e+00, 2.29790000e+05], - [ 4.95000000e+00, 2.43770000e+05], - [ 5.25000000e+00, 2.56080000e+05], - [ 5.55000000e+00, 2.68910000e+05], - [ 5.85000000e+00, 2.81760000e+05], - [ 6.15000000e+00, 2.93690000e+05], - [ 6.45000000e+00, 3.06460000e+05], - [ 6.75000000e+00, 3.16310000e+05], - [ 7.05000000e+00, 3.27680000e+05], - [ 7.35000000e+00, 3.40790000e+05], - [ 7.65000000e+00, 3.50150000e+05], - [ 7.95000000e+00, 3.63730000e+05], - [ 8.25000000e+00, 3.74700000e+05], - [ 8.55000000e+00, 3.83250000e+05], - [ 8.85000000e+00, 3.93360000e+05], - [ 9.15000000e+00, 4.01330000e+05], - [ 9.45000000e+00, 4.08800000e+05], - [ 9.75000000e+00, 4.10170000e+05], - [ 1.00500000e+01, 4.16810000e+05], - [ 1.03500000e+01, 4.22100000e+05], - [ 1.06500000e+01, 4.24130000e+05], - [ 1.09500000e+01, 4.25640000e+05], - [ 1.12500000e+01, 4.26720000e+05], - [ 1.15500000e+01, 4.26860000e+05], - [ 1.18500000e+01, 4.25850000e+05], - [ 1.21500000e+01, 4.25010000e+05], - [ 1.24500000e+01, 4.19670000e+05], - [ 1.27500000e+01, 4.14030000e+05], - [ 1.30500000e+01, 4.06510000e+05], - [ 1.33500000e+01, 3.97900000e+05], - [ 1.36500000e+01, 3.85480000e+05], - [ 1.39500000e+01, 3.72070000e+05], - [ 1.42500000e+01, 3.55970000e+05], - [ 1.45500000e+01, 3.36740000e+05], - [ 1.48500000e+01, 3.11690000e+05], - [ 1.51500000e+01, 2.79820000e+05], - [ 1.54500000e+01, 2.57130000e+05], - [ 1.57500000e+01, 2.35370000e+05], - [ 1.60500000e+01, 2.18230000e+05], - [ 1.63500000e+01, 2.01140000e+05], - [ 1.66500000e+01, 1.84260000e+05], - [ 1.69500000e+01, 1.68320000e+05], - [ 1.72500000e+01, 1.54590000e+05], - [ 1.75500000e+01, 1.39760000e+05], - [ 1.78500000e+01, 1.27400000e+05], - [ 1.81500000e+01, 1.14020000e+05], - [ 1.84500000e+01, 1.03180000e+05], - [ 1.87500000e+01, 9.14750000e+04], - [ 1.90500000e+01, 8.00980000e+04], - [ 1.93500000e+01, 7.07470000e+04], - [ 1.96500000e+01, 6.10580000e+04], - [ 1.99500000e+01, 5.08750000e+04], - [ 2.02500000e+01, 4.25510000e+04], - [ 2.05500000e+01, 3.39610000e+04], - [ 2.08500000e+01, 2.52270000e+04], - [ 2.11500000e+01, 1.73040000e+04], - [ 2.14500000e+01, 1.12930000e+04], - [ 2.17500000e+01, 7.17200000e+03], - [ 2.20500000e+01, 3.76000000e+03], - [ 2.23500000e+01, 1.55200000e+03], - [ 2.26500000e+01, 3.94000000e+02], - [ 2.29500000e+01, 8.00000000e+00], - [ 2.32500000e+01, 0.00000000e+00], - [ 2.35500000e+01, 0.00000000e+00], - [ 2.38500000e+01, 0.00000000e+00], - [ 2.41500000e+01, 0.00000000e+00], - [ 2.44500000e+01, 0.00000000e+00], - [ 2.47500000e+01, 0.00000000e+00], - [ 2.50500000e+01, 0.00000000e+00], - [ 2.53500000e+01, 0.00000000e+00], - [ 2.56500000e+01, 0.00000000e+00], - [ 2.59500000e+01, 0.00000000e+00], - [ 2.62500000e+01, 0.00000000e+00], - [ 2.65500000e+01, 0.00000000e+00], - [ 2.68500000e+01, 0.00000000e+00], - [ 2.71500000e+01, 0.00000000e+00], - [ 2.74500000e+01, 0.00000000e+00], - [ 2.77500000e+01, 0.00000000e+00], - [ 2.80500000e+01, 0.00000000e+00], - [ 2.83500000e+01, 0.00000000e+00], - [ 2.86500000e+01, 0.00000000e+00], - [ 2.89500000e+01, 0.00000000e+00], - [ 2.92500000e+01, 0.00000000e+00], - [ 2.95500000e+01, 0.00000000e+00], - [ 2.98500000e+01, 0.00000000e+00], - [ 3.01500000e+01, 0.00000000e+00], - [ 3.04500000e+01, 0.00000000e+00], - [ 3.07500000e+01, 0.00000000e+00], - [ 3.10500000e+01, 0.00000000e+00], - [ 3.13500000e+01, 0.00000000e+00], - [ 3.16500000e+01, 0.00000000e+00], - [ 3.19500000e+01, 0.00000000e+00], - [ 3.22500000e+01, 0.00000000e+00], - [ 3.25500000e+01, 0.00000000e+00], - [ 3.28500000e+01, 0.00000000e+00], - [ 3.31500000e+01, 0.00000000e+00], - [ 3.34500000e+01, 0.00000000e+00], - [ 3.37500000e+01, 0.00000000e+00], - [ 3.40500000e+01, 0.00000000e+00], - [ 3.43500000e+01, 0.00000000e+00], - [ 3.46500000e+01, 0.00000000e+00]]) - - minval = np.sqrt(np.spacing(np.float32(1))) - cj = interp.UnivariateSpline(d[:,0], - d[:,1]+minval, - k=1, s=0.5) - - # Interpolate the spline for our bin array into a background for our histogram - bg = cj(bincenters) - bg[bg < minval] = minval # do not allow division by zero - - return bg - -def _bg_ksi2(bincenters): - """ returns ksi1 background - - Parameters - ---------- - bincenters : numpy array - centers of bins for ksi1 background - - Returns - ------- - bg : numpy array - background number fractions for ksi1 - - Notes - ----- - Background is determined by cubic spline interpolation from a large - numerical simulation containing 17156424 ksi values measured from - random orientations. The bin spacing in this histogram is 0.3 degrees. - - """ - import numpy as np - import scipy.interpolate as interp - - d = np.array([ [ 0.00000000e+00, 0.00000000e+00], - [ 1.50000000e-01, 3.80000000e+01], - [ 4.50000000e-01, 2.53000000e+02], - [ 7.50000000e-01, 6.37000000e+02], - [ 1.05000000e+00, 1.30400000e+03], - [ 1.35000000e+00, 2.14800000e+03], - [ 1.65000000e+00, 3.15500000e+03], - [ 1.95000000e+00, 4.51300000e+03], - [ 2.25000000e+00, 6.03800000e+03], - [ 2.55000000e+00, 7.55500000e+03], - [ 2.85000000e+00, 9.49000000e+03], - [ 3.15000000e+00, 1.15950000e+04], - [ 3.45000000e+00, 1.37350000e+04], - [ 3.75000000e+00, 1.64950000e+04], - [ 4.05000000e+00, 1.88700000e+04], - [ 4.35000000e+00, 2.18850000e+04], - [ 4.65000000e+00, 2.54230000e+04], - [ 4.95000000e+00, 2.88190000e+04], - [ 5.25000000e+00, 3.21320000e+04], - [ 5.55000000e+00, 3.60380000e+04], - [ 5.85000000e+00, 3.99840000e+04], - [ 6.15000000e+00, 4.36950000e+04], - [ 6.45000000e+00, 4.81920000e+04], - [ 6.75000000e+00, 5.32350000e+04], - [ 7.05000000e+00, 5.78030000e+04], - [ 7.35000000e+00, 6.26690000e+04], - [ 7.65000000e+00, 6.77040000e+04], - [ 7.95000000e+00, 7.24380000e+04], - [ 8.25000000e+00, 7.90320000e+04], - [ 8.55000000e+00, 8.46900000e+04], - [ 8.85000000e+00, 9.05220000e+04], - [ 9.15000000e+00, 9.71920000e+04], - [ 9.45000000e+00, 1.03820000e+05], - [ 9.75000000e+00, 1.10760000e+05], - [ 1.00500000e+01, 1.17170000e+05], - [ 1.03500000e+01, 1.23890000e+05], - [ 1.06500000e+01, 1.31260000e+05], - [ 1.09500000e+01, 1.38800000e+05], - [ 1.12500000e+01, 1.47670000e+05], - [ 1.15500000e+01, 1.54370000e+05], - [ 1.18500000e+01, 1.62540000e+05], - [ 1.21500000e+01, 1.71450000e+05], - [ 1.24500000e+01, 1.80180000e+05], - [ 1.27500000e+01, 1.88610000e+05], - [ 1.30500000e+01, 1.96360000e+05], - [ 1.33500000e+01, 2.06070000e+05], - [ 1.36500000e+01, 2.15760000e+05], - [ 1.39500000e+01, 2.25380000e+05], - [ 1.42500000e+01, 2.34630000e+05], - [ 1.45500000e+01, 2.45320000e+05], - [ 1.48500000e+01, 2.56620000e+05], - [ 1.51500000e+01, 2.62000000e+05], - [ 1.54500000e+01, 2.64870000e+05], - [ 1.57500000e+01, 2.69010000e+05], - [ 1.60500000e+01, 2.71560000e+05], - [ 1.63500000e+01, 2.74630000e+05], - [ 1.66500000e+01, 2.78040000e+05], - [ 1.69500000e+01, 2.80570000e+05], - [ 1.72500000e+01, 2.82790000e+05], - [ 1.75500000e+01, 2.85960000e+05], - [ 1.78500000e+01, 2.88360000e+05], - [ 1.81500000e+01, 2.89220000e+05], - [ 1.84500000e+01, 2.92980000e+05], - [ 1.87500000e+01, 2.93430000e+05], - [ 1.90500000e+01, 2.96900000e+05], - [ 1.93500000e+01, 2.97160000e+05], - [ 1.96500000e+01, 2.98050000e+05], - [ 1.99500000e+01, 3.00280000e+05], - [ 2.02500000e+01, 3.02770000e+05], - [ 2.05500000e+01, 3.01800000e+05], - [ 2.08500000e+01, 3.04190000e+05], - [ 2.11500000e+01, 3.05480000e+05], - [ 2.14500000e+01, 3.02770000e+05], - [ 2.17500000e+01, 3.01660000e+05], - [ 2.20500000e+01, 2.96840000e+05], - [ 2.23500000e+01, 2.91940000e+05], - [ 2.26500000e+01, 2.87030000e+05], - [ 2.29500000e+01, 2.79490000e+05], - [ 2.32500000e+01, 2.74410000e+05], - [ 2.35500000e+01, 2.68910000e+05], - [ 2.38500000e+01, 2.60700000e+05], - [ 2.41500000e+01, 2.54640000e+05], - [ 2.44500000e+01, 2.47580000e+05], - [ 2.47500000e+01, 2.42160000e+05], - [ 2.50500000e+01, 2.36140000e+05], - [ 2.53500000e+01, 2.29720000e+05], - [ 2.56500000e+01, 2.26630000e+05], - [ 2.59500000e+01, 2.17650000e+05], - [ 2.62500000e+01, 2.12590000e+05], - [ 2.65500000e+01, 2.06500000e+05], - [ 2.68500000e+01, 2.01920000e+05], - [ 2.71500000e+01, 1.95560000e+05], - [ 2.74500000e+01, 1.88290000e+05], - [ 2.77500000e+01, 1.84200000e+05], - [ 2.80500000e+01, 1.78390000e+05], - [ 2.83500000e+01, 1.72670000e+05], - [ 2.86500000e+01, 1.66430000e+05], - [ 2.89500000e+01, 1.61550000e+05], - [ 2.92500000e+01, 1.55230000e+05], - [ 2.95500000e+01, 1.50620000e+05], - [ 2.98500000e+01, 1.44690000e+05], - [ 3.01500000e+01, 1.00740000e+05], - [ 3.04500000e+01, 6.55290000e+04], - [ 3.07500000e+01, 4.09580000e+04], - [ 3.10500000e+01, 2.05860000e+04], - [ 3.13500000e+01, 3.76200000e+03], - [ 3.16500000e+01, 0.00000000e+00], - [ 3.19500000e+01, 0.00000000e+00], - [ 3.22500000e+01, 0.00000000e+00], - [ 3.25500000e+01, 0.00000000e+00], - [ 3.28500000e+01, 0.00000000e+00], - [ 3.31500000e+01, 0.00000000e+00], - [ 3.34500000e+01, 0.00000000e+00], - [ 3.37500000e+01, 0.00000000e+00], - [ 3.40500000e+01, 0.00000000e+00], - [ 3.43500000e+01, 0.00000000e+00], - [ 3.46500000e+01, 0.00000000e+00]]) - - minval = np.sqrt(np.spacing(np.float32(1))) - cj = interp.UnivariateSpline(d[:,0], - d[:,1]+minval, - k=1, s=0.5) - - # Interpolate the spline for our bin array into a background for our histogram - bg = cj(bincenters) - bg[bg < minval] = minval # do not allow division by zero - - return bg - -def _bg_ksi3(bincenters): - """ returns ksi1 background - - Parameters - ---------- - bincenters : numpy array - centers of bins for ksi1 background - - Returns - ------- - bg : numpy array - background number fractions for ksi1 - - Notes - ----- - Background is determined by cubic spline interpolation from a large - numerical simulation containing 17156424 ksi values measured from - random orientations. The bin spacing in this histogram is 0.3 degrees. - - """ - import numpy as np - import scipy.interpolate as interp - - d = np.array([ [ 0.00000000e+00, 0.00000000e+00], - [ 1.50000000e-01, 2.02400000e+03], - [ 4.50000000e-01, 6.40000000e+03], - [ 7.50000000e-01, 1.04980000e+04], - [ 1.05000000e+00, 1.44530000e+04], - [ 1.35000000e+00, 1.87920000e+04], - [ 1.65000000e+00, 2.27030000e+04], - [ 1.95000000e+00, 2.67350000e+04], - [ 2.25000000e+00, 3.07190000e+04], - [ 2.55000000e+00, 3.50120000e+04], - [ 2.85000000e+00, 3.91140000e+04], - [ 3.15000000e+00, 4.28890000e+04], - [ 3.45000000e+00, 4.71630000e+04], - [ 3.75000000e+00, 5.13210000e+04], - [ 4.05000000e+00, 5.43970000e+04], - [ 4.35000000e+00, 5.88480000e+04], - [ 4.65000000e+00, 6.29620000e+04], - [ 4.95000000e+00, 6.75770000e+04], - [ 5.25000000e+00, 7.14270000e+04], - [ 5.55000000e+00, 7.46270000e+04], - [ 5.85000000e+00, 7.91680000e+04], - [ 6.15000000e+00, 8.24090000e+04], - [ 6.45000000e+00, 8.65320000e+04], - [ 6.75000000e+00, 9.06090000e+04], - [ 7.05000000e+00, 9.47120000e+04], - [ 7.35000000e+00, 9.79920000e+04], - [ 7.65000000e+00, 1.01590000e+05], - [ 7.95000000e+00, 1.05900000e+05], - [ 8.25000000e+00, 1.09020000e+05], - [ 8.55000000e+00, 1.12940000e+05], - [ 8.85000000e+00, 1.17490000e+05], - [ 9.15000000e+00, 1.20630000e+05], - [ 9.45000000e+00, 1.23850000e+05], - [ 9.75000000e+00, 1.27110000e+05], - [ 1.00500000e+01, 1.33170000e+05], - [ 1.03500000e+01, 1.35750000e+05], - [ 1.06500000e+01, 1.38000000e+05], - [ 1.09500000e+01, 1.42120000e+05], - [ 1.12500000e+01, 1.45870000e+05], - [ 1.15500000e+01, 1.49250000e+05], - [ 1.18500000e+01, 1.52450000e+05], - [ 1.21500000e+01, 1.56420000e+05], - [ 1.24500000e+01, 1.59460000e+05], - [ 1.27500000e+01, 1.63300000e+05], - [ 1.30500000e+01, 1.65540000e+05], - [ 1.33500000e+01, 1.68700000e+05], - [ 1.36500000e+01, 1.71120000e+05], - [ 1.39500000e+01, 1.75060000e+05], - [ 1.42500000e+01, 1.78070000e+05], - [ 1.45500000e+01, 1.80540000e+05], - [ 1.48500000e+01, 1.83390000e+05], - [ 1.51500000e+01, 1.85630000e+05], - [ 1.54500000e+01, 1.89110000e+05], - [ 1.57500000e+01, 1.91660000e+05], - [ 1.60500000e+01, 1.95150000e+05], - [ 1.63500000e+01, 1.97860000e+05], - [ 1.66500000e+01, 1.99910000e+05], - [ 1.69500000e+01, 2.03460000e+05], - [ 1.72500000e+01, 2.05680000e+05], - [ 1.75500000e+01, 2.08830000e+05], - [ 1.78500000e+01, 2.10760000e+05], - [ 1.81500000e+01, 2.12770000e+05], - [ 1.84500000e+01, 2.14450000e+05], - [ 1.87500000e+01, 2.17540000e+05], - [ 1.90500000e+01, 2.19990000e+05], - [ 1.93500000e+01, 2.22920000e+05], - [ 1.96500000e+01, 2.24490000e+05], - [ 1.99500000e+01, 2.25390000e+05], - [ 2.02500000e+01, 2.27330000e+05], - [ 2.05500000e+01, 2.29780000e+05], - [ 2.08500000e+01, 2.31760000e+05], - [ 2.11500000e+01, 2.34130000e+05], - [ 2.14500000e+01, 2.34930000e+05], - [ 2.17500000e+01, 2.36170000e+05], - [ 2.20500000e+01, 2.39060000e+05], - [ 2.23500000e+01, 2.39200000e+05], - [ 2.26500000e+01, 2.40280000e+05], - [ 2.29500000e+01, 2.41970000e+05], - [ 2.32500000e+01, 2.42410000e+05], - [ 2.35500000e+01, 2.44470000e+05], - [ 2.38500000e+01, 2.44630000e+05], - [ 2.41500000e+01, 2.45470000e+05], - [ 2.44500000e+01, 2.46940000e+05], - [ 2.47500000e+01, 2.46660000e+05], - [ 2.50500000e+01, 2.48020000e+05], - [ 2.53500000e+01, 2.49590000e+05], - [ 2.56500000e+01, 2.50060000e+05], - [ 2.59500000e+01, 2.49730000e+05], - [ 2.62500000e+01, 2.49300000e+05], - [ 2.65500000e+01, 2.49770000e+05], - [ 2.68500000e+01, 2.51230000e+05], - [ 2.71500000e+01, 2.50670000e+05], - [ 2.74500000e+01, 2.50110000e+05], - [ 2.77500000e+01, 2.53140000e+05], - [ 2.80500000e+01, 2.50100000e+05], - [ 2.83500000e+01, 2.49790000e+05], - [ 2.86500000e+01, 2.49210000e+05], - [ 2.89500000e+01, 2.48700000e+05], - [ 2.92500000e+01, 2.47540000e+05], - [ 2.95500000e+01, 2.47380000e+05], - [ 2.98500000e+01, 2.47910000e+05], - [ 3.01500000e+01, 1.88290000e+05], - [ 3.04500000e+01, 1.38640000e+05], - [ 3.07500000e+01, 1.07030000e+05], - [ 3.10500000e+01, 8.20110000e+04], - [ 3.13500000e+01, 5.92320000e+04], - [ 3.16500000e+01, 4.13020000e+04], - [ 3.19500000e+01, 2.75660000e+04], - [ 3.22500000e+01, 1.75590000e+04], - [ 3.25500000e+01, 9.84800000e+03], - [ 3.28500000e+01, 4.43200000e+03], - [ 3.31500000e+01, 1.59800000e+03], - [ 3.34500000e+01, 1.18000000e+02], - [ 3.37500000e+01, 0.00000000e+00], - [ 3.40500000e+01, 0.00000000e+00], - [ 3.43500000e+01, 0.00000000e+00], - [ 3.46500000e+01, 0.00000000e+00]]) - - minval = np.sqrt(np.spacing(np.float32(1))) - cj = interp.UnivariateSpline(d[:,0], - d[:,1]+minval, - k=1, s=0.5) - - # Interpolate the spline for our bin array into a background for our histogram - bg = cj(bincenters) - bg[bg < minval] = minval # do not allow division by zero - - return bg - -def ksi1hist(ksi1values, binwidth=0.5, return_everything=False): - """ histogram ksi1 data - - Parameters - ---------- - ksi1values : numpy array of floats - ksi1values, in degrees - - binwidth : numpy float (optional) - width of bins, in degrees - - return_everything : bool (optional) - flag for whether - - Returns - ------- - fcorr : numpy array - background-corrected ksi1 number fractions - - bm : numpy array - mean values for the histogram bins - - ba : (optional, with return_everything flag) numpy array - histogram bin edges - - fraw : (optional, with return_everything flag) numpy array - ksi1 number fractions before background correction - """ - import numpy as np - - # Histogram ksi data - ba = np.r_[0:35:binwidth] - nb = ba.size - bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) - fraw, tmp = np.histogram(ksi1values, ba, density=True) - fraw = fraw / np.sum(fraw) - - bg = _bg_ksi1(bm) - - # Correct the histogram for measurement - fcorr = fraw / bg - fcorr = fcorr / np.sum(fcorr) - - if return_everything == False: - return fcorr, bm - else: - return fcorr, bm, ba, fraw - -def ksi2hist(ksi2values, binwidth=0.5, return_everything=False): - """ histogram ksi2 data - - Parameters - ---------- - ksi1values : numpy array of floats - ksi1values, in degrees - - binwidth : numpy float (optional) - width of bins, in degrees - - return_everything : bool (optional) - flag for whether - - Returns - ------- - fcorr : numpy array - background-corrected ksi1 number fractions - - bm : numpy array - mean values for the histogram bins - - ba : (optional, with return_everything flag) numpy array - histogram bin edges - - fraw : (optional, with return_everything flag) numpy array - ksi1 number fractions before background correction - """ - import numpy as np - - # Histogram ksi data - ba = np.r_[0:35:binwidth] - nb = ba.size - bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) - fraw, tmp = np.histogram(ksi2values, ba, density=True) - fraw = fraw / np.sum(fraw) - - bg = _bg_ksi2(bm) - - # Correct the histogram for measurement - fcorr = fraw / bg - fcorr = fcorr / np.sum(fcorr) - - if return_everything == False: - return fcorr, bm - else: - return fcorr, bm, ba, fraw - -def ksi3hist(ksi3values, binwidth=0.5, return_everything=False): - """ histogram ksi3 data - - Parameters - ---------- - ksi3values : numpy array of floats - ksi1values, in degrees - - binwidth : numpy float (optional) - width of bins, in degrees - - return_everything : bool (optional) - flag for whether - - Returns - ------- - fcorr : numpy array - background-corrected ksi1 number fractions - - bm : numpy array - mean values for the histogram bins - - ba : (optional, with return_everything flag) numpy array - histogram bin edges - - fraw : (optional, with return_everything flag) numpy array - ksi1 number fractions before background correction - """ - import numpy as np - - # Histogram ksi data - ba = np.r_[0:35:binwidth] - nb = ba.size - bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) - fraw, tmp = np.histogram(ksi3values, ba, density=True) - fraw = fraw / np.sum(fraw) - - bg = _bg_ksi3(bm) - - # Correct the histogram for measurement - fcorr = fraw / bg - fcorr = fcorr / np.sum(fcorr) - - if return_everything == False: - return fcorr, bm - else: - return fcorr, bm, ba, fraw - -def fit_foldnorm(f, bm, return_info=False, accuracy=None, iterations=5000, - method = 'slsqp', mub=None, sgb=None): - """ fit ksi histogram to folded normal distribution - - Parameters - ---------- - fcorr : numpy float array - histogram bin counts or frequencies - - bm : numpy float array - middle values of bins - - return_info : bool {false} - return info from fitting procedure - - accuracy : float or None {None} - numerical accuracy of fitting procedure, default is floating point - - iterations : int - maximum number of iterations in fitting minimization - - method : {'slsqp', 'lsq'} - method for constrained function minimization, either sequential - least squares programming (default) or standard least squares. - - mub : float iterable of size 2 - lower bound search value for folded normal mu at location 0; - upper bound at location 1 - - sgb : float iterable of size 2 - lower bound search value for folded normal mu at location 0; - upper bound at location 1 - - Returns - ------- - mu : float - folded normal fit location parameter - sig : float - folded normal fit shape parameter - rss : float - final residual sum of squares difference between the fit and - the histogram - info : tuple - (1) the number of iterations required for fitting - (2) the exit mode from the optimizer (see notes) - (3) message describing exit mode - - Notes - ----- - - For very narrow distributions, it can be necessary to adjust the upper - bound for mu. When the distribution is narrow and well-behaved, np.inf - works fine (i.e., use mub=[np.spacing(np.float(1.0)), np.inf]). - - Minimization is performed by default with scipy.optimize.fmin_slsqp. - Refer to the documentation for this function for further details on exit - mode returns. - """ - import numpy as np - from scipy.special import erf - import scipy.optimize as spop - - if accuracy == None: # default to floating point accuracy - accuracy = np.spacing(np.float32(0)) - - # Estimate the distribution fitting parameters and then minimize the RMS - # Note that RMS error is linearly related to log likelihood, so we should get - # comparable results in both minimizations - m1 = np.dot(bm, f) # first moment - m2 = np.dot(bm**2.0, f) # second moment - m4 = np.dot(bm**4.0, f) # fourth moment - s = np.dot((bm - m1)**2.0, f) # standard deviation - x = np.arange(0.0, - np.amax(bm) + np.amax(bm) / 1000.0, - np.amax(bm) / 1000.0) - - # Elandt finds that the 1st and 2nd moments give a better approximation - # when m1/s is greater than about 1.35, and the 2nd and 4th moment - # method is better otherwise. - if m1/s > 1.35: # estimate parameters by 1st and 2nd moments - a = m1**2.0 / m2 # ratio of moments - def i0(x): # Eq 2 [2] - return 1.0 - 0.5 * (1.0 + erf(x / np.sqrt(2.0))) - def g(th, i0, a): - return (np.sqrt(2.0/np.pi) * \ - np.exp(-0.5 * th**2.0) - \ - th * \ - (1.0 - 2.0 * i0(-th)))**2.0 / (a * (1.0 + th**2.0)) - 1.0 - - # Find the value of G closest to zero, limiting iterations - # (fminbnd is not sufficiently robust in our case because G - # sometimes is close to zero but does not necessarily intersect the - # x axis in the limit of the half normal distribution). Assuming - # the function will only come close to zero once, then we will just - # search for the the solution to the equation iteratively - lb = 0.0 - ub = np.amax(x) - res = np.inf - nit = 0 - j = 0 - while (res > accuracy and - nit < iterations and - (ub - lb) / 1000.0 > accuracy): - - # set a search range - qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) - - tmp = np.absolute(g(qtm, i0, a)) - resn = np.amin(tmp) # get value closest to zero in range - j = np.argmin(tmp) - - ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound - lb = qtm[np.amax([j - 1, 0])] # get new lower bound - nit += 1 # increment number of iterations counter - th=qtm[j] - del qtm, j, ub, lb, res, g, a, i0, nit, tmp - - else: # Estimate Parameters by second and fourth moments - - b = m4 / m2**2.0 # ratio of moments - - def h(q, b): # Eq. 22 [2] - return b * (1.0 + q**2.0)**2.0 - (3.0 + 6.0 * q**2.0 + q**4.0) - - # Find the value of h closest to zero, limiting iterations. - # Same method as above. - lb = 0.0 - ub = np.amax(x) - res = np.inf - nit = 0 - j = 0 - while (res > accuracy and - nit < iterations and - np.absolute(ub-lb) / 1000.0 > accuracy): - - # set a search range - qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) - - tmp = np.absolute(h(qtm, b)) - resn = np.amin(tmp) # get value closest to zero in range - j = np.argmin(tmp) - if np.isreal(resn): - res = resn # in case min cannot be found - ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound - lb = qtm[np.amax([j - 1, 0])] # get new lower bound - nit += 1 # increment number of iterations counter - - th = qtm[j] - del qtm, j, ub, lb, res, h, b, nit, tmp - del m2, m4 - - # Compute initial guess values - sgi = np.sqrt((s**2.0 + m1**2.0) / (1.0 + th**2.0)) - mui = th * sgi - - # Define minimization function as a least squares problem - def ff(params): - import numpy as np - import scipy.stats as stats - fx = stats.foldnorm.pdf(bm, params[0], loc=0, scale=params[1]) - fx = fx/(np.sum(fx)+np.spacing(1.0)) - return np.sum((f-fx)**2.0) - - # Set upper and lower bounds - if mub==None: - # np.inf would be a nicer upper bound here but realistically - # we need to hold it under 15 for distributions that - # are not well-behaved. - mub = (np.sqrt(accuracy), 15.0) - - if sgb==None: - # np.inf would be a nicer upper bound here but realistically - # we need to hold it under 5 for distributions that - # are not well-behaved - # Zero would be a nicer lower bound but realistically it - # we shouldn't allow it to be less than the bin width. - sgb = (np.mean(np.diff(bm)), 5.0) - - if method=='slsqp': - # ----- Begin perform constrained minimization, option #1 ----------------- - out, rss, its, imode, smode = spop.fmin_slsqp(ff, x0=[mui, sgi], - bounds=[mub, sgb], - full_output=True, - iter=iterations, - acc=accuracy, - iprint=1) - mu = out[0] - sig = out[1] - # ----- End perform constrained minimization, option #1 ----------------- - else: - # ----- Begin perform constrained minimization, option #2 ----------------- - out = spop.least_squares(ff, x0=[mui, sgi], - bounds=([mub[0], sgb[0]], - [mub[1], sgb[1]]), - verbose=1, - max_nfev=iterations, - gtol=accuracy, - xtol=accuracy, - ftol=accuracy) - mu = out.x[0] - sig = out.x[1] - rss = out.cost - imode = out.status - smode = out.message - its = out.nfev - # ----- End perform constrained minimization, option #2 ----------------- - - if return_info==False: - return mu, sig - else: - return mu, sig, rss, (its, imode, smode) - -def foldnorm_mode(mu, sig, accuracy=None): - """ mode/peak of the folded normal distribution - - Parameters - ---------- - mu : float - location parameter - sig : float - shape parameter - - Returns - ------- - expected_value : float - E(Y) - - Notes - ----- - Equation comes from the documentation for the folded normal distribution in - the R statistics package. - http://hosho.ees.hokudai.ac.jp/~kubo/Rdoc/library/VGAM/html/fnormal1.html - """ - import scipy.stats as stats - import numpy as np - import scipy.optimize as spop - - if accuracy == None: # default to floating point accuracy - accuracy = np.spacing(np.float32(1.0)) - - fn = stats.foldnorm(mu, loc=0, scale=sig) - def chk(x): - val = -fn.pdf(x) - return val - - res = spop.minimize(chk, 0.0, method='nelder-mead', - options={'xtol': 1e-8, 'disp': False}) - - return res.x[0] - - -def fit_ksivals(ksi1vals, ksi2vals, ksi3vals, binwidth=0.5, makeplot=True, \ - accuracy=None, plotdpi=96, fmax=15.0, bmax=15.0, - method = 'slsqp', mub=None, sgb=None, plotlegcontains='all'): - """ - Parameters - ---------- - - ksi1vals : numpy float array - - ksi2vals : numpy float array - - ksi3vals : numpy float array - - makeplot : bool (optional, default=True) - return a plot of the ksi value distributions - - binwidth : float (optional, default=0.5) - width of bins on plot (if makeplot=True) - - fmax : float (optional, default=15.0) - maximum value of y axis of plots (if makeplot=True) - - bmax : float (optional, default=15.0) - maximum value of y axis of plots (if makeplot=True) - - plotdpi : int (optional, default=96) - dpi for plot output. Use for screen resolution. - - accuracy : float (optional) - defined accuracy of least squares optimization on folded normal fit. - Default value is floating point accuracy. - - method : {'slsqp', 'lsq'} - method for constrained function minimization, either sequential - least squares programming (default) or standard least squares. - - mub : float iterable of size 2 - lower bound search value for folded normal mu at location 0; - upper bound at location 1 - - sgb : float iterable of size 2 - lower bound search value for folded normal mu at location 0; - upper bound at location 1 - - plotlegcontains : list of strings - {'all', 'mo', 'sig', 'mu', 'ksibar', 'std', 'rss'} - defines which parameters to include in the plot legend - - Returns - ------- - result : numpy float array - np.array([[mo1, mean1, mu1, sig1, rss1], - [mo2, mean2, mu2, sig2, rss2], - [mo3, mean3, mu3, sig3, rss3]]); i.e., - - result[0, 0] : modal value of folded normal fit of ksi1 - - result[0, 1] : mean value of ksi1 histogram - - result[0, 2] : mu value for folded normal fit of ksi1 - - result[0, 3] : sigma value for folded normal fit of ksi1 - - result[0, 4] : rss for folded normal fit of ksi1 - - result[1, 0] : modal value of folded normal fit of ksi2 - - result[1, 1] : mean value of ksi2 histogram - - result[1, 2] : mu value for folded normal fit of ksi2 - - result[1, 3] : sigma value for folded normal fit of ksi2 - - result[1, 4] : rss for folded normal fit of ksi2 - - result[2, 0] : modal value of folded normal fit of ksi3 - - result[2, 1] : mean value of ksi3 histogram - - result[2, 2] : mu value for folded normal fit of ksi3 - - result[2, 3] : sigma value for folded normal fit of ksi3 - - result[2, 4] : rss for folded normal fit of ksi3 - - fig : (optional, returned if makeplot=True) - figure matplotlib handle - - ax1 : (optional, returned if makeplot=True) - ksi1 matplotlib axis handle - - ax2 : (optional, returned if makeplot=True) - ksi2 matplotlib axis handle - - ax3 : (optional, returned if makeplot=True) - ksi2 matplotlib axis handle - - Notes - ----- - - If plot is output, the 'observation probability corrected' histogram is - shown in white in the foreground. The darker gray background histogram is - the uncorrected ksi values, and the lighter gray is the observation - probability distribution. - - """ - - import numpy as np - import scipy.stats as stats - import matplotlib.pyplot as plt - - if plotlegcontains=='all': - plotlegcontains = ['mo', 'sig', 'mu', 'ksibar', 'std', 'rss'] - - f1c, bm, ba, f1r = ksi1hist(ksi1vals, - binwidth, return_everything=True) - - f2c, bm, ba, f2r = ksi2hist(ksi2vals, - binwidth, return_everything=True) - - f3c, bm, ba, f3r = ksi3hist(ksi3vals, - binwidth, return_everything=True) - - mu1, sig1, rss1, dat1 = fit_foldnorm(f1c, bm, return_info=True, - accuracy=accuracy, - method=method, - mub=mub, - sgb=sgb) - - mu2, sig2, rss2, dat2 = fit_foldnorm(f2c, bm, return_info=True, - accuracy=accuracy, - method=method, - mub=mub, - sgb=sgb) - - mu3, sig3, rss3, dat3 = fit_foldnorm(f3c, bm, return_info=True, - accuracy=accuracy, - method=method, - mub=mub, - sgb=sgb) - - mo1 = foldnorm_mode(mu1, sig1) - mo2 = foldnorm_mode(mu2, sig2) - mo3 = foldnorm_mode(mu3, sig3) - - mean1 = np.dot(bm, f1c) - mean2 = np.dot(bm, f2c) - mean3 = np.dot(bm, f3c) - - returnarray = np.array([[mo1, mean1, mu1, sig1, rss1], - [mo2, mean2, mu2, sig2, rss2], - [mo3, mean3, mu3, sig3, rss3]]) - - if makeplot==False: - - return returnarray - - else: - - # parameters we might want to adjust - xlimv = [0.0, bmax] - ylimv = [0.0, fmax] - xtickstep = 3 - ytickstep = 3 - bgdistclr = [0.85, 0.85, 0.85] - uncorrclr = [0.5, 0.5, 0.5] - ksi1faceclr = [1.0, 1.0, 1.0] - ksi2faceclr = [1.0, 1.0, 1.0] - ksi3faceclr = [1.0, 1.0, 1.0] - fontsz=12 - - xv = np.arange(0.0, np.amax(bm) + np.amax(bm) / 1000.0, - np.amax(bm) / 1000.0) - - # Create an awesome plot - fig = plt.figure(figsize=(15, 4), dpi=plotdpi) - fig.patch.set_facecolor('w') - - # ksi1 subplot - ax1 = fig.add_subplot(131) - f = stats.foldnorm.pdf(xv, mu1, scale=sig1) - f = xv.size*f/np.sum(f) - ax1.plot(xv, f, '-k', linewidth=1) - ax1.bar(bm, f1r*bm.size, width=binwidth, - facecolor=uncorrclr, edgecolor=uncorrclr) - ax1.bar(bm, f1c*bm.size, width=binwidth, - facecolor=ksi1faceclr, edgecolor=[0.0, 0.0, 0.0]) - bg = _bg_ksi1(xv) - bg = bg/np.sum(bg) - ax1.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) - legstring = [] - if any('mo' in s for s in plotlegcontains): - legstring.append( - r'$Mo_{\xi1}$' + - r' = {0:5.2f}'.format(mo1) + - r'$^\circ$' +'\n') - if any('mu' in s for s in plotlegcontains): - legstring.append( - r'$\mu_{\xi1}$' + r' = {0:5.2f}'.format(mu1) + - r'$^\circ$' +'\n') - if any('sig' in s for s in plotlegcontains): - legstring.append( - r'$\sigma_{\xi1}$' + r' = {0:5.2f}'.format(sig1) + - r'$^\circ$' + '\n') - if any('ksibar' in s for s in plotlegcontains): - legstring.append( - r'$<\xi_1>$' + r' = {0:5.2f}'.format(np.mean(ksi1vals)) + - r'$^\circ$' +'\n') - if any('std' in s for s in plotlegcontains): - legstring.append( - r'$St.Dev.(\xi_1)$' + r' = {0:5.2f}'.format(np.std(ksi1vals)) + - r'$^\circ$' + '\n') - if any('rss' in s for s in plotlegcontains): - legstring.append( - r'$RSS$' + r' = {0:5.2e}'.format(rss1)) - ax1.text(0.97, 0.97, - ''.join(legstring), - fontsize=fontsz, - bbox=dict(edgecolor='k', facecolor='w'), - transform = ax1.transAxes, - horizontalalignment='right', - verticalalignment='top') - ax1.set_xlim(xlimv) - ax1.set_ylim(ylimv) - ax1.set_xticks(np.arange(xlimv[0], xlimv[1]+xtickstep, xtickstep)) - ax1.set_yticks(np.arange(ylimv[0], ylimv[1]+ytickstep, ytickstep)) - - # ksi2 subplot - ax2 = fig.add_subplot(132) - f = stats.foldnorm.pdf(xv, mu2, scale=sig2) - f = xv.size*f/np.sum(f) - ax2.plot(xv, f, '-k', linewidth=1) - ax2.bar(bm, f2r*bm.size, width=binwidth, - facecolor=uncorrclr, edgecolor=uncorrclr) - ax2.bar(bm, f2c*bm.size, width=binwidth, - facecolor=ksi2faceclr, edgecolor=[0.0, 0.0, 0.0]) - bg = _bg_ksi2(xv) - bg = bg/np.sum(bg) - ax2.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) - legstring = [] - if any('mo' in s for s in plotlegcontains): - legstring.append( - r'$Mo_{\xi2}$' + - r' = {0:5.2f}'.format(mo2) + - r'$^\circ$' +'\n') - if any('mu' in s for s in plotlegcontains): - legstring.append( - r'$\mu_{\xi2}$' + r' = {0:5.2f}'.format(mu2) + - r'$^\circ$' +'\n') - if any('sig' in s for s in plotlegcontains): - legstring.append( - r'$\sigma_{\xi_2}$' + r' = {0:5.2f}'.format(sig2) + - r'$^\circ$' + '\n') - if any('ksibar' in s for s in plotlegcontains): - legstring.append( - r'$<\xi_2>$' + r' = {0:5.2f}'.format(np.mean(ksi2vals)) + - r'$^\circ$' +'\n') - if any('std' in s for s in plotlegcontains): - legstring.append( - r'$St.Dev.(\xi2)$' + r' = {0:5.2f}'.format(np.std(ksi2vals)) + - r'$^\circ$' + '\n') - if any('rss' in s for s in plotlegcontains): - legstring.append( - r'$RSS$' + r' = {0:5.2e}'.format(rss2)) - ax2.text(0.03, 0.97, - ''.join(legstring), - fontsize=fontsz, - bbox=dict(edgecolor='k', facecolor='w'), - transform = ax2.transAxes, - horizontalalignment='left', - verticalalignment='top') - ax2.set_xlim(xlimv) - ax2.set_ylim(ylimv) - ax2.set_xticks(np.arange(xlimv[0], xlimv[1]+xtickstep, xtickstep)) - ax2.set_yticks([]) - - - # ksi2 subplot - ax3 = fig.add_subplot(133) - f = stats.foldnorm.pdf(xv, mu3, scale=sig3) - f = xv.size*f/np.sum(f) - ax3.plot(xv, f, '-k', linewidth=1) - ax3.bar(bm, f3r*bm.size, width=binwidth, - facecolor=uncorrclr, edgecolor=uncorrclr) - ax3.bar(bm, f3c*bm.size, width=binwidth, - facecolor=ksi3faceclr, edgecolor=[0.0, 0.0, 0.0]) - bg = _bg_ksi3(xv) - bg = bg/np.sum(bg) - ax3.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) - legstring = [] - if any('mo' in s for s in plotlegcontains): - legstring.append( - r'$Mo_{\xi3}$' + - r' = {0:5.2f}'.format(mo3) + - r'$^\circ$' +'\n') - if any('mu' in s for s in plotlegcontains): - legstring.append( - r'$\mu_{\xi3}$' + r' = {0:5.2f}'.format(mu3) + - r'$^\circ$' +'\n') - if any('sig' in s for s in plotlegcontains): - legstring.append( - r'$\sigma_{\xi3}$' + r' = {0:5.2f}'.format(sig3) + - r'$^\circ$' + '\n') - if any('ksibar' in s for s in plotlegcontains): - legstring.append( - r'$<\xi_3>$' + r' = {0:5.2f}'.format(np.mean(ksi3vals)) + - r'$^\circ$' +'\n') - if any('std' in s for s in plotlegcontains): - legstring.append( - r'$St.Dev.(\xi_3)$' + r' = {0:5.2f}'.format(np.std(ksi3vals)) + - r'$^\circ$' + '\n') - if any('rss' in s for s in plotlegcontains): - legstring.append( - r'$RSS$' + r' = {0:5.2e}'.format(rss3)) - ax3.text(0.03, 0.97, - ''.join(legstring), - fontsize=fontsz, - bbox=dict(edgecolor='k', facecolor='w'), - transform = ax3.transAxes, - horizontalalignment='left', - verticalalignment='top') - ax3.set_xlim(xlimv) - ax3.set_ylim(ylimv) - ax3.set_xticks(np.arange(xlimv[0], xlimv[1]+xtickstep, xtickstep)) - ax3.set_yticks([]) - - ax1.set_ylabel(r'Relative Number Fraction', fontsize=fontsz*1.2) - ax1.set_xlabel(r'$\xi_1$ ($^\circ$)', fontsize=fontsz*1.2) - ax2.set_xlabel(r'$\xi_2$ ($^\circ$)', fontsize=fontsz*1.2) - ax3.set_xlabel(r'$\xi_3$ ($^\circ$)', fontsize=fontsz*1.2) - - for label in (ax1.get_yticklabels() + ax1.get_xticklabels() + - ax2.get_xticklabels() + ax3.get_xticklabels()): - label.set_fontsize(fontsz) - - fig.subplots_adjust(bottom=0.15) - plt.tight_layout() - plt.draw() - - return returnarray, \ - fig, ax1, ax2, ax3 - -# ---------------------------------------------------------------------------- - -def CSLcrit(name='Brandon', sigmas=None): - ''' calculate CSL criteria - - **CSLcrit** calculates the angular deviation from the ideal misorientation - relationship allowed by different *named* coincident site lattice (CSL) - criteria of the form: - - ..math :: - \theta_c=\theta_0 / sigma^eta - - Parameters - ---------- - name : {'Brandon', 'Pumphrey', 'Ishida-McLean', 'Palumbo-Aust'} - - CSL criteria name. Defaults to Brandon. Also accepts 'B' for Brandon, - 'P' for Pumphrey, 'IM' for Ishida-McLean, and 'PA' for Palumbo-Aust. - String argument is case insensitive. Dash can be replaced by '&' or - 'and' with or without spaces around it. - - sigmas : None - - Sigma values of interest. If None, returns all values from 1 to 51. - - Returns - ------- - thc : numpy float array - - Notes - ----- - This is a Python translation of a Matlab function of the same name written - by EJP 2011-05-02 while at Ruhr-Universitaet Bochum. This function was used - for the analysis in references [1]_ and [2]_. - - For general reference on Coincident Site Lattice theory, see - reference [3]_. The named CSL criteria used here from the following: - Brandon [4]_, Pumphrey [5]_, Palumbo & Aust [6]_, and Ishida & McLean [7]_. - - References - ---------- - .. [1] Otto et al, J Mater Sci 47 (2012) p2915-2927. - .. [2] Otto et al, Acta Mater 60 (2012) p2982-2998. - .. [3] Warrington & Boon, Acta Metall 23 (1975) p599 - .. [4] Brandon, Acta Metall 14 (1966) p1479 - .. [5] Pumphrey, in Grain Boundary Structure & Properties, London: Academic Press (1976) p139 - .. [6] Palumbo & Aust, Acta Metall 38 (1990) p2343 - .. [7] Ishida & McLean, Philos Mag 27 (1973) p1125 - ''' - import numpy as np - import warnings - - # Remove - name = name.replace('&', '').replace('and', '').replace('-','') - name = name.replace(' ', '').lower() - - if sigmas == None: - - sig = np.arange(1, 53, 2, dtype=np.float) - - else: - - sig = np.asanyarray(sigmas, dtype=np.float) - - if np.any(sig % 2): - - warnings.warn('Only odd values of sigma are valid. ' + \ - 'Odd values have been removed.') - - if (name == 'brandon') or (name == 'b'): - - th0 = 15.0 - eta = 1.0 / 2.0 - - elif (name == 'pumphrey') or (name =='p'): - - th0 = 15.0 - eta = 2.0 / 3.0 - - elif (name == 'palumboaust') or (name == 'pa'): - - th0 = 15.0 - eta = 5.0 / 6.0 - - elif (name == 'ishidamclean') or (name == 'im'): - - th0 = 8.0 - eta = 1.0 - - else: - - warnings.warn('CSLcrit: unrecognized named criterion. ' + \ - 'Proceeding with Brandon criterion.') - - th0 = 15.0 - eta = 1.0 / 2.0 - - # Convert theta to radians - th0 = th0 * np.pi / 180.0 - - # Calculate criteria - thc = th0 / sig**eta - - return thc - -# ---------------------------------------------------------------------------- - -#def genCSLs(sigmas=None): -#function [CSL,SQ,SV,SN]=GenCSL(varargin) -#% GenCSL generates the exact misorientations of the coincident site lattice -#% theory as rotation matrices and as quaternions. -#% -#% INPUTS: varargin can be a list of CSL values (as in, for example, -#% [3, 9, 27]). If left blank, it generates the CSLs up to Sigma49. -#% -#% OUTPUTS: the CSL in a structural format (CSL), or as lists of -#% quaternions (SQ), their corresponding sigma values (SV), and -#% their standard names (SN). -#% -#% REFERENCES: -#% [1] H. Grimmer, W. Bollman, D. H. Warrington. "Coincident-Site Lattices -#% and Complete Pattern-Shift Lattices in Cubic Crystals." Acta Cryst. A30 -#% (1974) p197. -#% [2] D. H. Warrington, P. Bufalini. "The Coincident Site Lattice and Grain -#% Boundaries." Scripta Metall. 5 (1971) p771. -#% -#% NOTES: Function exactly reproduces Table 1 from Grimmer et al, except for -#% Sigma31b, which appears to be in error in the paper. -#% -#% ------------------------------------------------------------------------- -#% 2011-05-01 | Eric Payton, Ruhr-Universitaet Bochum (payton.28[at]osu.edu) -#% ------------------------------------------------------------------------- -#% This program is provided without any guarantee of correctness. -#% If you modify it and/or improve it, please kindly share with me your new -#% and improved version to the email address above. Thanks! -#%---------------------------------------------------------------------------- - - - -# % Remove symmetric redundancies by putting into fundamental region -# O=RMat2Quat(SM);RR=zeros(length(SM),4); -# for ind=1:length(SM) -# q=QuatProd(repmat(O(ind,:),size(SYM,1),1),SYM); -# [an,ax]=Quat2AngAx(q); -# ax=sort(abs(ax(an==min(an),:)),2,'descend');an=min(an);ax=ax(1,:); -# RR(ind,:)=[an,ax]; -# end -# [~,b]=unique(sigdec(RR,5),'rows');RR=RR(b,:); -# -# % Sort the results by increasing angle in axis/angle description, such -# % that, in the event of multiple results, th.a1 -# for ind=1:size(RR,1) -# tmp=Quat2RMat(RR(ind,:)); -# eval(['CSL.S' num2str(S) char(96+ind) ... -# '=tmp{1};']); -# SN{ee}=['\Sigma' num2str(S) char(96+ind)];ee=ee+1; -# end -# else -# tmp=Quat2RMat(RR); -# eval(['CSL.S' num2str(S) '=tmp{1};']); -# SN{ee}=['\Sigma' num2str(S)];ee=ee+1; -# end -# -# % Store results as a list of quaternions corresponding Sigma values -# SQ=vertcat(SQ,RR); -# SV=vertcat(SV,repmat(S,size(RR,1),1)); -# -# clear SM O an ax RR b ind % clean up -#end % loop over all desired N -#clear Ndex S SYM TT X h1 k1 l1 h2 k2 l2 hkl1 hkl2 hkl3 q N ee % clean up -# -#% This is it. - -# ---------------------------------------------------------------------------- - -def genKitahara_KS(): - ''' KS OR matrices - - **genKitahara_KS** returns the rotation matrices for the 24 variants of the - Kurdjumov-Sachs orientation relationship in the order presented in Table 2 - of Kitahara [1]_. - - Parameters - ---------- - None - - Returns - ------- - V : 24 x 1 list of 3 x 3 numpy matrices - - Notes - ----- - Rounding the results of Kitahara_KS and removing redundancies gives the - list of Bain correspondence matrices. - - See also - -------- - genKitahara_NW, genBainCorrMatrices - - References - ---------- - .. [1] Kitahara et al, Acta Mater 54 (2006) p1279. - ''' - import numpy as np - - Pg = [0] * 24 - Pa = [0] * 24 - Dg = [0] * 24 - Da = [0] * 24 - - i = 0 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 0.0, 1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 1 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 0.0, 1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 2 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, 1.0, -1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 3 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, 1.0, -1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 4 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, -1.0, 0.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 5 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, -1.0, 0.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 6 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 0.0, -1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 7 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 0.0, -1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 8 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, -1.0, 0.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 9 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, -1.0, 0.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 10 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, 1.0, 1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 11 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, 1.0, 1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 12 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, -1.0, 1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 13 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, -1.0, 1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 14 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 0.0, -1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 15 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 0.0, -1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 16 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 1.0, 0.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 17 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 1.0, 0.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 18 - Pg[i] = [ 1.0, 1.0, -1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 1.0, 0.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 19 - Pg[i] = [ 1.0, 1.0, -1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 1.0, 0.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 20 - Pg[i] = [ 1.0, 1.0, -1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, -1.0, -1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 21 - Pg[i] = [ 1.0, 1.0, -1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 0.0, -1.0, -1.0] - Da[i] = [-1.0, 1.0, -1.0] - - i = 22 - Pg[i] = [ 1.0, 1.0, -1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 0.0, 1.0] - Da[i] = [-1.0, -1.0, 1.0] - - i = 23 - Pg[i] = [ 1.0, 1.0, -1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 0.0, 1.0] - Da[i] = [-1.0, 1.0, -1.0] - - V = [] - - for i in range(0, 24): - - a = Dg[i] / np.linalg.norm(Dg[i]) - b = np.cross(Dg[i], Pg[i]) / np.linalg.norm(np.cross(Dg[i], Pg[i])) - c = Pg[i] / np.linalg.norm(Pg[i]) - M = np.vstack([a, b, c]) - - a = Da[i] / np.linalg.norm(Da[i]) - b = np.cross(Da[i], Pa[i]) / np.linalg.norm(np.cross(Da[i], Pa[i])) - c = Pa[i] / np.linalg.norm(Pa[i]) - A = np.vstack([a, b, c]) - - V.append(np.linalg.solve(A, M)) - - return V - -# --------------------------------------------------------------------------- - -def genKitahara_NW(): - ''' NW OR matrices - - **genKitahara_NW** returns the rotation matrices for the 12 variants of the - Nishiyama-Wasserman orientation relationship in the order presented in - Table 2 of Kitahara [1]_. - - Parameters - ---------- - None - - Returns - ------- - V : 21 x 1 list of 3 x 3 numpy matrices - - Notes - ----- - Table in Reference [1]_ has an error in the gamma direction for - variant #10! For table 10 to agree with Table 6, the gamma direction must - be [ 2 -1 1] and not [-1 2 1]. - - See also - -------- - genKitahara_KS, genBainCorrMatrices - - References - ---------- - .. [1] Kitahara et al, Mater Charact (2006) p378. - ''' - import numpy as np - - Pg = [0] * 12 - Pa = [0] * 12 - Dg = [0] * 12 - Da = [0] * 12 - - i = 0 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 2.0, -1.0, -1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 1 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 2.0, -1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 2 - Pg[i] = [ 1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, -1.0, 2.0] - Da[i] = [ 0.0, -1.0, 1.0] - - - i = 3 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-2.0, -1.0, -1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 4 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, 2.0, -1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 5 - Pg[i] = [-1.0, 1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 1.0, -1.0, 2.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 6 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 2.0, 1.0, -1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 7 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, -2.0, -1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 8 - Pg[i] = [ 1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 1.0, 2.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 9 - Pg[i] = [-1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [ 2.0, -1.0, 1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 10 - Pg[i] = [-1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, 2.0, 1.0] - Da[i] = [ 0.0, -1.0, 1.0] - - i = 11 - Pg[i] = [-1.0, -1.0, 1.0] - Pa[i] = [ 0.0, 1.0, 1.0] - Dg[i] = [-1.0, -1.0, -2.0] - Da[i] = [ 0.0, -1.0, 1.0] - - V = [] - - for i in range(0, 12): - - a = Dg[i] / np.linalg.norm(Dg[i]) - b = np.cross(Dg[i], Pg[i]) / np.linalg.norm(np.cross(Dg[i], Pg[i])) - c = Pg[i] / np.linalg.norm(Pg[i]) - M = np.vstack([a, b, c]) - - a = Da[i] / np.linalg.norm(Da[i]) - b = np.cross(Da[i], Pa[i]) / np.linalg.norm(np.cross(Da[i], Pa[i])) - c = Pa[i] / np.linalg.norm(Pa[i]) - A = np.vstack([a, b, c]) - - V.append(np.linalg.solve(A, M)) - - return V - -# --------------------------------------------------------------------------- - -def genZirconia(): - - from cryspy import rot - import numpy as np - #%% Lattice correspondence A - - Abca1 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, - g21=1.0, g22=0.0, g23=0.0, - g31=0.0, g32=1.0, g33=0.0) - - Abca2 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, - g21=-1.0, g22=0.0, g23=0.0, - g31=0.0, g32=-1.0, g33=0.0) - - Acba1 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, - g21=0.0, g22=1.0, g23=0.0, - g31=-1.0, g32=0.0, g33=0.0) - - Acba2 = rot.rmat(g11=0.0, g12=0.0, g13=1.0, - g21=0.0, g22=-1.0, g23=0.0, - g31=1.0, g32=0.0, g33=0.0) - - Abca3 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, - g21=-1.0, g22=0.0, g23=0.0, - g31=0.0, g32=1.0, g33=0.0) - - Abca4 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, - g21=1.0, g22=0.0, g23=0.0, - g31=0.0, g32=-1.0, g33=0.0) - - Acba3 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, - g21=0.0, g22=-1.0, g23=0.0, - g31=-1.0, g32=0.0, g33=0.0) - - Acba4 = rot.rmat(g11=0.0, g12=0.0, g13=-1.0, - g21=0.0, g22=1.0, g23=0.0, - g31=1.0, g32=0.0, g33=0.0) - - corrA = [Abca1, Abca2, Acba1, Acba2, Abca3, Abca4, Acba3, Acba4] - corrtA = ['Abca1', 'Abca2', 'Acba1', 'Acba2', 'Abca3', 'Abca4', 'Acba3', 'Acba4'] - - - Bcab1 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, - g21=0.0, g22=0.0, g23=1.0, - g31=1.0, g32=0.0, g33=0.0) - - Bcab2 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, - g21=0.0, g22=0.0, g23=1.0, - g31=-1.0, g32=0.0, g33=0.0) - - Bacb1 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, - g21=0.0, g22=0.0, g23=1.0, - g31=0.0, g32=1.0, g33=0.0) - - Bacb2 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, - g21=0.0, g22=0.0, g23=1.0, - g31=0.0, g32=-1.0, g33=0.0) - - Bcab3 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, - g21=0.0, g22=0.0, g23=-1.0, - g31=-1.0, g32=0.0, g33=0.0) - - Bcab4 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, - g21=0.0, g22=0.0, g23=1.0, - g31=1.0, g32=0.0, g33=0.0) - - Bacb3 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, - g21=0.0, g22=0.0, g23=-1.0, - g31=0.0, g32=-1.0, g33=0.0) - - Bacb4 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, - g21=0.0, g22=0.0, g23=-1.0, - g31=0.0, g32=1.0, g33=0.0) - - corrB = [Bcab1, Bcab2, Bacb1, Bacb2, Bcab3, Bcab4, Bacb3, Bacb4] - corrtB = ['Bcab1', 'Bcab2', 'Bacb1', 'Bacb2', 'Bcab3', 'Bcab4', 'Bacb3', 'Bacb4'] - - Cabc1 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, - g21=0.0, g22=1.0, g23=0.0, - g31=0.0, g32=0.0, g33=1.0) - - Cabc2 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, - g21=0.0, g22=-1.0, g23=0.0, - g31=0.0, g32=0.0, g33=1.0) - - Cbac1 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, - g21=-1.0, g22=0.0, g23=0.0, - g31=0.0, g32=0.0, g33=1.0) - - Cbac2 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, - g21=1.0, g22=0.0, g23=0.0, - g31=0.0, g32=0.0, g33=1.0) - - Cabc3 = rot.rmat(g11=-1.0, g12=0.0, g13=0.0, - g21=0.0, g22=1.0, g23=0.0, - g31=0.0, g32=0.0, g33=-1.0) - - Cabc4 = rot.rmat(g11=1.0, g12=0.0, g13=0.0, - g21=0.0, g22=-1.0, g23=0.0, - g31=0.0, g32=0.0, g33=-1.0) - - Cbac3 = rot.rmat(g11=0.0, g12=-1.0, g13=0.0, - g21=-1.0, g22=0.0, g23=0.0, - g31=0.0, g32=0.0, g33=-1.0) - - Cbac4 = rot.rmat(g11=0.0, g12=1.0, g13=0.0, - g21=1.0, g22=0.0, g23=0.0, - g31=0.0, g32=0.0, g33=-1.0) - - corrC = [Cabc1, Cabc2, Cbac1, Cbac2, Cabc3, Cabc4, Cbac3, Cbac4] - corrtC = ['Cabc1', 'Cabc2', 'Cbac1', 'Cbac2', 'Cabc3', 'Cabc4', 'Cbac3', 'Cbac4'] - - return np.hstack([corrA, corrB, corrC]), np.hstack([corrtA, corrtB, corrtC]) - -# --------------------------------------------------------------------------- - -def lattParamAus_Onink(at_pct_carbon, temperature_degC): - ''' estimate austenite lattice parameter at temperature - - **latticeParamAus_Onink** estimates the lattice parameter of the - face-centered cubic austenite phase at high temperature as a function of - carbon content based on the best fit equations of Onink et al [1]_. - Lattice parameter is returned in angstroms. - - Parameters - ---------- - at_pct_carbon : float - atomic percent of carbon - - temperature_degC : float - temperature in degrees celsius - - Returns - ------- - a0 : float - lattice parameter of austenite (gamma) phase in angstroms - - Notes - ----- - Verified validity range varies with temperature and carbon content - (Ref [1]_, Table 2): - - ======= ====================== - at % C Temperature Range in K - ======= ====================== - 0.05 1080-1250 - 1.30 1080-1250 - 1.75 1060-1250 - 2.60 1030-1250 - 3.79 1000-1250 - ======= ====================== - - References - ---------- - .. [1] M Onink, C. M. Brakman, F. D. Tichelaar, E. J. Mittemeijer, - S. van der Zwaag. "The lattice parameters of austenite and ferrite - in Fe-C alloys as functions of carbon concentration and - temperature." Scripta Metall Mater 29 (1993) p1011-1016. - - Examples - -------- - >>> # Reproduce Figure 2 from Onink et al [1]_. The x-axis will be similar - >>> # but not *exactly* the same because we are plotting atomic percent - >>> # instead of number C atoms / 100 Fe atoms. - >>> import numpy as np - >>> import matplotlib.pyplot as plt - >>> from scipy import constants - >>> temperature_degC = constants.K2C(np.arange(1000.0, 1275.0, 25.0)) - >>> for at_pct_carbon in [0.0, 1.3, 1.75, 2.6, 3.65]: - >>> print(at_pct_carbon) - >>> a0 = latticeParamAus_Onink(at_pct_carbon, temperature_degC) - >>> # For consistency in cryspy, our inputs are in degC - >>> # and output is in angstroms. - >>> # Converting for comparison to the published plot... - >>> plt.plot(constants.C2K(temperature_degC), a0/10.0) - >>> plt.legend(['Fe', 'Fe-0.3C', 'Fe-0.4C', 'Fe-0.6C', 'Fe-0.8C']) - >>> plt.ylim([0.3630, 0.3690]) - >>> plt.xlim([950, 1350]) - >>> plt.xlabel('Temperature (K)') - >>> plt.ylabel('Lattice parameter of austenite (nm)') - >>> plt.show() - ''' - from scipy import constants - tempK = constants.C2K(temperature_degC) - a0 = (0.363067 + 0.000783 * at_pct_carbon) * \ - (1.0 + (24.92 - 0.51 * at_pct_carbon) * 1.0E-6 * (tempK - 1000.0)) - return a0 * 10.0 - -# --------------------------------------------------------------------------- - -def lattParamMart_Kurdjumov(c_wt_pct, a0_angstroms): - ''' RTP Fe alpha prime parameters as function of wt%C - - **lattParams_Kurdjumov** calculates room temperature lattice parameters - for austenite and martensite from carbon content. - - Parameters - ---------- - c_wt_pct : float - weight percent carbon - a0 : float - austenite lattice parameter in angstroms - - Returns - ------- - a : float - martensite a axis lattice parameter in angstroms - c : float - martensite c axis lattice parameter in angstroms - - Notes - ----- - Equations come from Ref. [1]_ with reference to Ref. [2]_ and [3]_ for - discovery, [4]_ and [5]_ for validation, and [6]_ and [7]_ for showing that - the same equations are acceptable for alloy steels. - - References - ---------- - .. [1] Z. Fan et al. Phys Rev B 52(14) (1995) p9979. - .. [2] G. V. Kurdjumow and E. Kaminsky, Nature 122 (1928) p425. - .. [3] G. V. Kurdjumov and E. Kaminsky, Z Phys 53 (1929) p696. - .. [4] G. V. Kurdjumov, J ISIL 195 (1960) p26. - .. [5] C. S. Roberts, Trans AIME 197 (1953) p203 - .. [6] Z. Nishiyama and M. Doi, J. JIM 8 (1944) p305 - .. [7] P. G. Winchell and M. Cohen, Trans AIME 55 (1962) p347. - ''' - # c in atomic percent - - a = a0_angstroms - 0.013 * c_wt_pct - c = a0_angstroms + 0.116 * c_wt_pct - - return a, c - -# ---------------------------------------------------------------------------- - -def lattParams_RobertsCohen(c_at_pct): - ''' RTP Fe alpha prime & gamma parameters as function of at%C - - **lattParams_RobertsCohen** calculates room temperature lattice parameters - for austenite and martensite from carbon content. - - Parameters - ---------- - c_at_pct : float - atomic percent carbon - - Returns - ------- - a0 : float - austenite lattice parameter in angstroms - a : float - martensite a axis lattice parameter in angstroms - c : float - martensite c axis lattice parameter in angstroms - - Notes - ----- - Atomic pct equations come from Ref. [1]_ using the data obtained in - Ref. [2]_. - - References - ---------- - .. [1] M. Cohen, "The Strengthening of Steel." Transactions of the - Metallurgical Society of AIME 224 (1962) p637-657. - .. [2] C.S. Roberts, "Technical Note: Effect of Carbon on the Volume - Fractions and Lattice Parameters Of Retained Austenite and - Martensite." Journal of Metals, AIME Transactions (Institute of - Metals Division) 197 (1953). p203-204. - ''' - # c in atomic percent - - a0 = 3.548 + 0.0094 * c_at_pct # units of angstroms - a = 2.861 - 0.0028 * c_at_pct - c = 2.861 + 0.0247 * c_at_pct - - return a0, a, c - -# ---------------------------------------------------------------------------- - -def lattParams_Lee(c_at_pct, temp_degC): - ''' Temperature dependent alpha & gamma prime parameters as function of %C - - Parameters - ---------- - c_at_pct : float - atomic percent carbon in austenite - temp_degC : float - temperature in degrees C - - Returns - ------- - a0 : float - austenite lattice parameter in angstroms - a : float - martensite a axis lattice parameter in angstroms - c : float - martensite c axis lattice parameter in angstroms - - Notes - ----- - We assume in this function that the carbon content in austenite and - martensite has to be the same, since the martensite forms from the - austenite. - - References - ---------- - .. [1] S-J Lee et al. Acta Mater 55 (2007) p875 - ''' - - tt = temp_degC + 273.15 - - betaG = (24.9 - 0.5 * c_at_pct) * 1.0E-6 - a0 = (0.36306 + 7.83E-4 * c_at_pct) * (1.0 + betaG * (tt - 1000.0)) - - betaM = (14.9 - 1.9 * c_at_pct) * 1.0E-6 - c = (0.28610 + 0.0025855 * c_at_pct) * (1.0 + betaM * (tt - 273.0)) - a = (0.28610 - 0.0002898 * c_at_pct) * (1.0 + betaM * (tt - 273.0)) - - return a0, a, c - -# --------------------------------------------------------------------------- - -def martstart_YangBhadeshiaAdjustment(G, Ms0=363.5, f=0.01, m=0.05, a=1, b=0.2689): - ''' - Predicts reduction in martensite start temperature with reduction in - austenite grain size according to geometric model. - - Parameters - ---------- - Ms0 : float - Martensite start temperature at limit of infinitely large grains, which - is a function of composition. - f : float - Detectable martensite fraction - m : float - Martensite plate aspect ratio - a : float - fitting constant, units 1/mm^3 - b : float - fitting constant, unitless - G : float - ASTM grain size number - - Returns - ------- - Ms : float - Martensite start temperature, in degC - - Notes - ----- - All input values default to those from [1]_. The user should note that Ms0 - will vary with composition. - - - References - ---------- - ..[1] Yang & Bhadeshia, Scripta Mater 60 (2009) p493 - ..[2] p90-91 of Underwood, Quantitative Stereology. Reading, Mass: Addison- - Wesley Publishing Co., 1970. - ''' - import numpy as np - - # Convert the ASTM grain size to the mean grain section area in sq microns - A_gamma = 100.0 * 25.4**2 / 2.0**(G - 1.0) - - # Convert to sq millimeters - A_gamma = A_gamma / 1000.0**2 - - # For lack of a better option at this time, we will assume spherical grains - # and use the equivalent spherical volume for the mean cross sectional - # area. The mean sectional area of a sphere is 2/3*pi*r^2. If we set this - # equal to unity, then r=sqrt(3/2/pi). The volume of a sphere with this - # radius is 4*pi/3*(3/2/pi)^3/2 - V_gamma = A_gamma * 4.0 * np.pi / 3.0 * (3.0 / 2.0 / np.pi)**(3.0 / 2.0) - - Ms = Ms0 - (1.0 / b) * np.log((1.0 / (a * V_gamma)) * \ - (np.exp(-np.log(1.0 - f) / m) - 1.0) + 1.0) - - return Ms - -# --------------------------------------------------------------------------- - -def martstart_vanBohemen2017(G, C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, - Mo=0.0, Si=0.0, K_C=370.0, K_HP=350.0, - K_1=1015.0, D_C=15.0): - ''' predicts the martensite start temperature from composition and austenite grain size - - Parameters - ---------- - G : float - Austenite ASTM grain size number - C : float - wt% C - Mn : float - wt% Mn - Cr : float - wt% Cr - Ni : float - wt% Ni - Mo : float - wt% Mo - Si : float - wt% Si - K_C : float - J/mol - K_HP : float - J*(microns^0.5)/mol - D_C : float - Critical austenite equivalent diameter grain size for single packet - microstructures, with units of microns - K_1 : float {1015.0} - A constant depending on defect size, strain, and interfacial energyies, - with units of J/mol - - - Returns - ------- - Ms : float - martensite start temperature in degC - - Notes - ----- - The model does not account for the compositional effects of Nb, Ti, V, Al, - Cu,W and Co, since limited data is available for a thorough validation to - evaluate the coefficients Km for these elements [1]_. - - - References - ---------- - .. [1] van Bohemen & Morsdorf, Acta Mater 125 (2017) p401. - ''' - import numpy as np - - # The following are the fitting parameters and constants - # used by van Bohemen & Morsdorf - - # Convert ASTM GS to "diameter" in microns. - # Note that this is not really a diameter, but the square root of area. - D_gamma = np.sqrt(100.0 * 25.4**2 / 2.0**(G - 1.0)) - - # Non-chemical free energy term from the reduction in martensite lath - # aspect ratio that occurs as grain size is reduced - W_C = K_C * np.exp(-6.0 * D_gamma / D_C) - - # Non-chemical free energy term associated with Hall-Petch hardening - # of the austenite - W_HP = K_HP / np.sqrt(D_gamma) - - # Compositional dependence of athermal frictional work - # (as fit to experimental data) - W_mu = 670.0 * np.sqrt(C) + \ - np.sqrt((195.0 * np.sqrt(Mn))**2 + \ - (140.0 * np.sqrt(Si))**2 + \ - (170.0 * np.sqrt(Cr))**2 + \ - ( 5.0 * np.sqrt(Ni))**2 + \ - (205.0 * np.sqrt(Mo))**2) - - # Estimated change in Gibbs energy due to defect content, athermal - # frictional work, Hall-Petch hardening, and martensite lath aspect ratio - delGc = K_1 + W_mu + W_HP + W_C - - # van Bohemen's best fit to FactSage prediction of martensite start - # temperatures - T1 = 718.3 - 291.0*C - 24.0*Mn - 1.8*Si - 5.6*Cr - 18.4*Ni + 3.5*Mo - - # - ms = T1 - delGc / 7.22 - - return ms - -# --------------------------------------------------------------------------- - -def martstart_vanBohemen2012(C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, - Mo=0.0, Si=0.0): - ''' predicts the martensite start temperature from composition - - Parameters - ---------- - C : float - wt% C - Mn : float - wt% Mn - Cr : float - wt% Cr - Ni : float - wt% Ni - Mo : float - wt% Mo - Si : float - wt% Si - - Returns - ------- - Ms : float - martensite start temperature in degC - - Notes - ----- - Non-linear fit may be better for high C than Steven-Haynes. - - - References - ---------- - .. [1] van Bohemen, Mater Sci Tech 28 (2012) p487. - ''' - import numpy as np - sumKixi = 31.*Mn + 13.*Si + 10.*Cr + 18.*Ni + 12.*Mo - ms = 565.0 - sumKixi - 600.0*(1.0-np.exp(-0.96*C)) - return ms - -# ---------------------------------------------------------------------------- - -def martstart_StevenHaynesMod(C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, - Mo=0.0, Co=0.0, Si=0.0): - ''' predicts the martensite start temperature from composition - - Parameters - ---------- - C : float - wt% C - Mn : float - wt% Mn - Cr : float - wt% Cr - Ni : float - wt% Ni - Mo : float - wt% Mo - Co : float - wt% Co - Si : float - wt% Si - - Returns - ------- - Ms : float - martensite start temperature in degC - - Notes - ----- - - Implements modification from Ref. _[2] to equation originally from - Ref. _[1]. - - The equation should work reasonably well under the limits of - 0.6 wt%C, 4.9 wt% Mn, 12.2 wt% Cr, 12.5 wt% Ni, and 5.4 wt% Mo, 3.2 wt% Si, - and 9 wt% Co. - - Underestimates start temperature at high Mo contents - - References - ---------- - .. [1] W. Steven & A.G. Haynes. JISI 183 (1956) p349. - .. [2] C.Y. Kung & J.J. Rayment, MMTA 13A (1982) p328. - .. [3] G. Krauss, Steels: Processing, Structure, and Performance. - Materials Park, OH: ASM International (2008). (Ch 5, p64) - ''' - Ms = 561.0 - 474.0 * C - 33.0 * Mn - 17.0 * Cr - 17.0 * Ni - 21.0 * Mo +\ - (10.0 * Co - 7.5 * Si) - return Ms - - -def martstart_AndrewsLinearMod(C=0.0, Mn=0.0, Cr=0.0, Ni=0.0, - Mo=0.0, Co=0.0, Si=0.0): - ''' predicts the martensite start temperature from composition - - Parameters - ---------- - C : float - wt% C - Mn : float - wt% Mn - Cr : float - wt% Cr - Ni : float - wt% Ni - Mo : float - wt% Mo - Co : float - wt% Co - Si : float - wt% Si - - Returns - ------- - Ms : float - martensite start temperature in degC - - Notes - ----- - - Implements modification from Ref. _[2] to equation originally from - Ref. _[1]. - - The equation should work reasonably well under the limits of - 0.6 wt%C, 4.9 wt% Mn, 12.2 wt% Cr, 12.5 wt% Ni, and 5.4 wt% Mo, 3.2 wt% Si, - and 9 wt% Co. - - Overestimates martensite start temperature for high chromium alloys - - References - ---------- - .. [1] K.W. Andrews, JISI 203 (1965) p721. - .. [2] C.Y. Kung & J.J. Rayment, MMTA 13A (1982) p328. - .. [3] G. Krauss, Steels: Processing, Structure, and Performance. - Materials Park, OH: ASM International (2008). (Ch 5, p64) - ''' - Ms = 539.0 - 423.0 * C - 30.4 * Mn - 12.1 * Cr - 17.7 * Ni - 7.5 * Mo +\ - (10.0 * Co - 7.5 * Si) - return Ms - -def martfract_HarrisCohen(deltaT): - ''' returns transformed fraction martensite as a function of undercooling - - Parameters - ---------- - deltaT : float - Amount of undercooling in degC - - Notes - ----- - - Developed for steels containing 1.1% C - - References - ---------- - .. [1] G. Krauss, Steels: Processing, Structure, and Performance. - Materials Park, OH: ASM International (2008). (Ch 5, p65) - .. [2] W.H. Harris & M. Cohen. Trans AIME 180 (1949) p447. - - See Also - -------- - cryspy.orrl.martstart_AndrewsLinearMod - cryspy.orrl.martstart_StephenHaynesMod - ''' - f = 1.0 - 6.96 * 1.0E-15 * (455.0 - deltaT)**5.32 - - return f - -# ---------------------------------------------------------------------------- - -def martfract_KoistenenMarburger(deltaT): - ''' returns transformed fraction martensite as a function of undercooling - - Parameters - ---------- - deltaT : float - Amount of undercooling in degC - - Notes - ----- - - Developed for steels containing 0.37-1.1 wt% C - - References - ---------- - .. [1] G. Krauss, Steels: Processing Structure and Performance. - Materials Park, OH: ASM International (2008). (Ch 5, p65) - .. [2] D.P. Koistenen & R.E. Marburger. Acta Metall 7 (1959) p59. - - See Also - -------- - cryspy.orrl.martstart_AndrewsLinearMod - cryspy.orrl.martstart_StephenHaynesMod - ''' - import numpy as np - - f = 1.0 - np.exp(-1.1E-2 * deltaT) - - return f - -# ---------------------------------------------------------------------------- - -def genBainCorrMatrices(): - ''' Bain corr matrices - - **genBainCorrMatrices** returns the rotation matrices for the 12 Bain - correspondence matrices, numbered according to parallelism of the c-axis - of the B phase. - - Parameters - ---------- - None - - Returns - ------- - V : 12 x 1 list of 3 x 3 numpy matrices - - Notes - ----- - Rounding the results of Kitahara_KS and removing redundancies gives the - list of Bain correspondence matrices. - - See also - -------- - genKitahara_NW, genBainCorrMatrices - - References - ---------- - .. [1] Kitahara et al, Acta Mater 54 (2006) p1279. - ''' - import numpy as np - - # Numbered according to parallelism of c-axis of B phase - c = 12 * [0] - - c[ 0] = np.mat([[ 1.0, -1.0, 0.0], - [ 1.0, 1.0, 0.0], - [ 0.0, 0.0, 1.0]]) - - c[ 1] = np.mat([[-1.0, -1.0, 0.0], - [ 1.0, -1.0, 0.0], - [ 0.0, 0.0, 1.0]]) - - c[ 2] = np.mat([[ 1.0, 1.0, 0.0], - [-1.0, 1.0, 0.0], - [ 0.0, 0.0, 1.0]]) - - c[ 3] = np.mat([[-1.0, 1.0, 0.0], - [ 1.0, 1.0, 0.0], - [ 0.0, 0.0, -1.0]]) - - c[ 4] = np.mat([[-1.0, 0.0, 1.0], - [ 1.0, 0.0, 1.0], - [ 0.0, 1.0, 0.0]]) - - c[ 5] = np.mat([[ 1.0, 0.0, -1.0], - [ 1.0, 0.0, 1.0], - [ 0.0, -1.0, 0.0]]) - - c[ 6] = np.mat([[-1.0, 0.0, -1.0], - [-1.0, 0.0, 1.0], - [ 0.0, 1.0, 0.0]]) - - c[ 7] = np.mat([[ 1.0, 0.0, 1.0], - [ 1.0, 0.0, -1.0], - [ 0.0, 1.0, 0.0]]) - - c[ 8] = np.mat([[ 0.0, 1.0, -1.0], - [ 0.0, 1.0, 1.0], - [ 1.0, 0.0, 0.0]]) - - c[ 9] = np.mat([[ 0.0, 1.0, 1.0], - [ 0.0, -1.0, 1.0], - [ 1.0, 0.0, 0.0]]) - - c[10] = np.mat([[ 0.0, -1.0, 1.0], - [ 0.0, 1.0, 1.0], - [-1.0, 0.0, 0.0]]) - - c[11] = np.mat([[ 0.0, -1.0, -1.0], - [ 0.0, 1.0, -1.0], - [ 1.0, 0.0, 0.0]]) - - return c - -# ---------------------------------------------------------------------------- - -def calcPTMT(a0, a, c, delta=1.0): - ''' perform PTMT calculations - - **calcPTMT** performs calculations of the phenomenological theory of - martensitic transformations using inputs of the lattice parameters of the - face-centered cubic (gamma) and body-centered-tetragonal (alpha prime) - phases. - - Parameters - ---------- - a0 : float - lattice parameter of the FCC austenite phase in angstroms. - a : float - lattice parameter for the a and b axes of the BC(C/T) martensite phase - in angstroms. - c : float - lattice parameter for the c axis of the BC(C/T) martensite phase in - angstroms. - - Returns - ------- - ksivals : n x 3 numpy float array - ksi angles for the orientation relationship - - detailtuple : tuple of floats - detailed results of the ptmt calculation. In order, the returns are: - (0) invariantPlaneNormal, (1) displacementVector, - (2) shapeDeformationMatrix, (3) shapeDeformationMagnitude, - (4) complimentaryShearDirection, (5) complimentaryShearMagnitude, - (6) complimentaryShearAngleRadians - - References - ---------- - .. [1] Z. Nishiyama, Martensitic Transformation. New York: Academic Press (1978). - .. [2] J. S. Bowles and J. K. Mackenzie. "The crystallography of martensite - transformations I." Acta Metall 2 (1954). - .. [3] J. K. Mackenzie and J. S. Bowles. "The crystallography of martensite - transformations II." Acta Metall 2 (1954). - .. [4] H. K. D. H. Bhadeshia, Worked Examples in the Geometry of Crystals, - 2nd Ed. Brookfield, VT: The Institute of Metals, 1987. - .. [5] C. M. Wayman, Introduction to the Crystallography of Martensitic - Transformations. New York: Macmillan Company (1964). - - Notes - ----- - Provided for ease of copy-and-paste, due to the unweildy outputs - - #from cryspy import orrl - #ksivals, resultsTuple = orrl.calcPTMT(a0, a, c) - #invariantPlaneNormal = resultsTuple[0] - #displacementVector = resultsTuple[1] - #shapeDeformationMatrix = resultsTuple[2] - #shapeDeformationMagnitude = resultsTuple[3] - #complimentaryShearDirection = resultsTuple[4] - #complimentaryShearMagnitude = resultsTuple[5] - #complimentaryShearAngleRadians = resultsTuple[6] - - #from cryspy import orrl - #import numpy as np - #a0, a, c = orrl.lattParams_RobertsCohen(np.arange(0, 5, 0.5)) - #ksivals = [] - #invariantPlaneNormal = [] - #displacementVector = [] - #shapeDeformationMatrix = [] - #shapeDeformationMagnitude = [] - #complimentaryShearDirection = [] - #complimentaryShearMagnitude = [] - #complimentaryShearAngleRadians = [] - #for i in np.arange(0, np.shape(a0)[0]): - # tmp, resultsTuple = orrl.calcPTMT(a0[i], a[i], c[i]) - # ksivals.append(tmp) - # invariantPlaneNormal.append(resultsTuple[0]) - # displacementVector.append(resultsTuple[1]) - # shapeDeformationMatrix.append(resultsTuple[2]) - # shapeDeformationMagnitude.append(resultsTuple[3]) - # complimentaryShearDirection.append(resultsTuple[4]) - # complimentaryShearMagnitude.append(resultsTuple[5]) - # complimentaryShearAngleRadians.append(resultsTuple[6]) - - Examples - ------- - >>> # Reproduce example from Nishiyama (with lattice parameters from Wayman) - >>> a0 = 3.591 # angstroms, gamma - >>> a = 2.875 # angstroms, alpha prime - >>> c = 2.875 # angstroms, alpha prime - >>> ksivals, resultsTuple = calcPTMT(a0, a, c) - >>> invariantPlaneNormal = resultsTuple[0] - >>> displacementVector = resultsTuple[1] - >>> shapeDeformationMatrix = resultsTuple[2] - >>> shapeDeformationMagnitude = resultsTuple[3] - >>> complimentaryShearDirection = resultsTuple[4] - >>> complimentaryShearMagnitude = resultsTuple[5] - >>> complimentaryShearAngleRadians = resultsTuple[6] - - >>> from cryspy import orrl - >>> import numpy as np - >>> a0, a, c = orrl.lattParams_RobertsCohen(np.arange(0, 5, 0.5)) - >>> ksivals = [] - >>> invariantPlaneNormal = [] - >>> displacementVector = [] - >>> shapeDeformationMatrix = [] - >>> shapeDeformationMagnitude = [] - >>> complimentaryShearDirection = [] - >>> complimentaryShearMagnitude = [] - >>> complimentaryShearAngleRadians = [] - >>> for i in np.arange(0, np.shape(a0)[0]): - >>> tmp, resultsTuple = orrl.calcPTMT(a0, a, c) - >>> ksivals.append(tmp) - >>> invariantPlaneNormal.append(resultsTuple[0]) - >>> displacementVector.append(resultsTuple[1]) - >>> shapeDeformationMatrix.append(resultsTuple[2]) - >>> shapeDeformationMagnitude.append(resultsTuple[3]) - >>> complimentaryShearDirection.append(resultsTuple[4]) - >>> complimentaryShearMagnitude.append(resultsTuple[5]) - >>> complimentaryShearAngleRadians.append(resultsTuple[6]) - ''' - - import numpy as np - from scipy.linalg import lstsq as sp_lstsq - from warnings import warn - - # set up dilatation parameters from inputs - # I added the anisotropic dilatation later, so it is implemented differently. - # The anisotropic case (del1 and del2) follows Otte, Acta Cryst 16 (1963) p8. - # The isotropic case follows the implementation description in Wayman's text. - if np.size(delta) == 1: - del1 = delta - del2 = delta - elif np.size(delta) == 2: - del1 = delta[0] - del2 = delta[1] - -# # In Python 3.6, I'm getting kernel death failures for bad parameter inputs. -# # Putting forth these warnings. -# if a0 <= 0: -# warn('Input parameters outside of acceptable ranges.\n'+ -# '(a0 <= 0)') -# -# if a0 / a <= c / a: -# warn('Input parameters outside of acceptable ranges.\n'+ -# '(a0/a <= c/a)') -# -# tmpval = 4.0 * (a**2 / a0**2)**2 + \ -# (4.0 * (c**2 / a0**2) - 8.0) * (a**2 / a0**2) + \ -# 2.0 - (c**2 / a0**2)**2 -# if tmpval > 0: -# warn('Input parameters outside of acceptable ranges.\n'+ -# '4x^2 + (4y-8)x + 2-y^2) > 0, where x=a^2/a0^2 and y=c^2/a0^2') -# -# if a / a0 < -0.27 * c / a + 1.086: -# warn('Input parameters outside of acceptable ranges.\n'+ -# '(a/a0 < -0.27 * ca + 1.086)') - - ''' - During PTMT calculations, several steps impose limits on the ranges of the - ratios a/a0, c/a, and c/a0. For example, to generate the Bain distortion - matrix, a0 must be greater than zero; in order to be able to calculate an - invariant line, a0/a must be less than or equal to c/a and - 4x^2 + (4y-8)x + 2-y^2) must be less than or equal to zero, where x is a^2/a0^2 - and y is c^2/a0^2; for calculation of the invariant plane normal, both c/a0 and - a/a0 must be greater than zero, and so on. The border between the valid and - invalid ranges is found to follow a/a0 < -0.27 * c/a + 1.086 with an r-squared - value in excess of 0.9999. - ''' - - # Using a0=3.591 and a=c=2.875 does not agree exactly with top of page 358 in - # Nishiyama (values from Wayman). Better with a=c=2.8747428847860585? - # Should we change both a and a0? - # a0 = 3.591 # angstroms, gamma - # a = 2.875 # angstroms, alpha prime - # c = 2.875 # angstroms, alpha prime - - slipPlaneFamilyBCT = [1, 1, 2] - slipDirectionFamilyBCT = [1, 1, -1] - - #%% Prepare inputs - cc = genBainCorrMatrices() - - slipPlaneFamilyBCT = np.mat(slipPlaneFamilyBCT / np.linalg.norm(slipPlaneFamilyBCT)) - slipDirectionFamilyBCT = np.mat(slipDirectionFamilyBCT / np.linalg.norm(slipDirectionFamilyBCT)) - - ## Below not used, but could probably be done... - ## Loop through Bain matrices - # O = [] # empty list to contain variant rotations - # for baindex in range(0, np.shape(cc)[0]): - cm = cc[0]#cc[baindex] - - #%% Create the Bain distortion matrix, bb - # an anisotropic dilatation would be added here, a la Otte, Acta Cryst 1963 - # Wayman discusses this possibility on p122-123. - eta1 = np.sqrt(2.0) * a / a0 - eta3 = c / a0 - - # Override for checking against Bhadeshia's example calculation - #eta1 = 1.136071 - #eta3 = 0.803324 - - # In agreement with p123 Wayman; p358 Nishiyama - - bb = np.mat([[eta1, 0.0, 0.0], - [0.0, eta1, 0.0], - [0.0, 0.0, eta3]]) - # this is bold B in Nishiyama; (fBf) in Wayman - - #%% Calculation of invariant lines. - # The equations below are hand-solved from the system of equations shown - # as Eq. 6 on page 358 of Nishiyama. - # Resulting values agree with Equations 6' in Nishiyama - x1 = -np.sqrt((1.0 - eta1**2) / (eta3**2 - eta1**2)) - x2a = -np.sqrt(1.0 - 2.0 * x1**2) - x2b = -x2a - x3 = -x1 - - xin = [np.mat([x1, x2a, x3]), np.mat([x1, x2b, x3])] # two possibilities - # results are in agreement with x1 and x2 in Wayman; xi1 and xi2 in Nishiyama - - #%% Calculation of invariant normal - # Resulting values agree with Equations 7' in Nishiyama - eta1i = 1.0 / eta1**2 - eta3i = 1.0 / eta3**2 - n1 = np.sqrt((1.0 - eta1i) / (eta3i - eta1i)) - n2a = np.sqrt(1.0 - 2.0 * n1**2) - n2b = -n2a - n3 = n1 - - nin = [np.mat([n1, n2a, n3]), np.mat([n1, n2b, n3])] # two possibilities - # results in agreement with ni1' and ni2' in Nishiyama; n1' and n2' in Wayman - # and the solutions on page 62 of Bhadeshia (with different values of the etas) - - #%% Lines after Bain distortion - # There are four possible combinations of x and n (top of page 359). - # Nishiyama only looks at one as an example in his textbook. - # Resulting value for xui agrees with equation 8, p359 - # Resulting value for pp2 agrees with equation 9, p359 - - # One could choose one of the four combinations this way - # for xindex in np.arange(0,2): - # for nindex in np.arange(0,2): - # I'm choosing not to do this here because we can get the ksi angles from - # just the first index, then populate the set of variants from the ksi angles. - xindex = 0 - nindex = 0 - - xi = xin[xindex] - ni = nin[nindex] - - # Convert BCT slip plane and family into FCC coordinates - slipPlaneFCC = slipPlaneFamilyBCT * cm - slipDirectionFCC = np.linalg.solve(cm, slipDirectionFamilyBCT.T) - - p2 = slipPlaneFCC / np.linalg.norm(slipPlaneFCC) - d2 = slipDirectionFCC / np.linalg.norm(slipPlaneFCC) - xui = xi * bb # x underscore sub i - pp2 = p2 * bb.I - pp2 = pp2 / np.linalg.norm(pp2) # p prime underscore sub 2 - - #%% Invariant line strain - # The numerical absolute values of u and v agree with bottom of page 359. - # The middle value of u is negative in the text but positive here. - # It appears that this is probably an error in the text. - u = np.cross(xi, p2) - v = np.cross(xui, pp2) - - # R1 and R2 seem to be transposed in Nishiyama vs. Wayman - rr1 = np.mat(np.vstack([xi, p2, u]).T) - rr2 = np.mat(np.vstack([xui, pp2, v]).T) - - # iS_0i, Eq 13 in Nishiyama. Agrees with values in Wayman, p126. - yy = rr2.T * bb * rr1 - - # Nishiyama Eq. 15. His result is obtained with ni[0] and xi[0]. - # Agrees with result in Wayman, p126. - zz = np.dot(ni, rr1).A1 - - # Now, to produce the solution, we can use the associative properties of - # the matrices to multiply (n'_i;i) by the matrix containing the rotation - # of beta about x_i (see Nishiyama Eqs 15 and 14, respectively.) - # When we multiply through and collect the necessary terms, we find: - tmp1 = np.array([[zz[1] * yy[1,0] + zz[2] * yy[2,0], - zz[2] * yy[1,0] - zz[1] * yy[2,0]], - [zz[1] * yy[1,1] + zz[2] * yy[2,1], - zz[2] * yy[1,1] - zz[1] * yy[2,1]], - [zz[1] * yy[1,2] + zz[2] * yy[2,2], - zz[2] * yy[1,2] - zz[1] * yy[2,2]]]) - - tmp2 = np.array([[zz[0] - zz[0] * yy[0, 0]], - [zz[1] - zz[0] * yy[0, 1]], # Note that the problem is - [zz[2] - zz[0] * yy[0, 2]]]) # now overspecified! - - - # Now we can solve the (overspecified) system for cosB and sinB - # If our system was not overspecified, we could use - # np.linalg.solve(tmp1, tmp2) - # (as we would in Matlab.) - - # It appears that matlab must convert to a least squares method - # for an overspecified system. - - # The numpy least squares function at the time of this writing has - # a bug that results in a segfault, so we are using the scipy version. - - # Numpy segfault issue documented here: - # https://github.com/numpy/numpy/issues/9891 - if (~np.any(np.isnan(tmp1)) or ~np.any(np.isnan(tmp2))): - tmp3 = sp_lstsq(tmp1, tmp2)[0] - cosB = tmp3[0] - sinB = tmp3[1] - else: - cosB = 1.0 - sinB = 0.0 - # This result is in agreement with Wayman as well. - - betamx = np.mat([[1.0, 0.0, 0.0], [0.0, cosB, -sinB], [0.0, sinB, cosB]]) - - # Create a dilatation parameter matrix - # See page 122-123 of Wayman text. Here we allow a tetragonal anisotropic - # dilation. - deltamx = np.mat(np.eye(3)) - deltamx[0,0] = del1 - deltamx[1,1] = del1 - deltamx[2,2] = del2 - #deltamx = np.mat([[1.2*dil, 0., 0.], [0., 1.2*dil, 0.], [0., 0., 0.9*dil]]) - - # The results for ss here agree with Eq 17. - # Also sgrees with (fSf) in Wayman p126. - # And agrees with (F S F) in Bhadeshia p63 (with different etas) - ss = deltamx * rr1 * betamx * yy * rr1.T - ## Check results. Should be <= round off error, approx 1E-15 - #print(np.mat(zz) * np.mat(betamx) * np.mat(yy) - np.mat(zz)) - - # Note that ss is also given by (fJf) * bb, where (fJf) is given by - # J = rr1 * betamx * rr2.T - - #%% Calculate the useful stuff - - # Calculate the invariant plane normal - p1 = p2 * np.linalg.inv(ss) - p2 - p1p = p1 / np.linalg.norm(p1) # agrees with Eq. 18 in Nishiyama and p127 Wayman - - # Calculate the displacement vector - d1 = (ss * d2 - d2) / (p1p * d2) # agrees with Eq. 19 Nishiyama and p127 Wayman - - # Magnitude of the shape deformation - m1 = np.linalg.norm(d1) # agrees with Eq. 20 - - # Direction of complimentary shear - y = np.mat(np.cross(p1p, np.mat([1.0, 0.0, 0.0]))).T # [1 0 0] in text p361 - d2 = (y - np.linalg.solve(ss, y)) / (p2 * y) - - # Magnitude of complimentary shear - m2 = np.linalg.norm(d2) - - # Angle of complimentary shear - alpha = np.arctan(0.5 * m2) - - # Shape deformation matrix (Bhadeshia) - delta = m1 * p1p * d1 - s = np.sqrt(m1**2 - delta**2) # FIXME: Refer back to Bhadeshia, where is this variable to be used? - pp = np.eye(3, 3) + m1 * d1 * p1p - - #%% Get orientation relationship between martensite and austenite - # Note that this gives the CORRECT orientation relationship for the c/a and - # a/a0 ratios for K-S. - - # Victoria said: "KSI is similarity-transformed version of J (i.e., - # expressed in martensite basis)" - # This would then be solved as follows: - # gamma = cm.A / np.tile(np.linalg.norm(cm.A, axis=1), [3, 1]).T - # vx = gamma * (a / a0) * aJg * gamma.T # some variant of the OR - # I don't find that this returns the expected result. - - - # Bhadeshia writes the following, but the determinant isnt unity - aJg = cm * ss.I - # However, if we multiply by a/a0, the expected det=1 matrix is returned - # Matrices compare well to the T matrices in Kitahara - - vx = (a / a0) * aJg # some variant of the orientation relationship - #print(np.linalg.det(vx)) # check that determinant is unity - - - # This works for only certain variants. For example, it gets the right - # ksi angles for Kitahara T4 and T24 in the KS paper; however, it does not - # get the expected result for Kitahara T18 in the KS paper. - # Returns expected results for Kitahara T1 and T11 and in NW paper. - #vx = np.matrix([[0.742, -0.667, -0.075], - # [0.650, 0.742, -0.167], # check using Kitahara matrix - # [0.167, 0.075, 0.983]]) # for KS (#T24) - #vx = np.matrix([[0.667, -0.742, 0.075], - # [0.742, 0.650, -0.167], # check using Kitahara matrix - # [0.075, 0.167, 0.983]]) # for KS (#T4) - #vs = np.matrix([[-0.075, -0.742, 0.667], - # [0.167, 0.650, 0.742], # check using Kitahara matrix - # [-0.983, 0.167, 0.075]])# for KS (#T18) - #vx = np.matrix([[0.000, 0.707, -0.707], - # [-0.169, 0.697, 0.697], # check using a Kitahara matrix - # [0.986, 0.120, 0.120]]) # for NW - #vx = np.matrix([[-0.707, 0.000, -0.707], - # [-0.120, -0.986, 0.120], # check using a Kitahara matrix - # [-0.697, 0.169, 0.697]]) # for NW - - # Now, to get the ksi angles, we first need the Bain correspondence for - # the representative variant - bt = np.round(vx) - - # The orientation of the variant matrix matters. NOT transposing gives the - # expected result for the Kitahara KS and NW matrices, so it appears that - # I am using the correct orientation (aus -> mart). - # bt = bt.T - # vx = vx.T - # print(vx) - # FIXME: Eventually this would be much more robust if we were to orient - # all variants against a standard variant and then get the ksi - # angles. - - g1 = vx.ravel()[0, 0:3] - b1 = bt.ravel()[0, 0:3] - g2 = vx.ravel()[0, 3:6] - b2 = bt.ravel()[0, 3:6] - g3 = vx.ravel()[0, 6:9] - b3 = bt.ravel()[0, 6:9] - - # individual ksi angles - tmpksi1 = np.real(np.arccos(np.dot(g1 / np.linalg.norm(g1), - b1.T / np.linalg.norm(b1)))) - tmpksi2 = np.real(np.arccos(np.dot(g2 / np.linalg.norm(g2), - b2.T / np.linalg.norm(b2)))) - ksi1 = np.amin([tmpksi1, tmpksi2]) * 180. / np.pi - ksi2 = np.amax([tmpksi1, tmpksi2]) * 180. / np.pi - ksi3 = np.real(np.arccos(np.dot(g3 / np.linalg.norm(g3), - b3.T / np.linalg.norm(b3)))) * 180. / np.pi - - #print(ksi1, ksi2, ksi3) - # TODO: Generate full set of variant matrices as an output - - #%% Compile results together - ksivals = np.array([ksi1, ksi2, ksi3]) - invariantPlaneNormal = p1p - displacementVector = d1 - shapeDeformationMatrix = pp - shapeDeformationMagnitude = m1 - complimentaryShearDirection = d2 - complimentaryShearMagnitude = m2 - complimentaryShearAngleRadians = alpha - - return ksivals, \ - (invariantPlaneNormal, displacementVector, \ - shapeDeformationMatrix, shapeDeformationMagnitude, \ - complimentaryShearDirection, complimentaryShearMagnitude, \ - complimentaryShearAngleRadians) - -# ---------------------------------------------------------------------------- - -def solvePTMTparams(ksi1, ksi2, ksi3, accuracy='default', - initial_guess=(0.8035, 1.047), - max_iter=1000): - """ - **solvePTMTparams** solves the PTMT using a least squares approach to - return the apparent a/a0 and c/a ratios for a given set of ksi parameters. - - Parameters - ---------- - ksi1 : float - ksi1 value - - ksi2 : float - ksi2 value - - ksi3 : float - ksi3 value - - accuracy : float (optional) - desired accuracy of the result. - - initial_guess : 2-tuple of floats (optional) - Initial guess values. initial_guess[0] should be the a/a0 ratio and - initial_guess[1] should be the c/a ratio. Defaults to (0.8035, 1.047). - - max_iter : int (optional) {1000} - Maximum number of search iterations. - - Returns - ------- - res : object - results of the optimization routine. See scipy.optimize.least_squares - for more outputs. - - res.x = optimized values of [a/a0, c/a] - - res.fun = residual sum of squares of solution - - res.nfev = number of function evalutions performed - - res.success = True if convergence criteria satisfied - - res.status = reason code for algorithm termination - - res.message = string reason for algorithm termination - """ - import numpy as np - import scipy.optimize as spop - - if accuracy=='default': - accuracy = np.spacing(np.float64(1.0)) - - # Initial guesses for a/a0 and c/a ratios - aa0i = initial_guess[0] - cai = initial_guess[1] - - # Set up objective function for minimization - def rmsPTMT(x): - - a0 = 3.6 # this value doesn't actually matter when there is no dilation - a = a0 * x[0] - c = x[1] * a - - try: - tmp, resultsTuple = calcPTMT(a0, a, c) - except: - tmp = 999999999999999999999999. * np.ones(3) - - return np.sum(np.sqrt((ksi1 - tmp[0])**2 + - (ksi2 - tmp[1])**2 + - (ksi3 - tmp[2])**2)) - - res = spop.minimize(rmsPTMT, x0=[aa0i, cai], - method='Nelder-Mead', - options={'maxiter': max_iter, - 'xatol': accuracy, - 'fatol': accuracy}) - - # Also tried the following minimizations, and neither worked as reliably... - - #res = spop.least_squares(rmsPTMT, x0=[aa0i, cai], bounds=[aa0b, cab], - # verbose=1, max_nfev=max_iter, gtol=accuracy, - # xtol=accuracy, ftol=accuracy) - - #out, rss, its, imode, smode = spop.fmin_slsqp(rmsPTMT, x0=[aa0i, cai], - # bounds=[aa0b, cab], - # full_output=True, - # iter=iterations, - # acc=accuracy, - # #epsilon=1.E-12, - # iprint=1) - - return res - -# ---------------------------------------------------------------------------- - -def solvePTMTparams_dil(ksi1, ksi2, ksi3, a0, accuracy='default', - initial_guess=(0.8035, 1.047, 0.996, 1.052), - max_iter=1000): - """ - **solvePTMTparams** solves the PTMT using a least squares approach to - return the apparent a/a0 and c/a ratios for a given set of ksi parameters. - - Parameters - ---------- - ksi1 : float - ksi1 value - - ksi2 : float - ksi2 value - - ksi3 : float - ksi3 value - - accuracy : float (optional) - desired accuracy of the result. - - initial_guess : 2-tuple of floats (optional) - Initial guess values. initial_guess[0] should be the a/a0 ratio and - initial_guess[1] should be the c/a ratio. Defaults to (0.8035, 1.047). - - max_iter : int (optional) {1000} - Maximum number of search iterations. - - Returns - ------- - res : object - results of the optimization routine. See scipy.optimize.least_squares - for more outputs. - - res.x = optimized values of [a/a0, c/a] - - res.fun = residual sum of squares of solution - - res.nfev = number of function evalutions performed - - res.success = True if convergence criteria satisfied - - res.status = reason code for algorithm termination - - res.message = string reason for algorithm termination - """ - import numpy as np - import scipy.optimize as spop - - if accuracy=='default': - accuracy = np.spacing(np.float64(1.0)) - - # Initial guesses for a/a0 and c/a ratios - aa0i = initial_guess[0] - cai = initial_guess[1] - del1i = initial_guess[2] - del2i = initial_guess[3] - - # Set up objective function for minimization - def rmsPTMT(x): - - a = a0 * x[0] - c = x[1] * a - del1 = x[2] - del2 = x[3] - - try: - tmp, resultsTuple = calcPTMT(a0, a, c, delta=[del1, del2]) - except: - tmp = 999999999999999999999999. * np.ones(3) - - # I am applying a simple weighting here because - # experience is suggesting that ksi1 is harder to fit. - return np.sum(np.sqrt(3.0*(ksi1 - tmp[0])**2 + - 2.0*(ksi2 - tmp[1])**2 + - 1.0*(ksi3 - tmp[2])**2)) - - res = spop.minimize(rmsPTMT, x0=[aa0i, cai, del1i, del2i], - method='Nelder-Mead', - options={'maxiter': max_iter, - 'xatol': accuracy, - 'fatol': accuracy}) - - return res +# -*- coding: utf-8 -*- +''' +ovlib.orrl: Orientation relationship characterization +=============================================================================== +''' + +def namedOR(name): + """ Returns ksi values for named orientation relationships + + Parameters + ---------- + name : {'ks', 'nw', 'bain'} + Orientation relationship name + + Returns + ------- + ksi_values : 1x3 numpy array of floats + ksi values in degrees + + Notes + ----- + TODO: Add plane parallel Greninger-Troiano, Kelly, etc. + TODO: Allow 'Kurdjumov-Sachs' as well as 'ks', etc. + """ + import numpy as np + + if isinstance(name, str): + + if name.lower() == 'ks': + + s6 = np.sqrt(6.0) + s3 = np.sqrt(3.0) + ksi1 = np.arccos((s6 + 1.0) / (2.0 * s3)) + ksi2 = np.arccos((s6 + 18.0) / (12.0 * s3)) + ksi3 = np.arccos((s6 + 12.0) / (6.0 * s6)) + ksi = np.array([ksi1, ksi2, ksi3]) + del s6, s3, ksi1, ksi2, ksi3 + + elif name.lower() == 'nw': + + s6 = np.sqrt(6) + s2 = np.sqrt(2) + ksi0 = np.arccos((s2 + 1.0) / s6) + ksi = np.array([0.0, ksi0, ksi0]) + + elif name.lower() == 'bain': + + ksi = np.array([0.0, 0.0, 0.0]) + + else: + + print ('namedOR: Unrecognized named OR') + + else: + + print ('namedOR requires a string input. Returning Bain.') + ksi = np.array([0.0, 0.0, 0.0]) + + return ksi * 180.0/np.pi + +def getRepresentativeVariant(ksi_values): + """ Returns quaternion of representative variant + + Parameters + ---------- + ksi_values : + + Returns + ------- + psiq : quat class + """ + import numpy as np + from cryspy.rot import quat + + if isinstance(ksi_values, str): + orrl = namedOR(ksi_values) + else: + # Convert OR specification into radians + orrl = ksi_values * np.pi / 180.0 + + csum = np.cos(orrl[0])+np.cos(orrl[1])+np.cos(orrl[2]) + x0 = 0.5 * np.sqrt(csum + 1.0) + x = 0.5 * np.sqrt(2. * np.cos(orrl) - csum + 1.0) + + ksiq = quat(x0, x[0], x[1], x[2]) + + # Calculate quaternion for normalised Bain correspondence. + + qw = 0.5 * np.sqrt(2.0 + np.sqrt(2.0)) + qz = (2.0 / np.sqrt(2.0)) / (4.0 * qw) + gammaq = quat(qw, 0.0, 0.0, qz); + + psiq = ksiq * gammaq + + return psiq + +def generateVariants(ksi_values): + """ Generate variants from Kurdjumov-Sachs angles + + Returns matrices of an orientation relationship specified in Kurjumov-Sachs + angles. + + Parameters + ---------- + ksi_values : length 3 iterable OR {'KS', 'NW', 'Bain'} + + Returns + ------- + vv : rmat object + rotation matrices corresponding to variants + + """ + import numpy as np + from cryspy.rot import rmat + from cryspy.util import vecarraynorm, uniquerows, sigdec + + if isinstance(ksi_values, str): + + ksi = namedOR(ksi_values) + + # convert ksi radians to rotation matrices + + mb = np.zeros([2, 9]) + + mb[0, 0] = np.cos(ksi[0]) + mb[0, 4] = np.cos(ksi[1]) + mb[0, 8] = np.cos(ksi[2]) + + costh = 0.5 * (np.sum(np.cos(ksi)) - 1.0) # sum(cos(ksi)) is the matrix trace + mosth = 1.0 - costh + sinth = np.sqrt(1.0 - costh**2.0) + + + r1 = np.sqrt((mb[0, 0] - costh) / mosth) + r2 = np.sqrt((mb[0, 4] - costh) / mosth) + r3 = np.sqrt((mb[0, 8] - costh) / mosth) + del costh + + r1r2 = r1 * r2 * mosth + r1r3 = r1 * r3 * mosth + r2r3 = r2 * r3 * mosth + r3st = r3 * sinth + r2st = r2 * sinth + r1st = r1 * sinth + del r1, r2, r3, mosth, sinth + + mb[0, 5] = r2r3 - r1st + mb[0, 7] = r2r3 + r1st + mb[1, :] = mb[0, :] + + mb[0, 1] = -r1r2 + r3st + mb[0, 2] = -r1r3 - r2st + mb[0, 3] = -r1r2 - r3st + mb[0, 6] = -r1r3 + r2st + del r1r2, r1r3, r2r3, r3st, r2st, r1st + + mb[1, 1] = -mb[0, 1] + mb[1, 2] = -mb[0, 2] + mb[1, 3] = -mb[0, 3] + mb[1, 6] = -mb[0, 6] + # mb[0] is the 'positive' solution; mb[1] is the 'negative' solution + + # create Bain correspondence matrices + bb = np.zeros([12, 9]) + bb[ 0, :] = [ 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0] + bb[ 1, :] = [ 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0] + bb[ 2, :] = [-1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] + bb[ 3, :] = [ 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 0.0] + bb[ 4, :] = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0] + bb[ 5, :] = [ 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0] + bb[ 6, :] = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0] + bb[ 7, :] = [-1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0] + bb[ 8, :] = [ 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 0.0] + bb[ 9, :] = [ 1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0] + bb[10, :] = [ 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 0.0] + bb[11, :] = [-1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, -1.0] + + # normalize correspondence matrices + bb = rmat.from_array(bb / vecarraynorm(bb)) + mb = rmat.from_array(mb) + + # produce variants + vv = np.zeros([24, 9]) + tmp = mb[0] * bb + vv[np.arange(0, 24, 2), :] = tmp.to_array() + tmp = mb[1] * bb + vv[np.arange(1, 24, 2), :] = tmp.to_array() + + # reduce redundancies, if they exist (as they do, for example, in NW) + vv, ia, ic = uniquerows(sigdec(vv, 7)) + del ia, ic + + return rmat.from_array(vv) + + + +#------------------------------------------------------------------------------ + +def _bg_ksi1(bincenters): + """ returns ksi1 background + + Parameters + ---------- + bincenters : numpy array + centers of bins for ksi1 background + + Returns + ------- + bg : numpy array + background number fractions for ksi1 + + Notes + ----- + Background is determined by cubic spline interpolation from a large + numerical simulation containing 17156424 ksi values measured from + random orientations. + + """ + import numpy as np + import scipy.interpolate as interp + + d = np.array([ [ 0.00000000e+00, 0.00000000e+00], + [ 1.50000000e-01, 8.04400000e+03], + [ 4.50000000e-01, 2.45720000e+04], + [ 7.50000000e-01, 4.06200000e+04], + [ 1.05000000e+00, 5.61680000e+04], + [ 1.35000000e+00, 7.13330000e+04], + [ 1.65000000e+00, 8.80860000e+04], + [ 1.95000000e+00, 1.02050000e+05], + [ 2.25000000e+00, 1.17010000e+05], + [ 2.55000000e+00, 1.31760000e+05], + [ 2.85000000e+00, 1.47720000e+05], + [ 3.15000000e+00, 1.60180000e+05], + [ 3.45000000e+00, 1.75440000e+05], + [ 3.75000000e+00, 1.88830000e+05], + [ 4.05000000e+00, 2.03510000e+05], + [ 4.35000000e+00, 2.16520000e+05], + [ 4.65000000e+00, 2.29790000e+05], + [ 4.95000000e+00, 2.43770000e+05], + [ 5.25000000e+00, 2.56080000e+05], + [ 5.55000000e+00, 2.68910000e+05], + [ 5.85000000e+00, 2.81760000e+05], + [ 6.15000000e+00, 2.93690000e+05], + [ 6.45000000e+00, 3.06460000e+05], + [ 6.75000000e+00, 3.16310000e+05], + [ 7.05000000e+00, 3.27680000e+05], + [ 7.35000000e+00, 3.40790000e+05], + [ 7.65000000e+00, 3.50150000e+05], + [ 7.95000000e+00, 3.63730000e+05], + [ 8.25000000e+00, 3.74700000e+05], + [ 8.55000000e+00, 3.83250000e+05], + [ 8.85000000e+00, 3.93360000e+05], + [ 9.15000000e+00, 4.01330000e+05], + [ 9.45000000e+00, 4.08800000e+05], + [ 9.75000000e+00, 4.10170000e+05], + [ 1.00500000e+01, 4.16810000e+05], + [ 1.03500000e+01, 4.22100000e+05], + [ 1.06500000e+01, 4.24130000e+05], + [ 1.09500000e+01, 4.25640000e+05], + [ 1.12500000e+01, 4.26720000e+05], + [ 1.15500000e+01, 4.26860000e+05], + [ 1.18500000e+01, 4.25850000e+05], + [ 1.21500000e+01, 4.25010000e+05], + [ 1.24500000e+01, 4.19670000e+05], + [ 1.27500000e+01, 4.14030000e+05], + [ 1.30500000e+01, 4.06510000e+05], + [ 1.33500000e+01, 3.97900000e+05], + [ 1.36500000e+01, 3.85480000e+05], + [ 1.39500000e+01, 3.72070000e+05], + [ 1.42500000e+01, 3.55970000e+05], + [ 1.45500000e+01, 3.36740000e+05], + [ 1.48500000e+01, 3.11690000e+05], + [ 1.51500000e+01, 2.79820000e+05], + [ 1.54500000e+01, 2.57130000e+05], + [ 1.57500000e+01, 2.35370000e+05], + [ 1.60500000e+01, 2.18230000e+05], + [ 1.63500000e+01, 2.01140000e+05], + [ 1.66500000e+01, 1.84260000e+05], + [ 1.69500000e+01, 1.68320000e+05], + [ 1.72500000e+01, 1.54590000e+05], + [ 1.75500000e+01, 1.39760000e+05], + [ 1.78500000e+01, 1.27400000e+05], + [ 1.81500000e+01, 1.14020000e+05], + [ 1.84500000e+01, 1.03180000e+05], + [ 1.87500000e+01, 9.14750000e+04], + [ 1.90500000e+01, 8.00980000e+04], + [ 1.93500000e+01, 7.07470000e+04], + [ 1.96500000e+01, 6.10580000e+04], + [ 1.99500000e+01, 5.08750000e+04], + [ 2.02500000e+01, 4.25510000e+04], + [ 2.05500000e+01, 3.39610000e+04], + [ 2.08500000e+01, 2.52270000e+04], + [ 2.11500000e+01, 1.73040000e+04], + [ 2.14500000e+01, 1.12930000e+04], + [ 2.17500000e+01, 7.17200000e+03], + [ 2.20500000e+01, 3.76000000e+03], + [ 2.23500000e+01, 1.55200000e+03], + [ 2.26500000e+01, 3.94000000e+02], + [ 2.29500000e+01, 8.00000000e+00], + [ 2.32500000e+01, 0.00000000e+00], + [ 2.35500000e+01, 0.00000000e+00], + [ 2.38500000e+01, 0.00000000e+00], + [ 2.41500000e+01, 0.00000000e+00], + [ 2.44500000e+01, 0.00000000e+00], + [ 2.47500000e+01, 0.00000000e+00], + [ 2.50500000e+01, 0.00000000e+00], + [ 2.53500000e+01, 0.00000000e+00], + [ 2.56500000e+01, 0.00000000e+00], + [ 2.59500000e+01, 0.00000000e+00], + [ 2.62500000e+01, 0.00000000e+00], + [ 2.65500000e+01, 0.00000000e+00], + [ 2.68500000e+01, 0.00000000e+00], + [ 2.71500000e+01, 0.00000000e+00], + [ 2.74500000e+01, 0.00000000e+00], + [ 2.77500000e+01, 0.00000000e+00], + [ 2.80500000e+01, 0.00000000e+00], + [ 2.83500000e+01, 0.00000000e+00], + [ 2.86500000e+01, 0.00000000e+00], + [ 2.89500000e+01, 0.00000000e+00], + [ 2.92500000e+01, 0.00000000e+00], + [ 2.95500000e+01, 0.00000000e+00], + [ 2.98500000e+01, 0.00000000e+00], + [ 3.01500000e+01, 0.00000000e+00], + [ 3.04500000e+01, 0.00000000e+00], + [ 3.07500000e+01, 0.00000000e+00], + [ 3.10500000e+01, 0.00000000e+00], + [ 3.13500000e+01, 0.00000000e+00], + [ 3.16500000e+01, 0.00000000e+00], + [ 3.19500000e+01, 0.00000000e+00], + [ 3.22500000e+01, 0.00000000e+00], + [ 3.25500000e+01, 0.00000000e+00], + [ 3.28500000e+01, 0.00000000e+00], + [ 3.31500000e+01, 0.00000000e+00], + [ 3.34500000e+01, 0.00000000e+00], + [ 3.37500000e+01, 0.00000000e+00], + [ 3.40500000e+01, 0.00000000e+00], + [ 3.43500000e+01, 0.00000000e+00], + [ 3.46500000e+01, 0.00000000e+00]]) + + minval = np.sqrt(np.spacing(np.float32(1))) + cj = interp.UnivariateSpline(d[:,0], + d[:,1]+minval, + k=1, s=0.5) + + # Interpolate the spline for our bin array into a background for our histogram + bg = cj(bincenters) + bg[bg < minval] = minval # do not allow division by zero + + return bg + +def _bg_ksi2(bincenters): + """ returns ksi1 background + + Parameters + ---------- + bincenters : numpy array + centers of bins for ksi1 background + + Returns + ------- + bg : numpy array + background number fractions for ksi1 + + Notes + ----- + Background is determined by cubic spline interpolation from a large + numerical simulation containing 17156424 ksi values measured from + random orientations. The bin spacing in this histogram is 0.3 degrees. + + """ + import numpy as np + import scipy.interpolate as interp + + d = np.array([ [ 0.00000000e+00, 0.00000000e+00], + [ 1.50000000e-01, 3.80000000e+01], + [ 4.50000000e-01, 2.53000000e+02], + [ 7.50000000e-01, 6.37000000e+02], + [ 1.05000000e+00, 1.30400000e+03], + [ 1.35000000e+00, 2.14800000e+03], + [ 1.65000000e+00, 3.15500000e+03], + [ 1.95000000e+00, 4.51300000e+03], + [ 2.25000000e+00, 6.03800000e+03], + [ 2.55000000e+00, 7.55500000e+03], + [ 2.85000000e+00, 9.49000000e+03], + [ 3.15000000e+00, 1.15950000e+04], + [ 3.45000000e+00, 1.37350000e+04], + [ 3.75000000e+00, 1.64950000e+04], + [ 4.05000000e+00, 1.88700000e+04], + [ 4.35000000e+00, 2.18850000e+04], + [ 4.65000000e+00, 2.54230000e+04], + [ 4.95000000e+00, 2.88190000e+04], + [ 5.25000000e+00, 3.21320000e+04], + [ 5.55000000e+00, 3.60380000e+04], + [ 5.85000000e+00, 3.99840000e+04], + [ 6.15000000e+00, 4.36950000e+04], + [ 6.45000000e+00, 4.81920000e+04], + [ 6.75000000e+00, 5.32350000e+04], + [ 7.05000000e+00, 5.78030000e+04], + [ 7.35000000e+00, 6.26690000e+04], + [ 7.65000000e+00, 6.77040000e+04], + [ 7.95000000e+00, 7.24380000e+04], + [ 8.25000000e+00, 7.90320000e+04], + [ 8.55000000e+00, 8.46900000e+04], + [ 8.85000000e+00, 9.05220000e+04], + [ 9.15000000e+00, 9.71920000e+04], + [ 9.45000000e+00, 1.03820000e+05], + [ 9.75000000e+00, 1.10760000e+05], + [ 1.00500000e+01, 1.17170000e+05], + [ 1.03500000e+01, 1.23890000e+05], + [ 1.06500000e+01, 1.31260000e+05], + [ 1.09500000e+01, 1.38800000e+05], + [ 1.12500000e+01, 1.47670000e+05], + [ 1.15500000e+01, 1.54370000e+05], + [ 1.18500000e+01, 1.62540000e+05], + [ 1.21500000e+01, 1.71450000e+05], + [ 1.24500000e+01, 1.80180000e+05], + [ 1.27500000e+01, 1.88610000e+05], + [ 1.30500000e+01, 1.96360000e+05], + [ 1.33500000e+01, 2.06070000e+05], + [ 1.36500000e+01, 2.15760000e+05], + [ 1.39500000e+01, 2.25380000e+05], + [ 1.42500000e+01, 2.34630000e+05], + [ 1.45500000e+01, 2.45320000e+05], + [ 1.48500000e+01, 2.56620000e+05], + [ 1.51500000e+01, 2.62000000e+05], + [ 1.54500000e+01, 2.64870000e+05], + [ 1.57500000e+01, 2.69010000e+05], + [ 1.60500000e+01, 2.71560000e+05], + [ 1.63500000e+01, 2.74630000e+05], + [ 1.66500000e+01, 2.78040000e+05], + [ 1.69500000e+01, 2.80570000e+05], + [ 1.72500000e+01, 2.82790000e+05], + [ 1.75500000e+01, 2.85960000e+05], + [ 1.78500000e+01, 2.88360000e+05], + [ 1.81500000e+01, 2.89220000e+05], + [ 1.84500000e+01, 2.92980000e+05], + [ 1.87500000e+01, 2.93430000e+05], + [ 1.90500000e+01, 2.96900000e+05], + [ 1.93500000e+01, 2.97160000e+05], + [ 1.96500000e+01, 2.98050000e+05], + [ 1.99500000e+01, 3.00280000e+05], + [ 2.02500000e+01, 3.02770000e+05], + [ 2.05500000e+01, 3.01800000e+05], + [ 2.08500000e+01, 3.04190000e+05], + [ 2.11500000e+01, 3.05480000e+05], + [ 2.14500000e+01, 3.02770000e+05], + [ 2.17500000e+01, 3.01660000e+05], + [ 2.20500000e+01, 2.96840000e+05], + [ 2.23500000e+01, 2.91940000e+05], + [ 2.26500000e+01, 2.87030000e+05], + [ 2.29500000e+01, 2.79490000e+05], + [ 2.32500000e+01, 2.74410000e+05], + [ 2.35500000e+01, 2.68910000e+05], + [ 2.38500000e+01, 2.60700000e+05], + [ 2.41500000e+01, 2.54640000e+05], + [ 2.44500000e+01, 2.47580000e+05], + [ 2.47500000e+01, 2.42160000e+05], + [ 2.50500000e+01, 2.36140000e+05], + [ 2.53500000e+01, 2.29720000e+05], + [ 2.56500000e+01, 2.26630000e+05], + [ 2.59500000e+01, 2.17650000e+05], + [ 2.62500000e+01, 2.12590000e+05], + [ 2.65500000e+01, 2.06500000e+05], + [ 2.68500000e+01, 2.01920000e+05], + [ 2.71500000e+01, 1.95560000e+05], + [ 2.74500000e+01, 1.88290000e+05], + [ 2.77500000e+01, 1.84200000e+05], + [ 2.80500000e+01, 1.78390000e+05], + [ 2.83500000e+01, 1.72670000e+05], + [ 2.86500000e+01, 1.66430000e+05], + [ 2.89500000e+01, 1.61550000e+05], + [ 2.92500000e+01, 1.55230000e+05], + [ 2.95500000e+01, 1.50620000e+05], + [ 2.98500000e+01, 1.44690000e+05], + [ 3.01500000e+01, 1.00740000e+05], + [ 3.04500000e+01, 6.55290000e+04], + [ 3.07500000e+01, 4.09580000e+04], + [ 3.10500000e+01, 2.05860000e+04], + [ 3.13500000e+01, 3.76200000e+03], + [ 3.16500000e+01, 0.00000000e+00], + [ 3.19500000e+01, 0.00000000e+00], + [ 3.22500000e+01, 0.00000000e+00], + [ 3.25500000e+01, 0.00000000e+00], + [ 3.28500000e+01, 0.00000000e+00], + [ 3.31500000e+01, 0.00000000e+00], + [ 3.34500000e+01, 0.00000000e+00], + [ 3.37500000e+01, 0.00000000e+00], + [ 3.40500000e+01, 0.00000000e+00], + [ 3.43500000e+01, 0.00000000e+00], + [ 3.46500000e+01, 0.00000000e+00]]) + + minval = np.sqrt(np.spacing(np.float32(1))) + cj = interp.UnivariateSpline(d[:,0], + d[:,1]+minval, + k=1, s=0.5) + + # Interpolate the spline for our bin array into a background for our histogram + bg = cj(bincenters) + bg[bg < minval] = minval # do not allow division by zero + + return bg + +def _bg_ksi3(bincenters): + """ returns ksi1 background + + Parameters + ---------- + bincenters : numpy array + centers of bins for ksi1 background + + Returns + ------- + bg : numpy array + background number fractions for ksi1 + + Notes + ----- + Background is determined by cubic spline interpolation from a large + numerical simulation containing 17156424 ksi values measured from + random orientations. The bin spacing in this histogram is 0.3 degrees. + + """ + import numpy as np + import scipy.interpolate as interp + + d = np.array([ [ 0.00000000e+00, 0.00000000e+00], + [ 1.50000000e-01, 2.02400000e+03], + [ 4.50000000e-01, 6.40000000e+03], + [ 7.50000000e-01, 1.04980000e+04], + [ 1.05000000e+00, 1.44530000e+04], + [ 1.35000000e+00, 1.87920000e+04], + [ 1.65000000e+00, 2.27030000e+04], + [ 1.95000000e+00, 2.67350000e+04], + [ 2.25000000e+00, 3.07190000e+04], + [ 2.55000000e+00, 3.50120000e+04], + [ 2.85000000e+00, 3.91140000e+04], + [ 3.15000000e+00, 4.28890000e+04], + [ 3.45000000e+00, 4.71630000e+04], + [ 3.75000000e+00, 5.13210000e+04], + [ 4.05000000e+00, 5.43970000e+04], + [ 4.35000000e+00, 5.88480000e+04], + [ 4.65000000e+00, 6.29620000e+04], + [ 4.95000000e+00, 6.75770000e+04], + [ 5.25000000e+00, 7.14270000e+04], + [ 5.55000000e+00, 7.46270000e+04], + [ 5.85000000e+00, 7.91680000e+04], + [ 6.15000000e+00, 8.24090000e+04], + [ 6.45000000e+00, 8.65320000e+04], + [ 6.75000000e+00, 9.06090000e+04], + [ 7.05000000e+00, 9.47120000e+04], + [ 7.35000000e+00, 9.79920000e+04], + [ 7.65000000e+00, 1.01590000e+05], + [ 7.95000000e+00, 1.05900000e+05], + [ 8.25000000e+00, 1.09020000e+05], + [ 8.55000000e+00, 1.12940000e+05], + [ 8.85000000e+00, 1.17490000e+05], + [ 9.15000000e+00, 1.20630000e+05], + [ 9.45000000e+00, 1.23850000e+05], + [ 9.75000000e+00, 1.27110000e+05], + [ 1.00500000e+01, 1.33170000e+05], + [ 1.03500000e+01, 1.35750000e+05], + [ 1.06500000e+01, 1.38000000e+05], + [ 1.09500000e+01, 1.42120000e+05], + [ 1.12500000e+01, 1.45870000e+05], + [ 1.15500000e+01, 1.49250000e+05], + [ 1.18500000e+01, 1.52450000e+05], + [ 1.21500000e+01, 1.56420000e+05], + [ 1.24500000e+01, 1.59460000e+05], + [ 1.27500000e+01, 1.63300000e+05], + [ 1.30500000e+01, 1.65540000e+05], + [ 1.33500000e+01, 1.68700000e+05], + [ 1.36500000e+01, 1.71120000e+05], + [ 1.39500000e+01, 1.75060000e+05], + [ 1.42500000e+01, 1.78070000e+05], + [ 1.45500000e+01, 1.80540000e+05], + [ 1.48500000e+01, 1.83390000e+05], + [ 1.51500000e+01, 1.85630000e+05], + [ 1.54500000e+01, 1.89110000e+05], + [ 1.57500000e+01, 1.91660000e+05], + [ 1.60500000e+01, 1.95150000e+05], + [ 1.63500000e+01, 1.97860000e+05], + [ 1.66500000e+01, 1.99910000e+05], + [ 1.69500000e+01, 2.03460000e+05], + [ 1.72500000e+01, 2.05680000e+05], + [ 1.75500000e+01, 2.08830000e+05], + [ 1.78500000e+01, 2.10760000e+05], + [ 1.81500000e+01, 2.12770000e+05], + [ 1.84500000e+01, 2.14450000e+05], + [ 1.87500000e+01, 2.17540000e+05], + [ 1.90500000e+01, 2.19990000e+05], + [ 1.93500000e+01, 2.22920000e+05], + [ 1.96500000e+01, 2.24490000e+05], + [ 1.99500000e+01, 2.25390000e+05], + [ 2.02500000e+01, 2.27330000e+05], + [ 2.05500000e+01, 2.29780000e+05], + [ 2.08500000e+01, 2.31760000e+05], + [ 2.11500000e+01, 2.34130000e+05], + [ 2.14500000e+01, 2.34930000e+05], + [ 2.17500000e+01, 2.36170000e+05], + [ 2.20500000e+01, 2.39060000e+05], + [ 2.23500000e+01, 2.39200000e+05], + [ 2.26500000e+01, 2.40280000e+05], + [ 2.29500000e+01, 2.41970000e+05], + [ 2.32500000e+01, 2.42410000e+05], + [ 2.35500000e+01, 2.44470000e+05], + [ 2.38500000e+01, 2.44630000e+05], + [ 2.41500000e+01, 2.45470000e+05], + [ 2.44500000e+01, 2.46940000e+05], + [ 2.47500000e+01, 2.46660000e+05], + [ 2.50500000e+01, 2.48020000e+05], + [ 2.53500000e+01, 2.49590000e+05], + [ 2.56500000e+01, 2.50060000e+05], + [ 2.59500000e+01, 2.49730000e+05], + [ 2.62500000e+01, 2.49300000e+05], + [ 2.65500000e+01, 2.49770000e+05], + [ 2.68500000e+01, 2.51230000e+05], + [ 2.71500000e+01, 2.50670000e+05], + [ 2.74500000e+01, 2.50110000e+05], + [ 2.77500000e+01, 2.53140000e+05], + [ 2.80500000e+01, 2.50100000e+05], + [ 2.83500000e+01, 2.49790000e+05], + [ 2.86500000e+01, 2.49210000e+05], + [ 2.89500000e+01, 2.48700000e+05], + [ 2.92500000e+01, 2.47540000e+05], + [ 2.95500000e+01, 2.47380000e+05], + [ 2.98500000e+01, 2.47910000e+05], + [ 3.01500000e+01, 1.88290000e+05], + [ 3.04500000e+01, 1.38640000e+05], + [ 3.07500000e+01, 1.07030000e+05], + [ 3.10500000e+01, 8.20110000e+04], + [ 3.13500000e+01, 5.92320000e+04], + [ 3.16500000e+01, 4.13020000e+04], + [ 3.19500000e+01, 2.75660000e+04], + [ 3.22500000e+01, 1.75590000e+04], + [ 3.25500000e+01, 9.84800000e+03], + [ 3.28500000e+01, 4.43200000e+03], + [ 3.31500000e+01, 1.59800000e+03], + [ 3.34500000e+01, 1.18000000e+02], + [ 3.37500000e+01, 0.00000000e+00], + [ 3.40500000e+01, 0.00000000e+00], + [ 3.43500000e+01, 0.00000000e+00], + [ 3.46500000e+01, 0.00000000e+00]]) + + minval = np.sqrt(np.spacing(np.float32(1))) + cj = interp.UnivariateSpline(d[:,0], + d[:,1]+minval, + k=1, s=0.5) + + # Interpolate the spline for our bin array into a background for our histogram + bg = cj(bincenters) + bg[bg < minval] = minval # do not allow division by zero + + return bg + +def ksi1hist(ksi1values, binwidth=0.5, return_everything=False): + """ histogram ksi1 data + + Parameters + ---------- + ksi1values : numpy array of floats + ksi1values, in degrees + + binwidth : numpy float (optional) + width of bins, in degrees + + return_everything : bool (optional) + flag for whether + + Returns + ------- + fcorr : numpy array + background-corrected ksi1 number fractions + + bm : numpy array + mean values for the histogram bins + + ba : (optional, with return_everything flag) numpy array + histogram bin edges + + fraw : (optional, with return_everything flag) numpy array + ksi1 number fractions before background correction + """ + import numpy as np + + # Histogram ksi data + ba = np.r_[0:35:binwidth] + nb = ba.size + bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) + fraw, tmp = np.histogram(ksi1values, ba, density=True) + fraw = fraw / np.sum(fraw) + + bg = _bg_ksi1(bm) + + # Correct the histogram for measurement + fcorr = fraw / bg + fcorr = fcorr / np.sum(fcorr) + + if return_everything == False: + return fcorr, bm + else: + return fcorr, bm, ba, fraw + +def ksi2hist(ksi2values, binwidth=0.5, return_everything=False): + """ histogram ksi2 data + + Parameters + ---------- + ksi1values : numpy array of floats + ksi1values, in degrees + + binwidth : numpy float (optional) + width of bins, in degrees + + return_everything : bool (optional) + flag for whether + + Returns + ------- + fcorr : numpy array + background-corrected ksi1 number fractions + + bm : numpy array + mean values for the histogram bins + + ba : (optional, with return_everything flag) numpy array + histogram bin edges + + fraw : (optional, with return_everything flag) numpy array + ksi1 number fractions before background correction + """ + import numpy as np + + # Histogram ksi data + ba = np.r_[0:35:binwidth] + nb = ba.size + bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) + fraw, tmp = np.histogram(ksi2values, ba, density=True) + fraw = fraw / np.sum(fraw) + + bg = _bg_ksi2(bm) + + # Correct the histogram for measurement + fcorr = fraw / bg + fcorr = fcorr / np.sum(fcorr) + + if return_everything == False: + return fcorr, bm + else: + return fcorr, bm, ba, fraw + +def ksi3hist(ksi3values, binwidth=0.5, return_everything=False): + """ histogram ksi3 data + + Parameters + ---------- + ksi3values : numpy array of floats + ksi1values, in degrees + + binwidth : numpy float (optional) + width of bins, in degrees + + return_everything : bool (optional) + flag for whether + + Returns + ------- + fcorr : numpy array + background-corrected ksi1 number fractions + + bm : numpy array + mean values for the histogram bins + + ba : (optional, with return_everything flag) numpy array + histogram bin edges + + fraw : (optional, with return_everything flag) numpy array + ksi1 number fractions before background correction + """ + import numpy as np + + # Histogram ksi data + ba = np.r_[0:35:binwidth] + nb = ba.size + bm = 0.5 * (ba[0:nb-1] + ba[1:nb]) + fraw, tmp = np.histogram(ksi3values, ba, density=True) + fraw = fraw / np.sum(fraw) + + bg = _bg_ksi3(bm) + + # Correct the histogram for measurement + fcorr = fraw / bg + fcorr = fcorr / np.sum(fcorr) + + if return_everything == False: + return fcorr, bm + else: + return fcorr, bm, ba, fraw + +def fit_foldnorm(f, bm, return_info=False, accuracy=None, iterations=1000): + """ fit ksi histogram to folded normal distribution + + Parameters + ---------- + fcorr : co + + Returns + ------- + mu : float + folded normal fit location parameter + sig : float + folded normal fit shape parameter + rss : float + final residual sum of squares difference between the fit and + the histogram + info : tuple + (1) the number of iterations required for fitting + (2) the exit mode from the optimizer (see notes) + (3) message describing exit mode + + Notes + ----- + Minimization is performed by scipy.optimize.fmin_slsqp. Refer to + the documentation for this function for further details on exit + mode returns. + """ + import numpy as np + from scipy.special import erf + import scipy.optimize as spop + + if accuracy == None: # default to floating point accuracy + accuracy = np.spacing(np.float32(0)) + + # Estimate the distribution fitting parameters and then minimize the RMS + # Note that RMS error is linearly related to log likelihood, so we should get + # comparable results in both minimizations + m1 = np.dot(bm, f) # first moment + m2 = np.dot(bm**2.0, f) # second moment + m4 = np.dot(bm**4.0, f) # fourth moment + s = np.dot((bm-m1)**2.0, f) # standard deviation + x = np.arange(0.0, np.amax(bm)+np.amax(bm)/1000.0, np.amax(bm)/1000.0) + + # Elandt finds that the 1st and 2nd moments give a better approximation + # when m1/s is greater than about 1.35, and the 2nd and 4th moment + # method is better otherwise. + if m1/s > 1.35: # estimate parameters by 1st and 2nd moments + a = m1**2.0 / m2 # ratio of moments + def i0(x): # Eq 2 [2] + return 1.0 - 0.5 * (1.0 + erf(x / np.sqrt(2.0))) + def g(th, i0, a): + return (np.sqrt(2.0/np.pi) * np.exp(-0.5 * th**2.0) - \ + th * (1.0 - 2.0 * i0(-th)))**2.0 / (a * (1.0 + th**2.0)) - 1.0 + + # Find the value of G closest to zero, limiting iterations + # (fminbnd is not sufficiently robust in our case because G + # sometimes is close to zero but does not necessarily intersect the + # x axis in the limit of the half normal distribution). Assuming + # the function will only come close to zero once, then we will just + # search for the the solution to the equation iteratively + lb = 0.0 + ub = np.amax(x) + res = np.inf + nit = 0 + j = 0 + while (res > accuracy and + nit < 1000 and + (ub - lb) / 1000.0 > accuracy): + + # set a search range + qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) + + tmp = np.absolute(g(qtm, i0, a)) + resn = np.amin(tmp) # get value closest to zero in range + j = np.argmin(tmp) + + ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound + lb = qtm[np.amax([j - 1, 0])] # get new lower bound + nit += 1 # increment number of iterations counter + th=qtm[j] + del qtm, j, ub, lb, res, g, a, i0, nit, tmp + + else: # Estimate Parameters by second and fourth moments + + b = m4 / m2**2.0 # ratio of moments + + def h(q, b): # Eq. 22 [2] + return b * (1.0 + q**2.0)**2.0 - (3.0 + 6.0 * q**2.0 + q**4.0) + + # Find the value of h closest to zero, limiting iterations. + # Same method as above. + lb = 0.0 + ub = np.amax(x) + res = np.inf + nit = 0 + j = 0 + while (res > accuracy and + nit < 1000 and + np.absolute(ub-lb) / 1000.0 > accuracy): + + # set a search range + qtm = np.arange(lb, ub+(ub-lb)/1000.0, (ub-lb)/1000.0) + + tmp = np.absolute(h(qtm, b)) + resn = np.amin(tmp) # get value closest to zero in range + j = np.argmin(tmp) + if np.isreal(resn): + res = resn # in case min cannot be found + ub = qtm[np.amin([j + 1, qtm.size - 1])] # get new upper bound + lb = qtm[np.amax([j - 1, 0])] # get new lower bound + nit += 1 # increment number of iterations counter + + th = qtm[j] + del qtm, j, ub, lb, res, h, b, nit, tmp + del m2, m4 + + # Compute initial guess values + sgi = np.sqrt((s**2.0 + m1**2.0) / (1.0 + th**2.0)) + mui = th * sgi + + # Define minimization function as a least squares problem + def ff(params): + import numpy as np + import scipy.stats as stats + fx = stats.foldnorm.pdf(bm, params[0], loc=0, scale=params[1]) + fx = fx/np.sum(fx) + return np.sum((f-fx)**2.0) + + # Perform constrained minimization + mub = (accuracy, np.inf) + sgb = (accuracy, np.inf) + out, rss, its, imode, smode = spop.fmin_slsqp(ff, x0=[mui, sgi], + bounds=[mub, sgb], + full_output=True, + iter=iterations, + acc=accuracy, + iprint=1) + + mu = out[0] + sig = out[1] + + if return_info==False: + return mu, sig + else: + return mu, sig, rss, (its, imode, smode) + +def foldnorm_mode(mu, sig, accuracy=None): + """ mode/peak of the folded normal distribution + + Parameters + ---------- + mu : float + location parameter + sig : float + shape parameter + + Returns + ------- + expected_value : float + E(Y) + + Notes + ----- + Equation comes from the documentation for the folded normal distribution in + the R statistics package. + http://hosho.ees.hokudai.ac.jp/~kubo/Rdoc/library/VGAM/html/fnormal1.html + """ + import scipy.stats as stats + import numpy as np + import scipy.optimize as spop + + if accuracy == None: # default to floating point accuracy + accuracy = np.spacing(np.float32(0)) + + fn = stats.foldnorm(mu, loc=0, scale=sig) + def chk(x): + val = -fn.pdf(x) + return val + + res = spop.minimize(chk, 0.0, method='nelder-mead', + options={'xtol': 1e-8, 'disp': False}) + + return res.x[0] + + +def fit_ksivals(ksi1vals, ksi2vals, ksi3vals, binwidth=0.5, makeplot=True, \ + accuracy=None): + + import numpy as np + import scipy.stats as stats + import matplotlib.pyplot as plt + + f1c, bm, ba, f1r = ksi1hist(ksi1vals, + binwidth, return_everything=True) + + f2c, bm, ba, f2r = ksi2hist(ksi2vals, + binwidth, return_everything=True) + + f3c, bm, ba, f3r = ksi3hist(ksi3vals, + binwidth, return_everything=True) + + mu1, sig1, rss1, dat1 = fit_foldnorm(f1c, bm, return_info=True, + accuracy=accuracy) + + mu2, sig2, rss2, dat2 = fit_foldnorm(f2c, bm, return_info=True, + accuracy=accuracy) + + mu3, sig3, rss3, dat3 = fit_foldnorm(f3c, bm, return_info=True, + accuracy=accuracy) + + print (foldnorm_mode(mu1, sig1), foldnorm_mode(mu2, sig2), foldnorm_mode(mu3, sig3)) # FIXME: make output clearer to the user + + if makeplot==False: + + return np.array([[mu1, sig1],[mu2, sig2],[mu3, sig3]]) + + else: + + # parameters we might want to adjust + xlimv = [0.0, 15.0] + ylimv = [0.0, 15.0] + xtickstep = 3 + ytickstep = 3 + bgdistclr = [0.8,0.8,0.8] + uncorrclr = [0.55, 0.55, 0.55] + ksi1faceclr = [1.0, 1.0, 1.0] + ksi2faceclr = [1.0, 1.0, 1.0] + ksi3faceclr = [1.0, 1.0, 1.0] + fontsz=12 + + xv = np.arange(0.0, np.amax(bm) + np.amax(bm) / 1000.0, + np.amax(bm) / 1000.0) + + # Create an awesome plot + fig = plt.figure(figsize=(15, 4), dpi=96) + fig.patch.set_facecolor('w') + + # ksi1 subplot + ax1 = fig.add_subplot(131) + f = stats.foldnorm.pdf(xv, mu1, scale=sig1) + f = xv.size*f/np.sum(f) + ax1.plot(xv, f, '-k', linewidth=1) + ax1.bar(bm-binwidth/2.0, f1r*bm.size, width=binwidth, + facecolor=uncorrclr, edgecolor=uncorrclr) + ax1.bar(bm-binwidth/2.0, f1c*bm.size, width=binwidth, + facecolor=ksi1faceclr, edgecolor=[0.0, 0.0, 0.0]) + bg = _bg_ksi1(xv) + bg = bg/np.sum(bg) + ax1.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) + ax1.text(0.97, 0.97, + r'$Mo_{\xi1}$ = '+foldnorm_mode(mu1, sig1).astype('|S5') + +r'$^\circ$'+'\n'+ + r'$\mu_{\xi1}$ = '+mu1.astype('|S5')+r'$^\circ$'+'\n'+ + r'$\sigma_{\xi1}$ = '+sig1.astype('|S5')+r'$^\circ$', + fontsize=fontsz, + bbox=dict(edgecolor='k', facecolor='w'), + transform = ax1.transAxes, + horizontalalignment='right', + verticalalignment='top') + ax1.set_xlim(xlimv) + ax1.set_ylim(ylimv) + ax1.set_xticks(np.arange(xlimv[0],xlimv[1]+xtickstep, xtickstep)) + ax1.set_yticks(np.arange(ylimv[0],ylimv[1]+ytickstep, ytickstep)) + + # ksi2 subplot + ax2 = fig.add_subplot(132) + f = stats.foldnorm.pdf(xv, mu2, scale=sig2) + f = xv.size*f/np.sum(f) + ax2.plot(xv, f, '-k', linewidth=1) + ax2.bar(bm-binwidth/2.0, f2r*bm.size, width=binwidth, + facecolor=uncorrclr, edgecolor=uncorrclr) + ax2.bar(bm-binwidth/2.0, f2c*bm.size, width=binwidth, + facecolor=ksi2faceclr, edgecolor=[0.0, 0.0, 0.0]) + bg = _bg_ksi2(xv) + bg = bg/np.sum(bg) + ax2.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) + ax2.text(0.03, 0.97, + r'$Mo_{\xi2}$ = '+foldnorm_mode(mu2, sig2).astype('|S5') + +r'$^\circ$'+'\n'+ + r'$\mu_{\xi1}$ = '+mu2.astype('|S5')+r'$^\circ$'+'\n'+ + r'$\sigma_{\xi1}$ = '+sig2.astype('|S5')+r'$^\circ$', + fontsize=fontsz, + bbox=dict(edgecolor='k', facecolor='w'), + transform = ax2.transAxes, + horizontalalignment='left', + verticalalignment='top') + ax2.set_xlim(xlimv) + ax2.set_ylim(ylimv) + ax2.set_xticks(np.arange(xlimv[0],xlimv[1]+xtickstep, xtickstep)) + ax2.set_yticks(np.arange(ylimv[0],ylimv[1]+ytickstep, ytickstep)) + + + # ksi2 subplot + ax3 = fig.add_subplot(133) + f = stats.foldnorm.pdf(xv, mu3, scale=sig3) + f = xv.size*f/np.sum(f) + ax3.plot(xv, f, '-k', linewidth=1) + ax3.bar(bm-binwidth/2.0, f3r*bm.size, width=binwidth, + facecolor=uncorrclr, edgecolor=uncorrclr) + ax3.bar(bm-binwidth/2.0, f3c*bm.size, width=binwidth, + facecolor=ksi3faceclr, edgecolor=[0.0, 0.0, 0.0]) + bg = _bg_ksi3(xv) + bg = bg/np.sum(bg) + ax3.fill_between(x=xv, y1=bg*bg.size, y2=0, facecolors=bgdistclr, edgecolors=bgdistclr) + ax3.text(0.03, 0.97, + r'$Mo_{\xi3}$ = '+foldnorm_mode(mu3, sig3).astype('|S5') + +r'$^\circ$'+'\n'+ + r'$\mu_{\xi1}$ = '+mu3.astype('|S5')+r'$^\circ$'+'\n'+ + r'$\sigma_{\xi1}$ = '+sig3.astype('|S5')+r'$^\circ$', + fontsize=fontsz, + bbox=dict(edgecolor='k', facecolor='w'), + transform = ax3.transAxes, + horizontalalignment='left', + verticalalignment='top') + ax3.set_xlim(xlimv) + ax3.set_ylim(ylimv) + ax3.set_xticks(np.arange(xlimv[0],xlimv[1]+xtickstep, xtickstep)) + ax3.set_yticks(np.arange(ylimv[0],ylimv[1]+ytickstep, ytickstep)) + + + ax1.set_ylabel(r'Relative Number Fraction') + ax1.set_xlabel(r'$\xi_1$ ($^\circ$)', fontsize=fontsz*1.2) + ax2.set_xlabel(r'$\xi_2$ ($^\circ$)', fontsize=fontsz*1.2) + ax3.set_xlabel(r'$\xi_3$ ($^\circ$)', fontsize=fontsz*1.2) + fig.subplots_adjust(bottom=0.15) + plt.show() + + return np.array([[mu1, sig1],[mu2, sig2],[mu3, sig3]]), \ + fig, ax1, ax2, ax3 \ No newline at end of file diff --git a/cryspy/rot.py b/cryspy/rot.py index 6538f71..219e80e 100755 --- a/cryspy/rot.py +++ b/cryspy/rot.py @@ -5,11 +5,11 @@ ''' class angax(object): - """ + """ Angle/axis rotation representation. - + ... - + Attributes ---------- th : list, tuple, or numpy array of floats @@ -20,24 +20,24 @@ class angax(object): the axis y coordinate z : list, tuple, or numpy array of floats the axis z coordinate - + Notes ----- Axes are normalized during initialization """ - - def __init__(self, th=0, x=1, y=0, z=0): + + def __init__(self,th=1,x=0,y=0,z=0): import numpy as np # note that in the above initialization, the default values will be # be given for the remaining parameters if insufficient values are # passed to the function. The best way to avoid this problem is to # always type a=..., b=... etc. - + # check that the shapes of all are the same - if np.shape(th)==np.shape(x)==np.shape(y)==np.shape(z): - + if np.shape(th)==np.shape(x)==np.shape(y)==np.shape(z): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... th = np.asarray(th) x = np.asarray(x) y = np.asarray(y) @@ -51,33 +51,33 @@ def __init__(self, th=0, x=1, y=0, z=0): self.y = y * nrm self.z = z * nrm self.size = np.size(self.x) - + else: print("angax construction error: check that the lengths of th, x,"\ - " y, and z are all the same.") #TODO: check into using warnings package + " y, and z are all the same.") return None #------------------------------------------------------------------------------- def numel(self): """ returns angax size - + Gives the number of elements in the angle/axis rotation object - + Parameters ---------- None - + Returns ------- n : int the number of elements in the rotation matrix object - + Examples -------- >>> import numpy as np >>> import cryspy.rot as rot - >>> my_angax = rot.angax(th=[np.pi/7.0, np.pi], + >>> my_angax = rot.angax(th=[np.pi/7.0, np.pi], ... x=[1, 2], y=[1, 1], z=[1, 5]) >>> numel = my_angax.numel() """ @@ -87,15 +87,15 @@ def numel(self): def to_array(self): ''' converts object to array - + Converts angax object to an n x 4 numpy array where the first column corresponds to the angle and the final three columns correspond to the axis. - + Parameters ---------- None - + Returns ------- a : angax object @@ -106,20 +106,20 @@ def to_array(self): >>> import cryspy.rot as rot >>> my_angax = rot.angax(th=0.785, x=1.0, y=1.0, z=0.0) >>> my_array = my_angax.to_array() - + See Also -------- np.array : numpy array ''' import numpy as np return np.vstack([self.x, self.y, self.z]).T - + #------------------------------------------------------------------------------- - + @classmethod def from_array(cls, arg): ''' create angax object from numpy array - + Converts an n x 4 numpy array where the first column corresponds to angles (in radians) and the latter three columns correspond to the axes into an angax object. @@ -128,19 +128,19 @@ def from_array(cls, arg): ---------- arg : n x 4 numpy array array to be converted into angax object - + Returns ------- a : angax object object containing the angle/axis representation of the rotation - + Examples -------- >>> import numpy as np >>> import cryspy.rot as rot >>> my_array = [[np.pi, 1.0, 0.0, 0.0], [np.pi/4.0, 0.0, 1.0, 3.0]] - >>> my_angax = rot.angax.from_array( my_array ) - + >>> my_angax = rot.angax.from_array( my_array ) + See Also -------- np.array : numpy array @@ -154,25 +154,25 @@ def from_array(cls, arg): arg = arg.T return cls(arg[:,0], arg[:,1], arg[:,2], arg[:,3]) else: - print('input is not an n x 4 numpy array') # TODO: check into using warnings + print('input is not an n x 4 numpy array') #------------------------------------------------------------------------------- - + @classmethod def from_bunge(cls, arg): ''' convert bunge to angax - + Converts Bunge Euler angle object to angle/axis representation. - + Parameters ---------- arg : Bunge object containing Euler angles - + Returns ------- a : angax object object containing the angle/axis representation of the rotation - + Examples -------- >>> import cryspy.rot as rot @@ -181,7 +181,7 @@ def from_bunge(cls, arg): ... PHI =[np.pi/2.0, np.pi/8.0], ... phi2=[np.pi/4.0, np.pi/6.0]) >>> my_angax = rot.angax.from_bunge( my_euler ) - + See Also -------- bunge : Bunge Euler angle rotation object @@ -189,17 +189,17 @@ def from_bunge(cls, arg): return angax.from_quat(quat.from_bunge(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_quat(cls, arg): ''' convert quat to angax - + Converts quaternions to angle/axis representation. - + Parameters ---------- arg : quaternion object - + Returns ------- a : angax object @@ -214,61 +214,61 @@ def from_quat(cls, arg): ... c=[0.0, 1.0], ... d=[0.0, 0.0]) >>> my_angax = rot.angax.from_quat( my_quat ) - + See Also -------- rodri : Rodrigues vector object ''' import numpy as np - + aa=arg.a aa[aa>1.0]=1.0 - + an = 2.0 * np.arccos(aa) - - # angle/axis is not well-defined when angle approaches zero - loc = an >= np.sqrt(np.spacing(1)) - sqi = np.zeros(np.size(an)) + + # angle/axis is not well-defined when angle approaches zero + loc = an >= np.sqrt(np.spacing(1)) + sqi = np.zeros(np.size(an)) sqi[loc]= 1.0 / np.sin(0.5 * an[loc]) - + # assign axes xx = arg.b * sqi yy = arg.c * sqi zz = arg.d * sqi - + # do not allow rotations greater than pi loc = an > np.pi an[loc] = 2.0 * np.pi - an[loc] xx[loc] = -xx[loc] yy[loc] = -yy[loc] zz[loc] = -zz[loc] - + # when the angle is near zero, force the axis to be 1 0 0 fix = an < np.sqrt(np.spacing(1)) an[fix] = 0.0 xx[fix] = 1.0 yy[fix] = 0.0 zz[fix] = 0.0 - + # when the angle is very close to pi, make it pi fix = np.absolute(np.pi - an) < np.sqrt(np.spacing(1)) an[fix] = np.pi - + return cls(th=an,x=xx,y=yy,z=zz) #------------------------------------------------------------------------------- - + @classmethod def from_rodri(cls, arg): ''' convert rodri to angax - + Converts Rodrigues vectors to angle/axis representation - + Parameters ---------- arg : rodri object object containing Rodrigues vectors, see :rodri: - + Returns ------- a : angax object @@ -278,8 +278,8 @@ def from_rodri(cls, arg): -------- >>> import cryspy.rot as rot >>> my_rodri = rot.rodri() - >>> my_angax = rot.angax.from_rodri( my_rodri ) - + >>> my_angax = rot.angax.from_rodri( my_rodri ) + See Also -------- rodri : Rodrigues vector object @@ -287,69 +287,69 @@ def from_rodri(cls, arg): import numpy as np nrm=np.array([arg.r0, arg.r1, arg.r2, arg.r3]) nrm=1.0 / np.sqrt(np.sum(nrm**2.0, axis=0)) - - an=2.0 * np.arctan(nrm) + + an=2.0 * np.arctan(nrm) xx=arg.r1 * nrm yy=arg.r2 * nrm zz=arg.r3 * nrm - + return cls(th=an, x=xx, y=yy, z=zz) #------------------------------------------------------------------------------- - + @classmethod def from_rmat(cls, arg): ''' converts rotation matrices to angle/axis representation - + Parameters ---------- arg : rmat object rotation matrix - + Returns ------- angax : angax object - angle/axis rotation representation - + angle/axis rotation representation + See Also -------- rmat - + Notes ----- - * assumes active rotation matrix - + * assumes active rotation matrix + * adapted from Peter Kovesi's matlab code, which itself follows the - implementation suggested by Hartley & Zissermann. - + implementation suggested by Hartley & Zissermann. + Original header from Kovesi's code follows:: MATRIX2ANGLEAXIS - Homogeneous matrix to angle-axis description - + Usage: t = matrix2angleaxis(T) - + Argument: T - 4x4 Homogeneous transformation matrix Returns: t - 3-vector giving rotation axis with magnitude equal to the rotation angle in radians. - + See also: ANGLEAXIS2MATRIX, ANGLEAXIS2MATRIX2, ANGLEAXISROTATE, NEWANGLEAXIS, NORMALISEANGLEAXIS - + Copyright (c) 2008 Peter Kovesi School of Computer Science & Software Engineering The University of Western Australia pk at csse uwa edu au http://www.csse.uwa.edu.au/ - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without + files (the "Software"), to deal in the Software without restriction, subject to the following conditions: - - The above copyright notice and this permission notice shall be + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + The Software is provided "as is", without warranty of any kind. ''' # Following the implementation suggested by Hartley and Zisserman: @@ -359,25 +359,25 @@ def from_rmat(cls, arg): import numpy.linalg as npla n = np.size(arg.g11) v = np.complex128(np.zeros([n,9])) - d = np.zeros([n,3]) - + d = np.zeros([n,3]) + for i in range(0, n): dtmp,vtmp = npla.eig(np.array( [[arg.g11[i]-1.0, arg.g12[i], arg.g13[i]], [arg.g21[i], arg.g22[i]-1.0, arg.g23[i]], [arg.g31[i], arg.g32[i], arg.g33[i]-1.0]] )) - + v[i] = np.array([vtmp[0,0], vtmp[0,1], vtmp[0,2], vtmp[1,0], vtmp[1,1], vtmp[1,2], vtmp[2,0], vtmp[2,1], vtmp[2,2]]) - + d[i] = np.absolute(np.array([dtmp[0], dtmp[1], dtmp[2]])) - - + + # find the index of the smallest eigenvalue ind=np.argsort(d.T,axis=0).T - + # extract the appropriate eigenvectors xx=np.zeros(n) yy=np.zeros(n) @@ -389,42 +389,41 @@ def from_rmat(cls, arg): yy[i]=v[i,loc+3] zz[i]=v[i,loc+6] chk[i]=d[i,loc] # record the min eigenvalue - + # DEBUG - # TODO: check into using warnings package if any(chk > 0.001): print('At least one rotation matrix is dubious.\n') print('Location(s):\n') print(np.where(chk > 0.001)) - + chk=(xx**2.0 + yy**2.0 + zz**2.0)**0.5 - 1.0 if any(chk > 0.0001): print('At least one rotation matrix results in a non-unit axis\n') print('Location(s)\n') print(np.where(chk > 0.0001)) - + # determine rotation angle tcq = arg.g11 + arg.g22 + arg.g33 - 1.0 tsq = xx * (arg.g32 - arg.g23) + yy * (arg.g13 - arg.g31) + zz * (arg.g21 - arg.g12) an = np.arctan2(tsq, tcq) - + # fix negative thetas rev = an < 0.0 an[rev] = -an[rev] xx[rev] = -xx[rev] yy[rev] = -yy[rev] zz[rev] = -zz[rev] - + # put in the range of 0 to pi loc=np.absolute(an)>np.pi an[loc] = 2.0 * np.pi - an[loc] xx[loc] = -xx[loc] yy[loc] = -yy[loc] zz[loc] = -zz[loc] + + return angax(th=an, x=xx, y=yy, z=zz) - return angax(th=an, x=xx, y=yy, z=zz) - -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- def __repr__(self): import numpy as np @@ -433,49 +432,45 @@ def __repr__(self): '{0: >8s} {1:^6s} {2:^6s} {3:^6s} \n'.format('theta', 'x', 'y', 'z') fmtstr = '{0: >8.3f}\xb0 @ [{1: >6.3f} {2: >6.3f} {3: >6.3f}]\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.th[i] * 180.0/np.pi, - self.x[i], - self.y[i], + repstr += fmtstr.format(self.th[i] * 180.0/np.pi, + self.x[i], + self.y[i], self.z[i]) for i in np.arange(0, 3): repstr += ' {0: ^7s} {0: ^6s} {0: ^6s} {0: ^6s} \n'.format( '.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.th[i] * 180.0 / np.pi, - self.x[i], - self.y[i], + repstr += fmtstr.format(self.th[i] * 180.0/np.pi, + self.x[i], + self.y[i], self.z[i]) else: - - th = np.atleast_1d(self.th.squeeze()) - x = np.atleast_1d(self.x.squeeze()) - y = np.atleast_1d(self.y.squeeze()) - z = np.atleast_1d(self.z.squeeze()) + for i in np.arange(0, self.size): - repstr += fmtstr.format(th[i] * 180.0 / np.pi, - x[i], - y[i], - z[i]) - + repstr += fmtstr.format(self.th[i] * 180.0/np.pi, + self.x[i], + self.y[i], + self.z[i]) + repstr += '\n' return repstr #------------------------------------------------------------------------------- def __getitem__(self,index): - + an = self.th[index] xx = self.x[index] yy = self.y[index] zz = self.z[index] - + return angax(th=an, x=xx, y=yy, z=zz) #------------------------------------------------------------------------------- @@ -483,9 +478,9 @@ def __getitem__(self,index): class bunge(object): ''' Bunge Euler angles rotation representation. - + ... - + Attributes ---------- phi1 : list, tuple, or numpy array of floats @@ -506,22 +501,22 @@ def __init__(self, phi1=0,PHI=0,phi2=0): # be given for the remaining parameters if insufficient values are # passed to the function. The best way to avoid this problem is to # always type phi1=..., PHI=... etc. - + # check that the shapes of all are the same - if np.shape(phi1)==np.shape(PHI)==np.shape(phi2): - + if np.shape(phi1)==np.shape(PHI)==np.shape(phi2): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... self.phi1 = util.vecarrayconvert(phi1) self.PHI = util.vecarrayconvert(PHI) self.phi2 = util.vecarrayconvert(phi2) self.size = np.size(self.phi1) - + else: print("bunge construction error: check that the lengths of phi1, "\ - "PHI, and phi2 are all the same.") # TODO: check into using warnings package + "PHI, and phi2 are all the same.") return None - + #------------------------------------------------------------------------------- def numel(self): @@ -537,16 +532,16 @@ def to_array(self): def to_nfft(self): return matthies.from_bunge(self).to_nfft() - + #------------------------------------------------------------------------------- - + @classmethod def from_array(cls, arg): ''' create bunge object from numpy array ''' return cls(arg[:,0], arg[:,1], arg[:,2]) - + #------------------------------------------------------------------------------- @classmethod @@ -554,32 +549,32 @@ def from_angax(cls, arg): return bunge.from_rmat(rmat.from_angax(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_quat(cls, arg): return bunge.from_rmat(rmat.from_quat(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_rodri(cls, arg): return bunge.from_angax(angax.from_rodri(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_matthies(cls, arg): - import righthand as rh + import cryspy.util as util import numpy as np - - ind = ~rh.isnegligible(arg.beta) - - phi1 = arg.alpha + + ind = ~util.isnegligible(arg.beta) + + phi1 = arg.alpha phi1[ind] = phi1[ind] + np.pi / 2.0 - + phi2 = arg.gamma phi2[ind] = phi2[ind] + 3.0 * np.pi / 2.0 - + return bunge(phi1, arg.beta, phi2) #------------------------------------------------------------------------------- @@ -587,22 +582,22 @@ def from_matthies(cls, arg): @classmethod def from_rmat(cls, arg): import numpy as np - + # Make sure the g33 values are in range c2 = np.amax( \ np.vstack([np.amin( \ - np.vstack([arg.g33, + np.vstack([arg.g33, np.tile(1.0, arg.g33.shape[0])]).T,\ - axis=1), + axis=1), np.tile(-1.0, arg.g33.shape[0])]).T, axis=1) - p = np.absolute(c2) > 1.0 - np.sqrt(np.spacing(1.0)) - - s2i = 1.0 / (np.sqrt(1.0 - c2**2.0) + np.spacing(1.0)) + p = np.absolute(c2) > 1.0 - np.sqrt(np.spacing(1)) + + s2i = 1.0 / np.sqrt(1.0 - c2**2.0) c1 = -arg.g32 * s2i s1 = arg.g31 * s2i c3 = arg.g23 * s2i s3 = arg.g13 * s2i - + # fix points near poles c1[p] = arg.g11[p] s1[p] = arg.g12[p] @@ -613,20 +608,20 @@ def from_rmat(cls, arg): p1 = np.arctan2(s1, c1) P0 = np.arccos(c2) p2 = np.arctan2(s3, c3) - + # positive convention p1[p1<0] = p1[p1<0] + 2.0 * np.pi P0[P0<0] = P0[P0<0] + 2.0 * np.pi p2[p2<0] = p2[p2<0] + 2.0 * np.pi - - return cls(phi1=p1, PHI=P0, phi2=p2) + + return cls(phi1=p1, PHI=P0, phi2=p2) #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np - + repstr = '{0: ^29s}\n'.format('Bunge Euler angles (\xb0)') + \ '{0:-^29s}\n'.format('-') + \ @@ -637,25 +632,25 @@ def __repr__(self): n = self.size - 1 if n > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, - self.PHI[i] * 180.0/np.pi, + repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, + self.PHI[i] * 180.0/np.pi, self.phi2[i] * 180.0/np.pi) for i in np.arange(0, 3): repstr += ' {0:^7s}{0:^7s}{0:^7s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, - self.PHI[i] * 180.0/np.pi, + repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, + self.PHI[i] * 180.0/np.pi, self.phi2[i] * 180.0/np.pi) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, - self.PHI[i] * 180.0/np.pi, + repstr += fmtstr.format(self.phi1[i] * 180.0/np.pi, + self.PHI[i] * 180.0/np.pi, self.phi2[i] * 180.0/np.pi) - + repstr += '\n' return repstr @@ -672,9 +667,9 @@ class quat(object): ''' quaternion class Quaternion rotation representation. - + ... - + Attributes ---------- a : scalar, list, tuple, or numpy array of floats @@ -685,53 +680,53 @@ class quat(object): the second value of the vector part of the quaternion d : scalar, list, tuple, or numpy array of floats the third value of the vector part of the quaternion - + Notes ----- - The quaternion parameters (a, b, c, d) are normalized during initialization to produce a unit quaternion. - + - Quaternions cannot be used to represent improper rotations. - - - The convention is employed that convention that the scalar part of the - quaternion cannot take on negative values. The vector part is + + - The convention is employed that convention that the scalar part of the + quaternion cannot take on negative values. The vector part is automatically adjusted accordingly during initialization. - - - Coded with some inspiration from the robotics toolbox quaternion class by + + - Coded with some inspiration from the robotics toolbox quaternion class by Luis Fernando Lara Tobar and Peter Corke (governed by the Mozilla Public - License 1.1) and the mtex toolbox by R. Hielscher and H. Schaeben + License 1.1) and the mtex toolbox by R. Hielscher and H. Schaeben (governed by GPL2), and the matlab and octave functions for computer vision and image processing from Peter Kovesi. - + Examples -------- >>> q = quat() - + >>> q = quat(0.6070,-0.7043,0.0634,-0.3627) - + >>> a = 0.6070,0.9688 >>> b = -0.7043,0.1176 >>> c = 0.0634,0.0263 >>> d = -0.3627,0.2166 >>> q = quat(a, b, c, d) - + >>> a = [0.6070,0.9688] >>> b = [-0.7043,0.1176] >>> c = [0.0634,0.0263] >>> d = [-0.3627,0.2166] >>> q = quat(a, b, c, d) - + >>> import numpy as np >>> a = np.array([0.6070,0.9688]) >>> b = np.array([-0.7043,0.1176]) >>> c = np.array([0.0634,0.0263]) >>> d = np.array([-0.3627,0.2166]) >>> q = quat(a, b, c, d) - + >>> b = bunge(np.pi, np.pi/2, np.pi/7) >>> q = quat.from_bunge(b) ''' - + def __init__(self, a=1, b=0, c=0, d=0): # note that in the above initialization, the default values will be # be given for the remaining parameters if insufficient values are @@ -739,12 +734,12 @@ def __init__(self, a=1, b=0, c=0, d=0): # always type a=..., b=... etc. import numpy as np import cryspy.util as util - + # check that the shapes of all are the same - if np.shape(a)==np.shape(b)==np.shape(c)==np.shape(d): - + if np.shape(a)==np.shape(b)==np.shape(c)==np.shape(d): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... a = util.vecarrayconvert(a) b = util.vecarrayconvert(b) c = util.vecarrayconvert(c) @@ -756,13 +751,13 @@ def __init__(self, a=1, b=0, c=0, d=0): self.a = a * nrm self.b = b * nrm self.c = c * nrm - self.d = d * nrm + self.d = d * nrm self.size = np.size(self.a) - + else: print("quat construction error: check that the lengths of a, b, "\ - "c, and d are all the same.") # TODO: check into using warnings package - return None + "c, and d are all the same.") + return None #------------------------------------------------------------------------------- @@ -780,31 +775,25 @@ def to_array(self): return np.vstack([self.a, self.b, self.c, self.d]).T #------------------------------------------------------------------------------- - + @classmethod def from_array(cls,arg): ''' create quaternion object from numpy array ''' - import numpy as np - arg = np.atleast_2d(arg) - return quat(a=arg[:, 0], b=arg[:, 1], c=arg[:, 2], d=arg[:, 3]) - + return quat(arg[:,0], arg[:,1], arg[:,2], arg[:,3]) + #------------------------------------------------------------------------------- - + @classmethod def from_angax(cls,arg): ''' convert angax to quat - + Conversion of angle/axis rotation representation to quaternion rotation representation - - Parameters - ---------- - angax : angax class object - + Notes - ----- - Follows algorithm from Peter Kovesi (pk at csse uwa edu au), School of + ----- + Follows algorithm from Peter Kovesi (pk at csse uwa edu au), School of Computer Science & Software Engineering, The University of Western Australia, http://www.csse.uwa.edu.au/ ''' @@ -814,44 +803,44 @@ def from_angax(cls,arg): y = -arg.y * an z = -arg.z * an hth = 0.5 * arg.th - sth = np.sin(hth) - - return cls(a=np.cos(hth), b=x*sth, c=y*sth, d=z*sth) + sth = np.sin(hth) + + return cls(a=np.cos(0.5 * hth), b=x*sth, c=y*sth, d=z*sth) #------------------------------------------------------------------------------- - + @classmethod def from_bunge(cls,arg): ''' convert bunge to quat - + Conversion of Bunge Euler angle rotation representation to quaternion rotation representation. - + Notes ----- - - follows an algorithm similar to the one used in mtex; however, + - follows an algorithm similar to the one used in mtex; however, gamma in mtex is phi2-2*pi/2. Here we use phi2+pi/2, which agrees with the Kocks chapter in Texture and Anisotropy. ''' import numpy as np - + ha = 0.5 * (arg.phi1 - np.pi/2) # half alpha hb = 0.5 * arg.PHI # half beta hg = 0.5 * (arg.phi2 + np.pi/2) # half gamma zz = np.zeros(np.size(ha)) # zeros array - + qa = quat(a=np.cos(ha), b=zz, c=zz, d=np.sin(ha)) qb = quat(a=np.cos(hb), b=zz, c=np.sin(hb), d=zz ) qc = quat(a=np.cos(hg), b=zz, c=zz, d=np.sin(hg)) - + return quat.conj(qa * qb * qc) #------------------------------------------------------------------------------- - + @classmethod def from_rodri(cls,arg): ''' convert rodri to quat - + Conversion of Rodrigues vector rotation representation to quaternion rotation representation. ''' @@ -862,9 +851,9 @@ def from_rodri(cls,arg): @classmethod def from_rmat(cls,arg): ''' convert rmat to quat - + Conversion of rotation matrices to quaternion rotation representation. - + Notes ----- - What happens when the rotation matrix describes an improper rotation? @@ -876,125 +865,125 @@ def from_rmat(cls,arg): uu = arg.g11 vv = arg.g22 ww = arg.g33 - + dd = np.zeros([np.size(uu), 4]) dd[:, 0] = np.sqrt(1.0 + uu + vv + ww) dd[:, 1] = np.sqrt(1.0 - uu - vv + ww) dd[:, 2] = np.sqrt(1.0 + uu - vv - ww) dd[:, 3] = np.sqrt(1.0 - uu + vv - ww) - + # maximum values and locations in dd mh = np.amax(dd, axis=1) j = np.argmax(dd, axis=1) - + mh = 0.50 * mh # convenience variable mi = 0.25 / mh # convenience variable - + n = np.size(arg.g11) - + a = np.zeros(n) b = np.copy(a) c = np.copy(a) d = np.copy(a) - + if n>1: - + x = j==0 a[x] = -mh[x] b[x] = ( arg.g23[x] - arg.g32[x]) * mi[x] c[x] = ( arg.g31[x] - arg.g13[x]) * mi[x] d[x] = ( arg.g12[x] - arg.g21[x]) * mi[x] - + x = j==1 d[x] = mh[x] c[x] = ( arg.g23[x] + arg.g32[x]) * mi[x] b[x] = ( arg.g31[x] + arg.g13[x]) * mi[x] a[x] =-( arg.g12[x] - arg.g21[x]) * mi[x] - + x = j==2 b[x] = mh[x] a[x] = (-arg.g23[x] + arg.g32[x]) * mi[x] d[x] = ( arg.g31[x] + arg.g13[x]) * mi[x] c[x] = ( arg.g12[x] + arg.g21[x]) * mi[x] - + x = j==3 c[x] = mh[x] d[x] = ( arg.g23[x] + arg.g32[x]) * mi[x] a[x] = (-arg.g31[x] + arg.g13[x]) * mi[x] b[x] = ( arg.g12[x] + arg.g21[x]) * mi[x] - + loc = a<0.0 a[loc] = -a[loc] b[loc] = -b[loc] c[loc] = -c[loc] d[loc] = -d[loc] - + else: - + if j==0: a = -mh b = ( arg.g23 - arg.g32) * mi c = ( arg.g31 - arg.g13) * mi d = ( arg.g12 - arg.g21) * mi - + if j==1: d = mh c = ( arg.g23 + arg.g32) * mi b = ( arg.g31 + arg.g13) * mi a =-( arg.g12 - arg.g21) * mi - + if j==2: b = mh a = (-arg.g23 + arg.g32) * mi d = ( arg.g31 + arg.g13) * mi c = ( arg.g12 + arg.g21) * mi - + if j==3: c = mh d = ( arg.g23 + arg.g32) * mi a = (-arg.g31 + arg.g13) * mi b = ( arg.g12 + arg.g21) * mi - + if a<0.0: a = -a b = -b c = -c d = -d - + a = np.squeeze(a) b = np.squeeze(b) c = np.squeeze(c) d = np.squeeze(d) - + return quat(a, b, c, d) #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np repstr = '{0: ^31s}\n'.format('quaternions') + \ '{0:-^31s}\n'.format('-') + \ ' {0:^6s} {1:^6s} {2:^6s} {3:^6s} \n'.format('a', 'b', 'c', 'd') fmtstr = '<{0: >6.3f} {1: >6.3f} {2: >6.3f} {3: >6.3f} >\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.a[i], - self.b[i], - self.c[i], + repstr += fmtstr.format(self.a[i], + self.b[i], + self.c[i], self.d[i]) for i in np.arange(0, 3): repstr += ' {0:^6s} {0:^6s} {0:^6s} {0:^6s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.a[i], - self.b[i], - self.c[i], - self.d[i]) + repstr += fmtstr.format(self.a[i], + self.b[i], + self.c[i], + self.d[i]) else: a = np.atleast_1d(self.a.squeeze()) @@ -1003,7 +992,7 @@ def __repr__(self): d = np.atleast_1d(self.d.squeeze()) for i in np.arange(0, self.size): repstr += fmtstr.format(a[i], b[i], c[i], d[i]) - + repstr += '\n' return repstr @@ -1011,38 +1000,38 @@ def __repr__(self): def __mul__(self, q2): ''' product - + Multiplication of quaternions - + Notes ----- q1 and q2 must be the same size or one must be single. ''' if isinstance(q2, quat): - # the following works if q1 and q2 are the same size or one of them + # the following works if q1 and q2 are the same size or one of them # is a single quaternion. It rightfully returns an error otherwise. a = self.a * q2.a - self.b * q2.b - self.c * q2.c - self.d * q2.d b = self.a * q2.b + self.b * q2.a + self.c * q2.d - self.d * q2.c c = self.a * q2.c + self.c * q2.a + self.d * q2.b - self.b * q2.d d = self.a * q2.d + self.d * q2.a + self.b * q2.c - self.c * q2.b - + loc = a < 0.0 a[loc] = -a[loc] b[loc] = -b[loc] c[loc] = -c[loc] d[loc] = -d[loc] - + return quat(a, b, c, d) -#------------------------------------------------------------------------------ - +#------------------------------------------------------------------------------ + def __truediv__(self, q): '''Returns quaternion misorientation.''' - + if isinstance(q, quat): qr = quat() - qr = self * q.inv() + qr = self * q.inv() return qr #------------------------------------------------------------------------------ @@ -1052,50 +1041,50 @@ def inv(self): '''Returns the quaternion inverse.''' qi = copy.copy(self) qi.a = -qi.a; - + return qi - + #------------------------------------------------------------------------------ def conj(self): ''' quaternion conjugate - + Returns the conjugate rotation for the quaternion - + ''' import copy qi = copy.copy(self) qi.b = -qi.b qi.c = -qi.c qi.d = -qi.d - + return qi - + #------------------------------------------------------------------------------ - + def __getitem__(self,index): - + aa = self.a[index] bb = self.b[index] cc = self.c[index] dd = self.d[index] - + return quat(a=aa, b=bb, c=cc, d=dd) #------------------------------------------------------------------------------ def symmetrize(self, s): ''' returns symmetrically equivalent rotations - + Parameters ---------- s : cryspy.xtal.symm object or cryspy.rot.quat object - + Returns ------- list : in which each entry is a symmetrically equivalent version - + Notes ----- Quaternions can only handle proper rotations. The symmetrize method @@ -1116,29 +1105,30 @@ def to_fundzone(self, s, qref=None): symmetry s ''' import numpy as np - - if qref == None: - qref = quat() - - o = qref.inv() * self - s = s.rotations - - # get the number of quaternions and the number of symmetries - #m = s.size - #n = self.size - + + if qref==None: + qref=quat() + + s = s.rotations * qref + + # get the number of quaternions and the number of symmetries + m = s.size + n = self.size + # compute all distances to fundamental regions - - - d = np.absolute(np.dot(s.to_array(), o.to_array().T)) - + w = np.tile(s.a, [n, 1]).T * np.tile(self.a, [m, 1]) + x = np.tile(s.b, [n, 1]).T * np.tile(self.b, [m, 1]) + y = np.tile(s.c, [n, 1]).T * np.tile(self.c, [m, 1]) + z = np.tile(s.d, [n, 1]).T * np.tile(self.d, [m, 1]) + d = np.absolute(w + x + y + z) + # locate and extract the fundamental region for each quaternion loc = np.argmax(d, axis=0) - q2i = s.inv() - + q2f = s[[loc]].conj() + # project to the fundamental region - qf = o * q2i[loc] - + qf = q2f * self + return qf #------------------------------------------------------------------------------ @@ -1146,26 +1136,26 @@ def to_fundzone(self, s, qref=None): @classmethod def rand(cls, n, kappa=0, mu=None): """ random quaternion(s) generated from von Mises-Fisher distribution - + Parameters ---------- n : int number of random quaternions - + kappa : float > 0 clustering parameter - + mu : quat instance location parameter (quaternion central to the distribution) - + Returns ------- qr : quat instance random quaternions - + See Also -------- - util.rand_vonMisesFisherM + util.rand_vonMisesFisherM """ import cryspy.util as util if mu == None: @@ -1173,10 +1163,10 @@ def rand(cls, n, kappa=0, mu=None): elif isinstance(mu, quat): mu = mu.to_array() else: - print('quat.rand: mu must be an instance of the quat class') # TODO: check into using warnings package - - return quat.from_array(util.rand_vonMisesFisherM(n=n, - kappa=kappa, + print('quat.rand: mu must be an instance of the quat class') + + return quat.from_array(util.rand_vonMisesFisherM(n=n, + kappa=kappa, mu=mu)) #------------------------------------------------------------------------------ @@ -1184,12 +1174,12 @@ def rand(cls, n, kappa=0, mu=None): class rodri(object): """ Rodrigues vector class - + Created on Fri Apr 13 16:20:21 2012 - + @author: epayton """ - + def __init__(self, r1=0, r2=0, r3=0): # note that in the above initialization, the default values will be # be given for the remaining parameters if insufficient values are @@ -1197,26 +1187,26 @@ def __init__(self, r1=0, r2=0, r3=0): # always type r1=..., r2=... etc. import numpy as np import cryspy.util as util - + # check that the shapes of all are the same - if np.shape(r1)==np.shape(r2)==np.shape(r3): - + if np.shape(r1)==np.shape(r2)==np.shape(r3): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... r1 = util.vecarrayconvert(r1) r2 = util.vecarrayconvert(r2) r3 = util.vecarrayconvert(r3) self.r1 = r1 self.r2 = r2 - self.r3 = r3 - + self.r3 = r3 + self.size = np.size(r1) - + else: print("rodri construction error: check that the lengths of r1, r2,"\ - " and r3 are all the same.") # TODO: check into using warnings package - return None + " and r3 are all the same.") + return None #------------------------------------------------------------------------------- @@ -1228,9 +1218,9 @@ def numel(self): def to_array(self): import numpy as np return np.vstack([self.r1, self.r2, self.r3]).T - + #------------------------------------------------------------------------------- - + @classmethod def from_angax(cls, arg): import numpy as np @@ -1258,42 +1248,42 @@ def from_rmat(cls, arg): #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np repstr = '{0:^24s}\n'.format('Rodrigues vectors') + \ '{0:-^24s}\n'.format('-') + \ ' {0: ^6s} {1: ^6s} {2: ^6s} \n'.format('r1', 'r2', 'r3') fmtstr = '< {0: >6.3f} {1: >6.3f} {2: >6.3f} >\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.r1[i], - self.r2[i], + repstr += fmtstr.format(self.r1[i], + self.r2[i], self.r3[i]) for i in np.arange(0, 3): repstr += ' {0:^6s} {0:^6s} {0:^6s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.r1[i], - self.r2[i], - self.r3[i]) + repstr += fmtstr.format(self.r1[i], + self.r2[i], + self.r3[i]) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.r1[i], - self.r2[i], + repstr += fmtstr.format(self.r1[i], + self.r2[i], self.r3[i]) - + repstr += '\n' return repstr #------------------------------------------------------------------------------- -class rmat(object): +class rmat(object): ''' rotation matrix class - + object for containing and manipulating rotation matrices ''' @@ -1304,14 +1294,14 @@ def __init__(self, g11=1, g12=0, g13=0, g21=0, g22=1, g23=0, g31=0, g32=0, g33=1 # always type g11=..., g2=... etc. import numpy as np import cryspy.util as util - + # check that the shapes of all are the same if np.shape(g11)==np.shape(g12)==np.shape(g13)==\ np.shape(g21)==np.shape(g22)==np.shape(g23)==\ - np.shape(g31)==np.shape(g32)==np.shape(g33): - + np.shape(g31)==np.shape(g32)==np.shape(g33): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... self.g11 = util.vecarrayconvert(g11) self.g12 = util.vecarrayconvert(g12) self.g13 = util.vecarrayconvert(g13) @@ -1321,30 +1311,30 @@ def __init__(self, g11=1, g12=0, g13=0, g21=0, g22=1, g23=0, g31=0, g32=0, g33=1 self.g31 = util.vecarrayconvert(g31) self.g32 = util.vecarrayconvert(g32) self.g33 = util.vecarrayconvert(g33) - + self.size = np.size(g11) - + else: print("rmat construction error: check that the lengths of g11, g12,"\ - " ... g33 are all the same.") # TODO: check into using warnings package - return None + " ... g33 are all the same.") + return None #------------------------------------------------------------------------------ def numel(self): """ returns rmat size - + Gives the number of elements in the rotation matrix object - + Parameters ---------- None - + Returns ------- n : int the number of elements in the rotation matrix object - + Examples -------- >>> import cryspy.rot as rot @@ -1359,9 +1349,9 @@ def numel(self): def to_array(self): """ convert rmat to array - + Converts a rotation matrix object to an n x 9 numpy array. - + Examples -------- >>> import cryspy.rot as rot @@ -1369,25 +1359,25 @@ def to_array(self): >>> my_array = my_rmat.to_array() """ import numpy as np - return np.vstack([self.g11, self.g12, self.g13, + return np.vstack([self.g11, self.g12, self.g13, self.g21, self.g22, self.g23, self.g31, self.g32, self.g33]).T - + #------------------------------------------------------------------------------ @classmethod def from_array(cls,arg): """ convert array to rmat - + Class method for converting an n x 9 numpy array into an object of the rotation matrix class. - + Parameters ---------- arg : n x 9 numpy array of floats the numpy array from which to create the rmat - + Examples -------- >>> import numpy as np @@ -1400,8 +1390,8 @@ def from_array(cls,arg): s = np.shape(arg) if np.all(np.asarray(s) != 9): - print('error: rotation object requires at least one dimension = 9') # TODO: check into using warnings package - + print('error: rotation object requires at least one dimension = 9') + if np.size(s) == 1: return cls(g11=arg[0], g12=arg[1], g13=arg[2], \ g21=arg[3], g22=arg[4], g23=arg[5], \ @@ -1409,26 +1399,26 @@ def from_array(cls,arg): else: return cls(g11=arg[:,0], g12=arg[:,1], g13=arg[:,2], \ g21=arg[:,3], g22=arg[:,4], g23=arg[:,5], \ - g31=arg[:,6], g32=arg[:,7], g33=arg[:,8]) - + g31=arg[:,6], g32=arg[:,7], g33=arg[:,8]) + #------------------------------------------------------------------------------ @classmethod def from_quat(cls,arg): """ convert quat to rmat - + Convert quaternion object to rotation matrix object - + Examples -------- >>> import cryspy.rot as rot >>> my_quat_obj = rot.quat() >>> my_rmat = rot.rmat.from_quat( my_quat_obj ) """ - + # user wants to convert quaternions to rotation matrices - + w = arg.a x = arg.b y = arg.c @@ -1438,14 +1428,14 @@ def from_quat(cls,arg): x2 = x**2.0 y2 = y**2.0 z2 = z**2.0 - + xy = x*y xz = x*z yz = y*z wx = w*x wy = w*y wz = w*z - + return cls( g11 = w2 + x2 - y2 - z2, g12 = 2.0 * (xy - wz), @@ -1458,20 +1448,20 @@ def from_quat(cls,arg): g33 = w2 - x2 - y2 + z2) #------------------------------------------------------------------------------ - + @classmethod - def from_bunge(cls,arg): + def from_bunge(cls,arg): # user wants to convert Bunge Euler angles to rotation matrices # algorithm from wikipedia - import numpy as np - + import numpy as np + c1 = np.cos(arg.phi1) c2 = np.cos(arg.PHI) c3 = np.cos(arg.phi2) s1 = np.sin(arg.phi1) s2 = np.sin(arg.PHI) s3 = np.sin(arg.phi2) - + # ZXZ combination return cls( g11 = c1 * c3 - s1 * c2 * s3, @@ -1479,32 +1469,32 @@ def from_bunge(cls,arg): g13 = s2 * s1, g22 = -s1 * s3 + c1 * c2 * c3, g21 = c3 * s1 + s3 * c2 * c1, - g23 = -s2 * c1, + g23 = -s2 * c1, g31 = s3 * s2, g32 = c3 * s2, g33 = c2) #------------------------------------------------------------------------------ - + @classmethod def from_angax(cls,arg): """ Create rmat from angax - + Converts an angle/axis to a passive rotation matrix - + Parameters ---------- arg : angax object object containin angle/axis pair - + Returns ------- rmat : rmat object rotation matrix corresponding to angle/axis input - + Notes ----- - This converts an angle/axis rotation to a passive rotation matrix. + This converts an angle/axis rotation to a passive rotation matrix. """ import numpy as np r1 = arg.x @@ -1519,7 +1509,7 @@ def from_angax(cls,arg): c = np.cos(arg.th) s = np.sin(arg.th) cm = 1 - c - + return cls( g11 = r1s * cm + c, g12 = r12 * cm - r3 * s, @@ -1532,35 +1522,35 @@ def from_angax(cls,arg): g33 = r3s * cm + c) #------------------------------------------------------------------------------ - + @classmethod def from_rodri(cls,arg): - return cls.from_angax(angax.from_rodri(arg)) + return cls.from_angax(angax.from_rodri(arg)) #------------------------------------------------------------------------------ def __repr__(self): - + import numpy as np repstr = '{0:^72s}\n'.format('rotation matrices') + \ '{0:-^72s}\n'.format('-') + \ ' {0:^6s} {1:^6s} {2:^6s} '.format('g11', 'g12', 'g13') + \ ' {0:^6s} {1:^6s} {2:^6s} '.format('g21', 'g22', 'g23') + \ ' {0:^6s} {1:^6s} {2:^6s} \n'.format('g31', 'g32', 'g33') - + fmtstr = '[{0: >6.3f}, {1: >6.3f}, {2: >6.3f};' + \ ' {3: >6.3f}, {4: >6.3f}, {5: >6.3f};' + \ ' {6: >6.3f}, {7: >6.3f}, {8: >6.3f}]\n' - + n = self.size - 1 if self.size > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.g11[i], - self.g12[i], - self.g13[i], - self.g21[i], + repstr += fmtstr.format(self.g11[i], + self.g12[i], + self.g13[i], + self.g21[i], self.g22[i], self.g23[i], self.g31[i], @@ -1571,34 +1561,34 @@ def __repr__(self): ' {0:^6s} {0:^6s} {0:^6s} '.format('.') + \ ' {0:^6s} {0:^6s} {0:^6s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.g11[i], - self.g12[i], - self.g13[i], - self.g21[i], + repstr += fmtstr.format(self.g11[i], + self.g12[i], + self.g13[i], + self.g21[i], self.g22[i], self.g23[i], self.g31[i], self.g32[i], - self.g33[i]) + self.g33[i]) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.g11[i], - self.g12[i], - self.g13[i], - self.g21[i], + repstr += fmtstr.format(self.g11[i], + self.g12[i], + self.g13[i], + self.g21[i], self.g22[i], self.g23[i], self.g31[i], self.g32[i], self.g33[i]) - + repstr += '\n' return repstr #------------------------------------------------------------------------------ - + def __getitem__(self, index): gg11 = self.g11[index] gg12 = self.g12[index] @@ -1618,16 +1608,16 @@ def __getitem__(self, index): def __mul__(self, b): """ multiplies rotation matrices - + uses Laderman method (23 steps rather than 27) - + Reference --------- Laderman, JD. "A Noncommutative Algorithm for Multiplying 3×3 Matrices - Using 23 Multiplications." Bull Amer Math Soc 82 (1976) p126. + Using 23 Multiplications." Bull Amer Math Soc 82 (1976) p126. """ if isinstance(b, rmat): - + m1 = ( self.g11 + self.g12 + self.g13 - self.g21 - \ self.g22 - self.g32 - self.g33 ) * b.g22 m2 = ( self.g11 - self.g21 ) * ( -b.g12 + b.g22 ) @@ -1653,7 +1643,7 @@ def __mul__(self, b): m21 = self.g21 * b.g13 m22 = self.g31 * b.g12 m23 = self.g33 * b.g33 - + c1 = m6 + m14 + m19 c2 = m1 + m4 + m5 + m6 + m12 + m14 + m15 c3 = m6 + m7 + m9 + m10 + m14 + m16 + m18 @@ -1663,12 +1653,12 @@ def __mul__(self, b): c7 = m6 + m7 + m8 + m11 + m12 + m13 + m14 c8 = m12 + m13 + m14 + m15 + m22 c9 = m6 + m7 + m8 + m9 + m23 - - return rmat(g11=c1, g12=c2, g13=c3, - g21=c4, g22=c5, g23=c6, + + return rmat(g11=c1, g12=c2, g13=c3, + g21=c4, g22=c5, g23=c6, g31=c7, g32=c8, g33=c9) else: - print('rmat multiplication error') # TODO: check into using warnings package + print('rmat multiplication error') #------------------------------------------------------------------------------ @@ -1676,7 +1666,7 @@ def __truediv__(self, b): """ matrix right division, a/b """ - if isinstance(b, rmat): + if isinstance(b, rmat): return self * b.inv() #------------------------------------------------------------------------------ @@ -1695,12 +1685,12 @@ def inv(self): c6 = badj.g23 * di c7 = badj.g31 * di c8 = badj.g32 * di - c9 = badj.g33 * di + c9 = badj.g33 * di - return rmat(g11=c1, g12=c2, g13=c3, - g21=c4, g22=c5, g23=c6, + return rmat(g11=c1, g12=c2, g13=c3, + g21=c4, g22=c5, g23=c6, g31=c7, g32=c8, g33=c9) - + #------------------------------------------------------------------------------ def det(self): @@ -1715,8 +1705,8 @@ def det(self): self.g13 * self.g22 * self.g31 return d -#------------------------------------------------------------------------------ - +#------------------------------------------------------------------------------ + def adj(self): """ returns the adjugate matrices @@ -1731,9 +1721,9 @@ def adj(self): m8 = -self.g11 * self.g32 + self.g12 * self.g31 m9 = self.g11 * self.g22 - self.g12 * self.g21 - return rmat(g11=m1, g12=m2, g13=m3, - g21=m4, g22=m5, g23=m6, - g31=m7, g32=m8, g33=m9) + return rmat(g11=m1, g12=m2, g13=m3, + g21=m4, g22=m5, g23=m6, + g31=m7, g32=m8, g33=m9) #------------------------------------------------------------------------------ @@ -1755,14 +1745,14 @@ def trace(self): return np.array(self.g11 + self.g22 + self.g33) -#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ class matthies(object): ''' Matthies Euler angles rotation representation. - + ... - + Attributes ---------- alpha : list, tuple, or numpy array of floats @@ -1783,22 +1773,22 @@ def __init__(self, alpha=0, beta=0, gamma=0): # be given for the remaining parameters if insufficient values are # passed to the function. The best way to avoid this problem is to # always type phi1=..., PHI=... etc. - + # check that the shapes of all are the same - if np.shape(alpha)==np.shape(beta)==np.shape(gamma): - + if np.shape(alpha)==np.shape(beta)==np.shape(gamma): + # the following may look ridiculous, but it allows the arguments to - # be passed as np.matrices, np.arrays, tuples, or lists... + # be passed as np.matrices, np.arrays, tuples, or lists... self.alpha = util.vecarrayconvert(alpha) self.beta = util.vecarrayconvert(beta) self.gamma = util.vecarrayconvert(gamma) self.size = np.size(self.alpha) - + else: print("bunge construction error: check that the lengths of phi1, "\ - "PHI, and phi2 are all the same.") # TODO: check into using warnings package + "PHI, and phi2 are all the same.") return None - + #------------------------------------------------------------------------------- def numel(self): @@ -1814,41 +1804,41 @@ def to_array(self): def to_nfft(self): ''' converts Matthies Euler angles into nfft convention - + Note ---- - Algorithm from mtex 4.1.4 ''' import numpy as np - + def fft_rho(rho): # This is the contents of fft_rho.m rho = np.mod(rho + np.pi, 2.0 * np.pi) - np.pi prho = rho / 2.0 / np.pi prho[prho >= 0.5] = -0.5 return np.array(prho) - + def fft_theta(th): # This is the contents of fft_theta.m th = np.mod(th, 2.0 * np.pi) pth = th / 2.0 / np.pi return np.array(pth) - + al_fft = fft_rho(self.alpha) be_fft = fft_rho(self.beta) # TODO: MTEX has an 'nfft_bug' preference flag. It is false in my distribution. Check in to why this is there. ga_fft = fft_rho(self.gamma) - + return 2.0 * np.pi * np.vstack([al_fft, be_fft, ga_fft]).T - + #------------------------------------------------------------------------------- - + @classmethod def from_array(cls,arg): ''' create bunge object from numpy array ''' return cls(arg[:,0], arg[:,1], arg[:,2]) - + #------------------------------------------------------------------------------- @classmethod @@ -1856,11 +1846,11 @@ def from_angax(cls,arg): return matthies.from_rmat(rmat.from_angax(arg)) #------------------------------------------------------------------------------- - + @classmethod def from_quat(cls, arg): ''' - + Example ------- >>> import numpy as np @@ -1868,31 +1858,31 @@ def from_quat(cls, arg): >>> v = np.arange(0.0, 2.0*np.pi, np.pi/7.0) >>> q = rot.quat.from_bunge(v, v, v) >>> m = rot.matthies.from_quat(q) - + Notes ----- - Algorithm from mtex 4.1.4 ''' import numpy as np - import righthand as rh + import cryspy.util as util q = arg.conj() # TODO: This is different from MTEX, since we are using a passive convention. The conjugate is required to get the same results... but is that what we want? at1 = np.arctan2(q.d, q.a) at2 = np.arctan2(q.b, q.c) alpha = at1 - at2 - beta = 2.0 * np.arctan2(np.sqrt(q.b**2 + q.c**2), + beta = 2.0 * np.arctan2(np.sqrt(q.b**2 + q.c**2), np.sqrt(q.a**2 + q.d**2)) gamma = at1 + at2 - - ind = rh.isnegligible(beta, factor=10.0) # FIXME: 10x is somewhat arbitrary here. Can we do something better? - alpha[ind] = 2.0 * np.arcsin(np.maximum(-1.0, - np.minimum(1.0, - rh.ssign(q.a[ind]) * q.d[ind]))) + + ind = util.isnegligible(beta, factor=10.0) # FIXME: 10x is somewhat arbitrary here. Can we do something better? + alpha[ind] = 2.0 * np.arcsin(np.maximum(-1.0, + np.minimum(1.0, + util.ssign(q.a[ind]) * q.d[ind]))) gamma[ind] = 0.0 - + alpha = np.mod(alpha, 2.0 * np.pi) gamma = np.mod(gamma, 2.0 * np.pi) - + return matthies(alpha, beta, gamma) #------------------------------------------------------------------------------- @@ -1905,31 +1895,31 @@ def from_rodri(cls, arg): @classmethod def from_rmat(cls, arg): - return matthies.from_quat(quat.from_rmat(arg)) + return matthies.from_quat(quat.from_rmat(arg)) #------------------------------------------------------------------------------- @classmethod def from_bunge(cls, arg): - + # algorithm from mtex 4.1.2 - import righthand as rh + import cryspy.util as util import numpy as np - - ind = ~rh.isnegligible(arg.PHI) - + + ind = ~util.isnegligible(arg.PHI) + alpha = arg.phi1 alpha[ind] = alpha[ind] - np.pi / 2.0 - + gamma = arg.phi2 gamma[ind] = gamma[ind] - 3.0 * np.pi / 2.0 - + return matthies(alpha, arg.PHI, gamma) #------------------------------------------------------------------------------- def __repr__(self): - + import numpy as np repstr = '{0:^29s}\n'.format('Matthies Euler angles (\xb0)') + \ @@ -1941,57 +1931,57 @@ def __repr__(self): n = self.size - 1 if n > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, - self.beta[i] * 180.0/np.pi, + repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, + self.beta[i] * 180.0/np.pi, self.gamma[i] * 180.0/np.pi) for i in np.arange(0, 3): repstr += ' {0:^7s} {0:^7s} {0:^7s} \n'.format('.') for i in np.arange(n-5, n): - repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, - self.beta[i] * 180.0/np.pi, + repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, + self.beta[i] * 180.0/np.pi, self.gamma[i] * 180.0/np.pi) else: - + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, - self.beta[i] * 180.0/np.pi, + repstr += fmtstr.format(self.alpha[i] * 180.0/np.pi, + self.beta[i] * 180.0/np.pi, self.gamma[i] * 180.0/np.pi) - + repstr += '\n' return repstr #------------------------------------------------------------------------------- def __getitem__(self,index): - + alf = self.alpha[index] bet = self.beta[index] gam = self.gamma[index] - + return matthies(alpha=alf, beta=bet, gamma=gam) - -#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ def consolidate(list_of_rotation_objects): """ stack lists of rotation objects into numpy array - + Parameters ---------- list_of_rotation_objects : list Python list containing cryspy.rot classes of the same type - + Returns ------- - numpy array - + numpy array + Notes ----- To re-create a cryspy.rot class object with the results of consolidate, see the .from_array method for any of the classes. - + Examples -------- >>> import cryspy.rot as rot @@ -2003,21 +1993,21 @@ def consolidate(list_of_rotation_objects): >>> symm_equivs_as_quat = rot.quat.from_array(symm_equivs_to_r) """ import numpy as np - + r = list_of_rotation_objects[0].to_array() - + if np.size(list_of_rotation_objects) > 1: - + return [np.vstack([r, item.to_array()]) for item in \ list_of_rotation_objects[1:]][0] else: - + return r -#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ # TODO: see if there is another, more elegant way to prevent import loops ### PREVENT IMPORT LOOPS ### # ovlib.xtal depends on ovlib.rot.quat and ovlib.rot.rmat already being defined -from cryspy.xtal import miller \ No newline at end of file +from cryspy3.xtal import miller \ No newline at end of file diff --git a/cryspy/tx.py b/cryspy/tx.py index c9c28e7..6b5978c 100755 --- a/cryspy/tx.py +++ b/cryspy/tx.py @@ -1,301 +1,300 @@ -# -*- coding: utf-8 -*- -# Created on Thu Feb 02 15:54:49 2017 by paytonej - -class kernel: - """ creates a kernel for odf functions - - Notes - ----- - - This is essentially a partial translation of the kernel class from MTEX - 4.1 - - So far only the deLaValeePoussin kernel has been implemented - """ - - def __init__(self, name='deLaValeePoussin', halfwidth=10.0, psi=None, bandwidth=None, antipodal=True): - - import numpy as np - import scipy.special as spec - - if name is 'deLaValeePoussin': - kappa = 0.5 * np.log(0.5) / np.log(np.cos(halfwidth * np.pi / 360.0)) - bandwidth = np.round(kappa) - - # Constant C - cc = spec.beta(1.5, 0.5) / spec.beta(1.5, kappa + 0.5) - - # Legendre Coefficients - aa = np.ones(bandwidth + 1) - aa[1] = kappa / (kappa + 2.0) - for l in np.arange(1, bandwidth): - aa[l+1] = ((kappa - l + 1) * aa[l-1] - (2.*l + 1.) * aa[l]) / (kappa + l + 2) - - for l in np.arange(0, bandwidth+1): - aa[l] = (2. * l + 1.0) * aa[l] - - # Cut A (aa). This is a private function within the mtex kernel class. - # Here we will organize it into the initialization of the kernel. - epsilon = 0.01 # in mtex, this is set by an mtex preference - ind = np.where(np.abs(aa) <= np.amax([np.amin(np.abs(aa)), - epsilon]))[0][0] - - self.name = name - self.halfwidth = halfwidth - self.kappa = kappa - self.L = bandwidth - self.C = cc - self.A = aa[0:np.amin([ind + 1, np.shape(aa)[0]])] # Chebyshev coefficients - self.antipodal = antipodal - - def __repr__(self): - if self.antipodal == True: - ant = 'antipodal' - else: - ant = '' - print('{0}{1:s} kernel with halfwidth {1:.5f}\xb0'.format(ant, - self.name, - self.halfwidth)) - -#------------------------------------------------------------------------------ - -def deg2dim(l): - """ return dimensions of the harmonic space up to order l - - Parameters - ---------- - l : float - - Returns - ------- - float - """ - import numpy as np - - dim = l * (2.0 * l - 1.0) * (2.0 * l + 1.0) / 3.0 - - return np.int(dim) - -#------------------------------------------------------------------------------ - - -# TODO: create similar functions to the below for fc2odf, odf2pf, pdf2pf, pf2odf. The code below should form a decent template to start from. -def odf2fc(quaternions, weights, kernel_legendre_coefficients): - """ computes Fourier coefficients for ODF using MTEX algorithm - - Parameters - ---------- - quaternions : cryspy.rot.quat class - crystal rotations - - weights : 1 x n numpy array - Weighting applied to each Euler angle - - kernel : cryspy.tx.kernel class - Kernel function to be used in ODF calculation - - Returns - ------- - 1 x n numpy array - Fourier coefficients - - Notes - ----- - Performs an external function call to MTEX's ODF calculation functions. - - Reference - --------- - - Examples - -------- - >>> import cryspy.io as cio - >>> import cryspy.tx as tx - >>> import numpy as np - >>> dat = cio.loadang('example_data.ang') - >>> o = dat.orientations - >>> c = c = np.ones(o.size) - >>> k = tx.kernel() - >>> fourier_coefficients = odf2fc(o.rotation, c, k) - """ - import cryspy.rot as rot - import cryspy.util as util - import righthand as rh - import numpy as np - import time - import subprocess - import io - - nfft_euler = rot.matthies.from_quat(quaternions).to_nfft().T - - # Convert our strings to byte string. - gb = nfft_euler.astype(np.float64).tostring() - cb = weights.astype(np.float64).tostring() - aab = kernel_legendre_coefficients.astype(np.float64).tostring() - - prgname = 'odf2fc' - - h = util.helper() # helper contains our system architecture-specific paths - prgpath = rh.fullfile([h.prgpth, prgname + h.ext]) - - # Give our temporary file a unique name using the time since epoch - suffix = str(int(time.time() * 100.0)) - name = prgname + suffix - - # The listfile will point the external function to where the inputs have been - # temporarily written. Each time we write a variable file, we will log it. - listfilepath = rh.fullfile([h.tmpdir, name + '.txt']) - listfile = io.open(listfilepath, 'w', newline='') # XXX: newline='' prevents the \lf\cr line endings in Windows. It is not clear to me at this time that this will work in Unix or Linux. - - # Write g to temporary folder and log it - datafilepath = rh.fullfile([h.tmpdir, name + '_' + 'g' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(gb) - listfile.write(unicode('g: ' + datafilepath + '\n', 'utf-8')) - - # Write c to temporary folder and log it - datafilepath = rh.fullfile([h.tmpdir, name + '_' + 'c' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(cb) - listfile.write(unicode('c: ' + datafilepath + '\n', 'utf-8')) - - # Write A to temporary folder and log it - datafilepath = rh.fullfile([h.tmpdir, name + '_' + 'A' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(aab) - listfile.write(unicode('A: ' + datafilepath + '\n', 'utf-8')) - - # Give a path for the output results file - outputfilepath = rh.fullfile([h.tmpdir, name + '_' + 'res1' + '.dat']) - with io.open(datafilepath, 'wb') as dfile: - dfile.write(aab) - listfile.write(unicode('res1: ' + outputfilepath + '\n', 'utf-8')) - - listfile.close() # close the file listing inputs - - # Use subprocess to perform the external system call to our function. - subprocess.call([prgpath, listfilepath]) # TODO: place in try/except structure and return an error if function call is unsuccessful - - # remove the temporary file containing the list of program inputs - del listfilepath - - # read in the output - result = np.fromfile(outputfilepath, 'd') - - return result - -#----------------------------------------------------------------------------- - -def calcFourier(component, kernel, ss): - - import numpy as np - import cryspy.rot as rot - - def gcA2Fourier(g, c, aa): - """ - Get Fourier coefficients from g, c, A - - same as the following subfunction gcA2fourier in MTEX: - [mtex dir]\ODFAnalysis\@unimodalComponent\calcFourier - """ - import cryspy.tx as tx - # this is a direct translation from mtex - # run NFSOFT - f = tx.odf2fc(g, c, aa) - return f[0::2] + 1j * f[1::2] - - def multiply_fourier_matrices(f1, f2, lA): - - import cryspy.tx as tx - - if not np.iscomplexobj(f1): - f1 = f1 + 0j - if not np.iscomplexobj(f2): - f1 = f2 + 0j - - f = np.zeros(np.size(f1)) + 0j - - for l in np.arange(0, lA): - - dmi = tx.deg2dim(l) - dmf = tx.deg2dim(l+1) - ind = np.arange(dmi, dmf) - shape = [2 * l + 1, 2 * l + 1] - f[ind] = np.matrix(np.reshape(f1[ind], shape)) * \ - np.matrix(np.reshape(f2[ind], shape)) - - return f - - # ----- - - cs = component.cs - - c = component.weights / ss.rotations.size / cs.rotations.size - - # Ralf original comment: - # "for a few center symmetrize before computing c_hat" - if 10 * component.size * ss.rotations.size * cs.rotations.size < \ - np.amax([kernel.L**3, 100]): - - # create a quat object containing all equiv rotations from xtal symm - g = rot.quat.from_array(rot.consolidate( - component.rotations.symmetrize(cs))) - - # add in the sample symmetries - g = rot.quat.from_array(rot.consolidate(g.symmetrize(ss))) - - c = np.tile(c, ss.rotations.size * cs.rotations.size) - - else: - g = component.rotations - - # Export Chebyshev coefficients - endval = np.amin([np.amax([2, kernel.L+1]), np.size(kernel.A)]) - 1 - aa = kernel.A[0:endval] - - # calculate Fourier coefficients - f_hat = gcA2Fourier(g, c, aa) - - # for many center symmetrize f_hat - # XXX: Note that this differs from MTEX 4.1.4 in that the greater than - # matches the less than for few centers above. - if 10 * component.size * ss.rotations.size * cs.rotations.size >= \ - np.amax([kernel.L**3, 100]): - - if cs.rotations.size != 1: - # symmetrize crystal symmetry - g = cs.rotations - aa[0:] = 1.0 - c = np.ones(cs.rotations.size) - f_hat = multiply_fourier_matrices(f_hat, - gcA2Fourier(g, c, aa), np.size(aa)-1) - - if ss.rotations.size != 1: - # symmetrize specimen symmetry - g = ss.rotations - aa[0:] = 1.0 - c = np.ones(ss.rotations.size) - f_hat = multiply_fourier_matrices(f_hat, - gcA2Fourier(g, c, aa), np.size(aa)-1) - - if component.antipodal == True: - f_hat = 0.5 * multiply_fourier_matrices(f_hat, - f_hat, - np.size(aa)-1) - - return f_hat - -#------------------------------------------------------------------------------ - -class odf(object): - - def __init__(self, orientations, antipodal=False): - import warnings - - self.rotations = orientations.rotations - self.size = orientations.size - self.antipodal = antipodal - self.weights = np.ones(component.size) # FIXME: This might not be correct for all cases - - if len(orientations.cs) == 1: - self.cs = orientations.cs[0] - else: - warnings.warn('Only one phase at a time ' + - 'can be analyzed for the ODF') \ No newline at end of file +# -*- coding: utf-8 -*- +# Created on Thu Feb 02 15:54:49 2017 by paytonej + +class kernel: + """ creates a kernel for odf functions + + Notes + ----- + - This is essentially a partial translation of the kernel class from MTEX + 4.1 + - So far only the deLaValeePoussin kernel has been implemented + """ + + def __init__(self, name='deLaValeePoussin', halfwidth=10.0, psi=None, bandwidth=None, antipodal=True): + + import numpy as np + import scipy.special as spec + + if name is 'deLaValeePoussin': + kappa = 0.5 * np.log(0.5) / np.log(np.cos(halfwidth * np.pi / 360.0)) + bandwidth = np.round(kappa) + + # Constant C + cc = spec.beta(1.5, 0.5) / spec.beta(1.5, kappa + 0.5) + + # Legendre Coefficients + aa = np.ones(bandwidth + 1) + aa[1] = kappa / (kappa + 2.0) + for l in np.arange(1, bandwidth): + aa[l+1] = ((kappa - l + 1) * aa[l-1] - (2.*l + 1.) * aa[l]) / (kappa + l + 2) + + for l in np.arange(0, bandwidth+1): + aa[l] = (2. * l + 1.0) * aa[l] + + # Cut A (aa). This is a private function within the mtex kernel class. + # Here we will organize it into the initialization of the kernel. + epsilon = 0.01 # in mtex, this is set by an mtex preference + ind = np.where(np.abs(aa) <= np.amax([np.amin(np.abs(aa)), + epsilon]))[0][0] + + self.name = name + self.halfwidth = halfwidth + self.kappa = kappa + self.L = bandwidth + self.C = cc + self.A = aa[0:np.amin([ind + 1, np.shape(aa)[0]])] # Chebyshev coefficients + self.antipodal = antipodal + + def __repr__(self): + if self.antipodal == True: + ant = 'antipodal' + else: + ant = '' + print '{0}{1:s} kernel with halfwidth {1:.5f}\xb0'.format(ant, + self.name, + self.halfwidth) + +#------------------------------------------------------------------------------ + +def deg2dim(l): + """ return dimensions of the harmonic space up to order l + + Parameters + ---------- + l : float + + Returns + ------- + float + """ + import numpy as np + + dim = l * (2.0 * l - 1.0) * (2.0 * l + 1.0) / 3.0 + + return np.int(dim) + +#------------------------------------------------------------------------------ + + +# TODO: create similar functions to the below for fc2odf, odf2pf, pdf2pf, pf2odf. The code below should form a decent template to start from. +def odf2fc(quaternions, weights, kernel_legendre_coefficients): + """ computes Fourier coefficients for ODF using MTEX algorithm + + Parameters + ---------- + quaternions : cryspy.rot.quat class + crystal rotations + + weights : 1 x n numpy array + Weighting applied to each Euler angle + + kernel : cryspy.tx.kernel class + Kernel function to be used in ODF calculation + + Returns + ------- + 1 x n numpy array + Fourier coefficients + + Notes + ----- + Performs an external function call to MTEX's ODF calculation functions. + + Reference + --------- + + Examples + -------- + >>> import cryspy.io as cio + >>> import cryspy.tx as tx + >>> import numpy as np + >>> dat = cio.loadang('example_data.ang') + >>> o = dat.orientations + >>> c = c = np.ones(o.size) + >>> k = tx.kernel() + >>> fourier_coefficients = odf2fc(o.rotation, c, k) + """ + import cryspy.rot as rot + import cryspy.util as util + import numpy as np + import time + import subprocess + import io + + nfft_euler = rot.matthies.from_quat(quaternions).to_nfft().T + + # Convert our strings to byte string. + gb = nfft_euler.astype(np.float64).tostring() + cb = weights.astype(np.float64).tostring() + aab = kernel_legendre_coefficients.astype(np.float64).tostring() + + prgname = 'odf2fc' + + h = util.helper() # helper contains our system architecture-specific paths + prgpath = util.fullfile([h.prgpth, prgname + h.ext]) + + # Give our temporary file a unique name using the time since epoch + suffix = str(int(time.time() * 100.0)) + name = prgname + suffix + + # The listfile will point the external function to where the inputs have been + # temporarily written. Each time we write a variable file, we will log it. + listfilepath = util.fullfile([h.tmpdir, name + '.txt']) + listfile = io.open(listfilepath, 'w', newline='') # XXX: newline='' prevents the \lf\cr line endings in Windows. It is not clear to me at this time that this will work in Unix or Linux. + + # Write g to temporary folder and log it + datafilepath = util.fullfile([h.tmpdir, name + '_' + 'g' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(gb) + listfile.write(unicode('g: ' + datafilepath + '\n', 'utf-8')) + + # Write c to temporary folder and log it + datafilepath = util.fullfile([h.tmpdir, name + '_' + 'c' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(cb) + listfile.write(unicode('c: ' + datafilepath + '\n', 'utf-8')) + + # Write A to temporary folder and log it + datafilepath = util.fullfile([h.tmpdir, name + '_' + 'A' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(aab) + listfile.write(unicode('A: ' + datafilepath + '\n', 'utf-8')) + + # Give a path for the output results file + outputfilepath = util.fullfile([h.tmpdir, name + '_' + 'res1' + '.dat']) + with io.open(datafilepath, 'wb') as dfile: + dfile.write(aab) + listfile.write(unicode('res1: ' + outputfilepath + '\n', 'utf-8')) + + listfile.close() # close the file listing inputs + + # Use subprocess to perform the external system call to our function. + subprocess.call([prgpath, listfilepath]) # TODO: place in try/except structure and return an error if function call is unsuccessful + + # remove the temporary file containing the list of program inputs + del listfilepath + + # read in the output + result = np.fromfile(outputfilepath, 'd') + + return result + +#----------------------------------------------------------------------------- + +def calcFourier(component, kernel, ss): + + import numpy as np + import cryspy.rot as rot + + def gcA2Fourier(g, c, aa): + """ + Get Fourier coefficients from g, c, A + + same as the following subfunction gcA2fourier in MTEX: + [mtex dir]\ODFAnalysis\@unimodalComponent\calcFourier + """ + import cryspy.tx as tx + # this is a direct translation from mtex + # run NFSOFT + f = tx.odf2fc(g, c, aa) + return f[0::2] + 1j * f[1::2] + + def multiply_fourier_matrices(f1, f2, lA): + + import cryspy.tx as tx + + if not np.iscomplexobj(f1): + f1 = f1 + 0j + if not np.iscomplexobj(f2): + f1 = f2 + 0j + + f = np.zeros(np.size(f1)) + 0j + + for l in np.arange(0, lA): + + dmi = tx.deg2dim(l) + dmf = tx.deg2dim(l+1) + ind = np.arange(dmi, dmf) + shape = [2 * l + 1, 2 * l + 1] + f[ind] = np.matrix(np.reshape(f1[ind], shape)) * \ + np.matrix(np.reshape(f2[ind], shape)) + + return f + + # ----- + + cs = component.cs + + c = component.weights / ss.rotations.size / cs.rotations.size + + # Ralf original comment: + # "for a few center symmetrize before computing c_hat" + if 10 * component.size * ss.rotations.size * cs.rotations.size < \ + np.amax([kernel.L**3, 100]): + + # create a quat object containing all equiv rotations from xtal symm + g = rot.quat.from_array(rot.consolidate( + component.rotations.symmetrize(cs))) + + # add in the sample symmetries + g = rot.quat.from_array(rot.consolidate(g.symmetrize(ss))) + + c = np.tile(c, ss.rotations.size * cs.rotations.size) + + else: + g = component.rotations + + # Export Chebyshev coefficients + endval = np.amin([np.amax([2, kernel.L+1]), np.size(kernel.A)]) - 1 + aa = kernel.A[0:endval] + + # calculate Fourier coefficients + f_hat = gcA2Fourier(g, c, aa) + + # for many center symmetrize f_hat + # XXX: Note that this differs from MTEX 4.1.4 in that the greater than + # matches the less than for few centers above. + if 10 * component.size * ss.rotations.size * cs.rotations.size >= \ + np.amax([kernel.L**3, 100]): + + if cs.rotations.size != 1: + # symmetrize crystal symmetry + g = cs.rotations + aa[0:] = 1.0 + c = np.ones(cs.rotations.size) + f_hat = multiply_fourier_matrices(f_hat, + gcA2Fourier(g, c, aa), np.size(aa)-1) + + if ss.rotations.size != 1: + # symmetrize specimen symmetry + g = ss.rotations + aa[0:] = 1.0 + c = np.ones(ss.rotations.size) + f_hat = multiply_fourier_matrices(f_hat, + gcA2Fourier(g, c, aa), np.size(aa)-1) + + if component.antipodal == True: + f_hat = 0.5 * multiply_fourier_matrices(f_hat, + f_hat, + np.size(aa)-1) + + return f_hat + +#------------------------------------------------------------------------------ + +class odf(object): + + def __init__(self, orientations, antipodal=False): + import warnings + + self.rotations = orientations.rotations + self.size = orientations.size + self.antipodal = antipodal + self.weights = np.ones(component.size) # FIXME: This might not be correct for all cases + + if len(orientations.cs) == 1: + self.cs = orientations.cs[0] + else: + warnings.warn('Only one phase at a time ' + + 'can be analyzed for the ODF') \ No newline at end of file diff --git a/cryspy/util.py b/cryspy/util.py index 0687519..9a24cbd 100755 --- a/cryspy/util.py +++ b/cryspy/util.py @@ -1,919 +1,1508 @@ -# -*- coding: utf-8 -*- -""" -ovlib.util: Utilities -=============================================================================== -""" - -def vecarraycross(b, c): - ''' cross products of arrays of vectors ''' - import numpy as np - bx = vecarrayconvert(b[0]) - by = vecarrayconvert(b[1]) - bz = vecarrayconvert(b[2]) - cx = vecarrayconvert(c[0]) - cy = vecarrayconvert(c[1]) - cz = vecarrayconvert(c[2]) - - if np.shape(bx) == np.shape(by) == np.shape(bz) == \ - np.shape(cx) == np.shape(cy) == np.shape(cz): - - ax = by * cz - bz * cy - ay = bz * cx - bx * cz - az = bx * cy - by * cx - - return [ax, ay, az] - else: - print("vecarraycross error: check that the lengths of arguments are equal.") # TODO: check into using warnings package - -#------------------------------------------------------------------------------- - -def vecarraynorm(a): - ''' norm of an array of vectors ''' - import numpy as np - ax = vecarrayconvert(a[0]) - ay = vecarrayconvert(a[1]) - az = vecarrayconvert(a[2]) - - if np.shape(ax) == np.shape(ay) == np.shape(az): - nrm=(ax*ax + ay*ay + az*az)**0.5 - else: - print("vecarraynorm error: check that the lengths of arguments are equal.") # TODO: check into using warnings package - - return nrm - -#------------------------------------------------------------------------------- - -def vecarraydot(b,c): - ''' dot products of an array of vectors ''' - import numpy as np - bx = vecarrayconvert(b[0]) - by = vecarrayconvert(b[1]) - bz = vecarrayconvert(b[2]) - cx = vecarrayconvert(c[0]) - cy = vecarrayconvert(c[1]) - cz = vecarrayconvert(c[2]) - - if np.shape(bx) == np.shape(by) == np.shape(bz) == \ - np.shape(cx) == np.shape(cy) == np.shape(cz): - - return bx*cx + by*cy + bz*cz - - else: - - print("vecarraydot error: check that the lengths of arguments are equal.") # TODO: check into using warnings package - -#------------------------------------------------------------------------------- - -def vecarraygcd(a,b): - ''' Compute greatest common denominator for values in two arrays''' - import numpy as np - - a = vecarrayconvert(a) - b = vecarrayconvert(b) - - if np.shape(a) == np.shape(b): - - r = np.zeros( np.size(a) ) # preallocate remainder array - - # if b is smaller than floating point error, we will make gcd=1 - a[ b < np.spacing(1) ] = 1.0 - j = b > np.spacing(1) - - if j.any(): - while j.any(): - r[j] = a[j] % b[j] - a[j] = b[j] - b[j] = r[j] - j[b==0]=False - - return a - else: - return a - -#------------------------------------------------------------------------------- - -def vecarrayconvert(a): - ''' convert any reasonable datatype into an n x 3 vector array''' - import numpy as np - - # asanyarray makes an array unless it's already an array - # squeeze remove unnecessary dimensions - # atleast_1d makes it so that the arrays are indexable if they are scalars - a = np.atleast_1d(np.squeeze(np.asanyarray(a))) - - return a - -#------------------------------------------------------------------------------- - -def stereotrans(xyz, center=[0,0,1], south=[1,0,0], east=[0,1,0]): - ''' - Perform non-standard stereographic projections following - Kosel TH, J Mater Sci 19 (1984) - ''' - import numpy as np - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xyz = vecarrayconvert(xyz) - nrmxyz = 1.0/vecarraynorm(xyz) - - xx = xyz[0]*nrmxyz - yy = xyz[1]*nrmxyz - zz = xyz[2]*nrmxyz - - n = np.size(xx) - - aa = np.tile(abc[0]*nrmabc,n) - bb = np.tile(abc[1]*nrmabc,n) - cc = np.tile(abc[2]*nrmabc,n) - dd = np.tile(fed[0]*nrmdef,n) - ee = np.tile(fed[1]*nrmdef,n) - ff = np.tile(fed[2]*nrmdef,n) - uu = np.tile(uvw[0]*nrmuvw,n) - vv = np.tile(uvw[1]*nrmuvw,n) - ww = np.tile(uvw[2]*nrmuvw,n) - - cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) - cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) - cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) - - denom = 1.0/(1.0+np.absolute(cosal)) - - xproj = cosmu * denom - yproj = -cosdl * denom - - hemis = np.tile('S',n) - if np.array(n)>1.0: - hemis[cosal<0.0] = 'N' - else: - if cosal<0: - hemis = np.tile('N',n) - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def stereotransstandard(xyz): - ''' - Perform the standard stereographic transform on cartesian vectors - following De Graef & McHenry - - Returns projected x, projected y, and the hemisphere of the projection - ''' - import numpy as np - x = vecarrayconvert(xyz[0]) - y = vecarrayconvert(xyz[1]) - z = vecarrayconvert(xyz[2]) - nrm = 1.0/vecarraynorm([x,y,z]) - denom = nrm/(1.0+abs(z*nrm)) - xproj = x * denom - yproj = -y * denom - - n = np.shape(x) - hemis = np.tile('S',n) - if n[0]>1.0: - hemis[z<0.0] = 'N' - else: - if z<0.0: - hemis = 'N' - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def revstereotrans(xyh,center=[0,0,-1],south=[-1,0,0],east=[0,-1,0]): - ''' - Reverse non-standard stereographic projection - ''' - import numpy as np - import numpy.linalg as npla - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - n = np.shape(xproj) - x = np.zeros(n) - y = np.zeros(n) - z = np.zeros(n) - - aa = abc[0]*nrmabc - bb = abc[1]*nrmabc - cc = abc[2]*nrmabc - dd = fed[0]*nrmdef - ee = fed[1]*nrmdef - ff = fed[2]*nrmdef - uu = uvw[0]*nrmuvw - vv = uvw[1]*nrmuvw - ww = uvw[2]*nrmuvw - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - - cosal = (1.0 - R) / (1.0 + R) - denom = abs(cosal) + 1.0 - cosmu = xproj * denom - cosdl = yproj * denom - - # For each case we determine xyz by solving a system of linear equations - for i in range(0,np.shape(cosal)[0]): - xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) - xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) - xyz = npla.solve(xyzcoef, xyzsoln) - x[i]= xyz[0] - y[i]=-xyz[1] - z[i]= xyz[2] - - # Correct z for the hemisphere - z = z * m - - return x,y,z - -#------------------------------------------------------------------------------- - -def revstereotransstandard(xyh): - ''' - Performs the reverse stereographic transform on the projected x and y - coordinates measured from the stereographic projection. - Reverse of De Graef and McHenry procedure. - - Returns the cartesian x, y, and z values of the normalized vector. - ''' - import numpy as np - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - z = (m - m * R) / (1.0 + R) - s = m * z + 1.0 - x = xproj * s - y = -yproj * s - - return x,y,z - -#------------------------------------------------------------------------------- - -def eatrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Perform non-standard equal area projections. - - Reference: - [1] Kosel TH, J Mater Sci 19 (1984) - ''' - import numpy as np - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xyz = vecarrayconvert(xyz) - nrmxyz = 1.0/vecarraynorm(xyz) - - xx = xyz[0]*nrmxyz - yy = xyz[1]*nrmxyz - zz = xyz[2]*nrmxyz - - n = np.shape(xx) - - aa = np.tile(abc[0]*nrmabc,n) - bb = np.tile(abc[1]*nrmabc,n) - cc = np.tile(abc[2]*nrmabc,n) - dd = np.tile(fed[0]*nrmdef,n) - ee = np.tile(fed[1]*nrmdef,n) - ff = np.tile(fed[2]*nrmdef,n) - uu = np.tile(uvw[0]*nrmuvw,n) - vv = np.tile(uvw[1]*nrmuvw,n) - ww = np.tile(uvw[2]*nrmuvw,n) - - cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) - cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) - cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) - - denom = 1.0/np.sqrt(1.0+np.absolute(cosal)) - - xproj = cosmu * denom - yproj = -cosdl * denom - - hemis = np.tile('S',n) - if np.array(n)>1.0: - hemis[cosal<0.0] = 'N' - else: - if cosal<0: - hemis = np.tile('N',n) - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def eatransstandard(xyz): - ''' - Perform the standard stereographic transform on cartesian vectors - following De Graef & McHenry - - Returns projected x, projected y, and the hemisphere of the projection - ''' - import numpy as np - - x=vecarrayconvert(xyz[0]) - y=vecarrayconvert(xyz[1]) - z=vecarrayconvert(xyz[2]) - nrm=1.0/vecarraynorm([x,y,z]) - denom=nrm/np.sqrt(1.0+abs(z*nrm)) - xproj = x * denom - yproj = -y * denom - - n=np.shape(x) - hemis = np.tile('S',n) - if n[0]>1.0: - hemis[z<0.0] = 'N' - else: - if z<0.0: - hemis = 'N' - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def reveatrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Reverse non-standard stereographic projection - ''' - import numpy as np - import numpy.linalg as npla - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - n = np.shape(xproj) - x = np.zeros(n) - y = np.zeros(n) - z = np.zeros(n) - - aa = abc[0]*nrmabc - bb = abc[1]*nrmabc - cc = abc[2]*nrmabc - dd = fed[0]*nrmdef - ee = fed[1]*nrmdef - ff = fed[2]*nrmdef - uu = uvw[0]*nrmuvw - vv = uvw[1]*nrmuvw - ww = uvw[2]*nrmuvw - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - - cosal = (1.0 - R) / (1.0 + R) - denom = (np.absolute(cosal) + 1.0)**2.0 - cosmu = xproj * denom - cosdl = yproj * denom - - # For each case we determine xyz by solving a system of linear equations - for i in range(0, np.shape(cosal)[0]): - xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) - xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) - xyz = npla.solve(xyzcoef, xyzsoln) - x[i]= xyz[0] - y[i]=-xyz[1] - z[i]= xyz[2] - - # Correct z for the hemisphere - z = z * m - - return x,y,z - -#------------------------------------------------------------------------------- - -def reveatransstandard(xyh): - ''' - Performs the reverse stereographic transform on the projected x and y - coordinates measured from the stereographic projection. - - Reverse of De Graef and McHenry procedure. - - Returns the cartesian x, y, and z values of the normalized vector. - ''' - import numpy as np - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - z = (m - m * R) / (1.0 + R) - s = (m * z + 1.0)**2.0 - x = xproj * s - y = -yproj * s - - return x,y,z - -#------------------------------------------------------------------------------- - -def gnomonictrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - Perform the gnomonic projection, allowing different projection views - ''' - import numpy as np - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xyz = vecarrayconvert(xyz) - nrmxyz = 1.0/vecarraynorm(xyz) - - xx = xyz[0]*nrmxyz - yy = xyz[1]*nrmxyz - zz = xyz[2]*nrmxyz - - n = np.shape(xx) - - aa = np.tile(abc[0]*nrmabc,n) - bb = np.tile(abc[1]*nrmabc,n) - cc = np.tile(abc[2]*nrmabc,n) - dd = np.tile(fed[0]*nrmdef,n) - ee = np.tile(fed[1]*nrmdef,n) - ff = np.tile(fed[2]*nrmdef,n) - uu = np.tile(uvw[0]*nrmuvw,n) - vv = np.tile(uvw[1]*nrmuvw,n) - ww = np.tile(uvw[2]*nrmuvw,n) - - cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) - cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) - cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) - - denom = 1.0/np.absolute(cosal) - - xproj = cosmu * denom - yproj = -cosdl * denom - - hemis = np.tile('S',n) - if np.array(n)>1.0: - hemis[cosal<0.0] = 'N' - else: - if cosal<0: - hemis = np.tile('N',n) - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def gnomonictransstandard(xyz): - ''' - Perform the standard gnomonic projection - - Returns projected x, projected y, and the hemisphere of the projection - ''' - import numpy as np - - x=vecarrayconvert(xyz[0]) - y=vecarrayconvert(xyz[1]) - z=vecarrayconvert(xyz[2]) - denom=1.0/np.absolute(z) - xproj = x * denom - yproj = -y * denom - - n=np.shape(x) - hemis = np.tile('S',n) - if n[0]>1.0: - hemis[z<0.0] = 'N' - else: - if z<0.0: - hemis = 'N' - - return xproj, yproj, hemis - -#------------------------------------------------------------------------------- - -def revgnomonictrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): - ''' - reverse gnomonic projection allowing different projection views - ''' - import numpy as np - import numpy.linalg as npla - - abc = vecarrayconvert(center) - nrmabc = 1.0/vecarraynorm(abc) - - uvw = vecarrayconvert(east) - nrmuvw = 1.0/vecarraynorm(uvw) - - fed = vecarrayconvert(south) # 'def' is reserved in python - nrmdef = 1.0/vecarraynorm(fed) - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - n = np.shape(xproj) - x = np.zeros(n) - y = np.zeros(n) - z = np.zeros(n) - - aa = abc[0]*nrmabc - bb = abc[1]*nrmabc - cc = abc[2]*nrmabc - dd = fed[0]*nrmdef - ee = fed[1]*nrmdef - ff = fed[2]*nrmdef - uu = uvw[0]*nrmuvw - vv = uvw[1]*nrmuvw - ww = uvw[2]*nrmuvw - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - - cosal = (1.0 - R) / (1.0 + R) - denom = np.absolute(cosal) - cosmu = xproj * denom - cosdl = yproj * denom - - # For each case we determine xyz by solving a system of linear equations - for i in range(0,np.shape(cosal)[0]): - xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) - xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) - xyz = npla.solve(xyzcoef, xyzsoln) - x[i]= xyz[0] - y[i]=-xyz[1] - z[i]= xyz[2] - - # Correct z for the hemisphere - z = z * m - - return x,y,z - -#------------------------------------------------------------------------------- - -def revgnomonictransstandard(xyh): - ''' - performs the reverse standard gnomonic projection - ''' - import numpy as np - - xproj = vecarrayconvert(xyh[0]) - yproj = vecarrayconvert(xyh[1]) - hemis = np.array(np.squeeze(xyh[2])) - - R = xproj*xproj + yproj*yproj - - m = np.ones(np.shape(hemis)) - m[hemis=='N'] = -1.0 - z = (m - m * R) / (1.0 + R) - s = m * z - x = xproj * s - y = -yproj * s - - return x,y,z - -#------------------------------------------------------------------------------- - -def xtaldot(p1=1,p2=0,p3=0, - g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1, - q1=1,q2=0,q3=0): - ''' implements the dot product within the crystallographic reference frame: - p*G\q where p and q are vectors and G is a metric matrix ''' - - p1 = vecarrayconvert(p1) - p2 = vecarrayconvert(p2) - p3 = vecarrayconvert(p3) - q1 = vecarrayconvert(q1) - q2 = vecarrayconvert(q2) - q3 = vecarrayconvert(q3) - g11 = vecarrayconvert(g11) - g12 = vecarrayconvert(g12) - g13 = vecarrayconvert(g13) - g21 = vecarrayconvert(g21) - g22 = vecarrayconvert(g22) - g23 = vecarrayconvert(g23) - g31 = vecarrayconvert(g31) - g32 = vecarrayconvert(g32) - g33 = vecarrayconvert(g33) - - return (g11*q1+g12*q2+g13*q3)*p1+ \ - (g21*q1+g22*q2+g23*q3)*p2+ \ - (g31*q1+g32*q2+g33*q3)*p3 - -#------------------------------------------------------------------------------- - -def xtalangle(p1=1,p2=0,p3=0,q1=1,q2=0,q3=0, - g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1): - ''' compute the angle between two directions in a crystal''' - - import numpy as np - - p1 = vecarrayconvert(p1) - p2 = vecarrayconvert(p2) - p3 = vecarrayconvert(p3) - q1 = vecarrayconvert(q1) - q2 = vecarrayconvert(q2) - q3 = vecarrayconvert(q3) - g11 = vecarrayconvert(g11) - g12 = vecarrayconvert(g12) - g13 = vecarrayconvert(g13) - g21 = vecarrayconvert(g21) - g22 = vecarrayconvert(g22) - g23 = vecarrayconvert(g23) - g31 = vecarrayconvert(g31) - g32 = vecarrayconvert(g32) - g33 = vecarrayconvert(g33) - - nrm=1.0/(vecarraynorm([p1,p2,p3])*vecarraynorm([q1,q2,q3])) - - return np.arccos(nrm * - xtaldot(p1,p2,p3,g11,g12,g13,g21,g22,g23,g31,g32,g33,q1,q2,q3)) - -#------------------------------------------------------------------------------- - -def rationalize(v, maxval=9): - ''' produce rational indices from fractions ''' - import numpy as np - import copy - - v0 = vecarrayconvert(v[0]) - v1 = vecarrayconvert(v[1]) - v2 = vecarrayconvert(v[2]) - nrm=1.0/vecarraynorm([v0,v1,v2]) - v0 = v0 * nrm - v1 = v1 * nrm - v2 = v2 * nrm - - if np.shape(v0)==np.shape(v1)==np.shape(v2): - if np.size(v0)==1: - v0=np.array([v0]) - v1=np.array([v1]) - v2=np.array([v2]) - - n=np.size(v[0]) - vi=np.zeros([n,maxval]) - vj=copy.copy(vi); vk=copy.copy(vi); vq=copy.copy(vi) - for i in range(1,maxval+1): - vx = np.around(np.float64(i) * v0) - vy = np.around(np.float64(i) * v1) - vz = np.around(np.float64(i) * v2) - tmpx = np.absolute(vx); tmpy = np.absolute(vy); tmpz = np.absolute(vz) - - # calculate the greatest common divisor between tmpx, tmpy, and tmpz - # we have to do this manually because fractions.gcd doesn't work on - # arrays and numpy doesn't yet have a gcd function implemented - div = 1.0/vecarraygcd(vecarraygcd(tmpx,tmpy),tmpz) - - # multipy the irrational indices by the greatest common divisor - vi[:,i-1] = vx * div - vj[:,i-1] = vy * div - vk[:,i-1] = vz * div - nrm=1.0/vecarraynorm([vi[:,i-1],vj[:,i-1],vk[:,i-1]]) - vq[:,i-1] = np.arccos(np.amin([ - np.tile(1.0,n), - vecarraydot([ vi[:,i-1]*nrm,vj[:,i-1]*nrm,vk[:,i-1]*nrm], - [v[0],v[1],v[2]]) - ],axis=0)) - - # extract the best match rational values - loc=np.argmin(vq.T, axis=0) - vi=vi[range(0, n),loc] - vj=vj[range(0, n),loc] - vk=vk[range(0, n),loc] - vq=vq[range(0, n),loc] - - return vi, vj, vk,vq - - else: - print("rationalize error:"+ \ - "check that the lengths of arguments are equal.") # TODO: check into using warnings package - -#------------------------------------------------------------------------------ - -def rand_vonMisesFisherM(n, kappa=0, mu=[1.0, 0.0, 0.0, 0.0]): - """ random number generation from von Mises-Fisher matrix distribution - - Return n samples of random unit directions centered around mu with - dispersion parameter kappa - - Parameters - ---------- - n : int - number of samples - - kappa : float > 0 - concentration parameter - - mu : iterable of floats - central vector; length determines dimensionality m - - Returns - ------- - x : n x m numpy array - rows correspond to random unit vectors from distribution - - Notes - ----- - This is a python translation of Sungkyu Jung's matlab code published on - his website, version dated 3 Feb 2010 [1]_. It uses the modified Ulrich's - algorithm from Wood [2]_. - - References - ---------- - .. [1] S. Jung, M. Foskey, J.S. Marron, "Principal Arc Analysis on Direct - Product Manifolds," Annnals of Applied Statistics (2011). - .. [2] A.T.A. Wood, "Simulation of the von Mises Fisher distribution," Commun. - Statist. 23 (1994). - """ - import numpy as np - import scipy.stats as stats - - # Convert mu to a 2d numpy array - mu = np.atleast_2d(np.squeeze(np.asarray(mu).ravel())) - - # the dimensionality is always given by the size of mu - m = mu.size - - b = (-2.0 * kappa + np.sqrt(4.0 * kappa**2.0 + (m - 1.0)**2.0)) / (m - 1.0) - x0 = (1.0 - b) / (1.0 + b) - c = kappa * x0 + (m - 1.0)*np.log(1.0 - x0**2.0) - - # steps 1 & 2 from [2] - nnow = n - ww = [] - while True: - ntrial = np.amax([np.around(nnow * 1.2), nnow + 10.0]) - z = stats.beta.rvs((m - 1.0) / 2.0, (m - 1.0) / 2.0, size=ntrial) - u = np.random.rand(ntrial) - w = (1.0 - (1.0 + b) * z) / (1.0 - (1.0 - b) * z) - - indicator = kappa * w + (m - 1.0) * np.log(1.0 - x0 * w) - c >= np.log(u) - if np.sum(indicator) >= nnow: - w1 = w[indicator] - ww = np.hstack([ww, w1[0:nnow]]) - break - else: - ww = np.hstack([ww, w[indicator]]) - nnow = nnow - np.sum[indicator] - - # step 3 from [2]: generate n uniformly distributed m dimensional random - # directions, using the logic: "directions of normal distribution are - # uniform on the sphere." - v = np.zeros([m - 1, n]) - nr = stats.norm.rvs(1.0, size=[m - 1, n]) - for i in range(0, n): - while True: - ni = np.dot(nr[:, i], nr[:, i]) # length of ith vector - # exclude too small values to avoid numerical discretization - if ni < np.sqrt(np.spacing(np.float64(1))): - # repeat randomization - nr[:, i] = stats.norm.rvs(1.0, size=[m - 1, 1]) - else: - v[:, i] = nr[:, i] / np.sqrt(ni) - break - - x = np.vstack([np.tile(np.sqrt(1.0 - ww**2.0), [m - 1, 1]) * v, - np.atleast_2d(ww)]) - - - # Get the rotation matrix that rotates the data to be centered at mu - d = mu.size - a = np.zeros(d) - a[-1] = 1.0 - a = np.atleast_2d(a) - ab = np.dot(a, mu.T) - alpha = np.arccos(ab) - ii = np.eye(d) - - if np.abs(ab - 1) < 1e-15: - rot = ii - elif np.abs(ab + 1) < 1e-15: - rot = -ii - else: - c = mu - a * ab - c = c / np.linalg.norm(c) - aa = np.dot(a.T, c) - np.dot(c.T, a) - rot = ii + np.sin(alpha)*aa + (np.cos(alpha) - 1.0)*(np.dot(a.T, a) + np.dot(c.T, c)) - - return np.dot(rot.T, x).T - -#------------------------------------------------------------------------------ - -class helper(object): - """ Provides system-specific information to cryspy - """ - def __init__(self): - - import tempfile, os, platform, warnings, inspect - - # get platform-dependent temporary directory - tmp_path = tempfile.gettempdir() - - # Determine the path to cryspy - cryspy_path = os.sep.join(inspect.getfile(inspect.currentframe()).split(os.sep)[0:-2]) - - # Get the bits used by the platform - bits, linkage = platform.architecture() - bits = bits[0:2] - - # determine the operating system platform - plat = platform.system().lower() - - # parse the platform bits and OS - result = [] - extension = '' - if plat == 'windows': - result = 'win' + bits - extension = '.exe' - elif plat == 'darwin' and bits == '64': - result = 'maci' + bits - elif plat == 'darwin' and bits == '32': - result = 'maci' - elif (plat == 'linux' or plat == 'linux2') and bits == '32': - result = 'glnx86' - elif (plat == 'linux' or plat == 'linux2') and bits == '64': - result = 'glnxa64' - else: - warnings.warn('Required c binary not compiled for your system.') - - # Create the path to our platform-dependent binary executables - prgpth = fullfile([cryspy_path, 'c', 'bin', result]) - - # Store this info in our helper object - self.tmpdir = tmp_path - self.arch = result - self.ext = extension - self.prgpth = prgpth - -#------------------------------------------------------------------------------ - -def uniformBungeGrid(delta): - ''' - Produce equal volume sampling over orientation space in Bunge Euler angles - - Parameters - ---------- - delta :: int - - Returns - ------- - b :: Bunge Euler angle object - - References - ---------- - .. [1] Kalidendi et al, Acta Mater 57 (2009) p3916. doi:10.1016/j.actamat.2009.04.055 - - ''' - import numpy as np - import cryspy.rot as rot - - phi1 = np.linspace(0.0, 2.0 * np.pi, delta) - PHI = np.cos(phi1) - phi2 = np.copy(phi1) - b = rot.bunge(phi1, PHI, phi2) - - return b - -#------------------------------------------------------------------------------ - -# TODO: def genCSL() \ No newline at end of file +# -*- coding: utf-8 -*- +""" +ovlib.util: Utilities +=============================================================================== +""" + +def vecarraycross(b, c): + ''' cross products of arrays of vectors ''' + import numpy as np + bx = vecarrayconvert(b[0]) + by = vecarrayconvert(b[1]) + bz = vecarrayconvert(b[2]) + cx = vecarrayconvert(c[0]) + cy = vecarrayconvert(c[1]) + cz = vecarrayconvert(c[2]) + + if np.shape(bx) == np.shape(by) == np.shape(bz) == \ + np.shape(cx) == np.shape(cy) == np.shape(cz): + + ax = by * cz - bz * cy + ay = bz * cx - bx * cz + az = bx * cy - by * cx + + return [ax, ay, az] + else: + print ("vecarraycross error: check that the lengths of arguments are equal.") + +#------------------------------------------------------------------------------- + +def vecarraynorm(a): + ''' norm of an array of vectors ''' + import numpy as np + ax = vecarrayconvert(a[0]) + ay = vecarrayconvert(a[1]) + az = vecarrayconvert(a[2]) + + if np.shape(ax) == np.shape(ay) == np.shape(az): + nrm=(ax*ax + ay*ay + az*az)**0.5 + else: + print ("vecarraynorm error: check that the lengths of arguments are equal.") + + return nrm + +#------------------------------------------------------------------------------- + +def vecarraydot(b,c): + ''' dot products of an array of vectors ''' + import numpy as np + bx = vecarrayconvert(b[0]) + by = vecarrayconvert(b[1]) + bz = vecarrayconvert(b[2]) + cx = vecarrayconvert(c[0]) + cy = vecarrayconvert(c[1]) + cz = vecarrayconvert(c[2]) + + if np.shape(bx) == np.shape(by) == np.shape(bz) == \ + np.shape(cx) == np.shape(cy) == np.shape(cz): + + return bx*cx + by*cy + bz*cz + + else: + + print ("vecarraydot error: check that the lengths of arguments are equal.") + +#------------------------------------------------------------------------------- + +def vecarraygcd(a,b): + ''' Compute greatest common denominator for values in two arrays''' + import numpy as np + + a = vecarrayconvert(a) + b = vecarrayconvert(b) + + if np.shape(a) == np.shape(b): + + r = np.zeros( np.size(a) ) # preallocate remainder array + + # if b is smaller than floating point error, we will make gcd=1 + a[ b < np.spacing(1) ] = 1.0 + j = b > np.spacing(1) + + if j.any(): + while j.any(): + r[j] = a[j] % b[j] + a[j] = b[j] + b[j] = r[j] + j[b==0]=False + + return a + else: + return a + +#------------------------------------------------------------------------------- + +def vecarrayconvert(a): # FIXME: Not necessary? Can just use asanyarray? + ''' convert any reasonable datatype into an n x 3 vector array''' + import numpy as np + + #a = np.asanyarray(a) + + #a = np.squeeze( np.float64( list( (a,) ))) + #a = np.atleast_2d(np.asarray(a)) + + # Make the arrays indexable if they are actually scalars + #if np.size(a) == 1: + # a = np.array([a]) + + a = np.atleast_1d(np.squeeze(np.asanyarray(a))) + + return a + +#------------------------------------------------------------------------------- + +def stereotrans(xyz, center=[0,0,1], south=[0,1,0], east=[1,0,0]): + ''' + Perform non-standard stereographic projections following + Kosel TH, J Mater Sci 19 (1984) + ''' + import numpy as np + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xyz = vecarrayconvert(xyz) + nrmxyz = 1.0/vecarraynorm(xyz) + + xx = xyz[0]*nrmxyz + yy = xyz[1]*nrmxyz + zz = xyz[2]*nrmxyz + + n = np.size(xx) + + aa = np.tile(abc[0]*nrmabc,n) + bb = np.tile(abc[1]*nrmabc,n) + cc = np.tile(abc[2]*nrmabc,n) + dd = np.tile(fed[0]*nrmdef,n) + ee = np.tile(fed[1]*nrmdef,n) + ff = np.tile(fed[2]*nrmdef,n) + uu = np.tile(uvw[0]*nrmuvw,n) + vv = np.tile(uvw[1]*nrmuvw,n) + ww = np.tile(uvw[2]*nrmuvw,n) + + cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) + cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) + cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) + + denom = 1.0/(1.0+np.absolute(cosal)) + + xproj = cosmu * denom + yproj = -cosdl * denom + + hemis = np.tile('S',n) + if np.array(n)>1.0: + hemis[cosal<0.0] = 'N' + else: + if cosal<0: + hemis = np.tile('N',n) + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def stereotransstandard(xyz): + ''' + Perform the standard stereographic transform on cartesian vectors + following De Graef & McHenry + + Returns projected x, projected y, and the hemisphere of the projection + ''' + import numpy as np + x = vecarrayconvert(xyz[0]) + y = vecarrayconvert(xyz[1]) + z = vecarrayconvert(xyz[2]) + nrm = 1.0/vecarraynorm([x,y,z]) + denom = nrm/(1.0+abs(z*nrm)) + xproj = x * denom + yproj = -y * denom + + n = np.shape(x) + hemis = np.tile('S',n) + if n[0]>1.0: + hemis[z<0.0] = 'N' + else: + if z<0.0: + hemis = 'N' + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def revstereotrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Reverse non-standard stereographic projection + ''' + import numpy as np + import numpy.linalg as npla + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + n = np.shape(xproj) + x = np.zeros(n) + y = np.zeros(n) + z = np.zeros(n) + + aa = abc[0]*nrmabc + bb = abc[1]*nrmabc + cc = abc[2]*nrmabc + dd = fed[0]*nrmdef + ee = fed[1]*nrmdef + ff = fed[2]*nrmdef + uu = uvw[0]*nrmuvw + vv = uvw[1]*nrmuvw + ww = uvw[2]*nrmuvw + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + + cosal = (1.0 - R) / (1.0 + R) + denom = abs(cosal) + 1.0 + cosmu = xproj * denom + cosdl = yproj * denom + + # For each case we determine xyz by solving a system of linear equations + for i in range(0,np.shape(cosal)[0]): + xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) + xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) + xyz = npla.solve(xyzcoef, xyzsoln) + x[i]= xyz[0] + y[i]=-xyz[1] + z[i]= xyz[2] + + # Correct z for the hemisphere + z = z * m + + return x,y,z + +#------------------------------------------------------------------------------- + +def revstereotransstandard(xyh): + ''' + Performs the reverse stereographic transform on the projected x and y + coordinates measured from the stereographic projection. + Reverse of De Graef and McHenry procedure. + + Returns the cartesian x, y, and z values of the normalized vector. + ''' + import numpy as np + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + z = (m - m * R) / (1.0 + R) + s = m * z + 1.0 + x = xproj * s + y = -yproj * s + + return x,y,z + +#------------------------------------------------------------------------------- + +def eatrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Perform non-standard equal area projections. + + Reference: + [1] Kosel TH, J Mater Sci 19 (1984) + ''' + import numpy as np + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xyz = vecarrayconvert(xyz) + nrmxyz = 1.0/vecarraynorm(xyz) + + xx = xyz[0]*nrmxyz + yy = xyz[1]*nrmxyz + zz = xyz[2]*nrmxyz + + n = np.shape(xx) + + aa = np.tile(abc[0]*nrmabc,n) + bb = np.tile(abc[1]*nrmabc,n) + cc = np.tile(abc[2]*nrmabc,n) + dd = np.tile(fed[0]*nrmdef,n) + ee = np.tile(fed[1]*nrmdef,n) + ff = np.tile(fed[2]*nrmdef,n) + uu = np.tile(uvw[0]*nrmuvw,n) + vv = np.tile(uvw[1]*nrmuvw,n) + ww = np.tile(uvw[2]*nrmuvw,n) + + cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) + cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) + cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) + + denom = 1.0/np.sqrt(1.0+np.absolute(cosal)) + + xproj = cosmu * denom + yproj = -cosdl * denom + + hemis = np.tile('S',n) + if np.array(n)>1.0: + hemis[cosal<0.0] = 'N' + else: + if cosal<0: + hemis = np.tile('N',n) + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def eatransstandard(xyz): + ''' + Perform the standard stereographic transform on cartesian vectors + following De Graef & McHenry + + Returns projected x, projected y, and the hemisphere of the projection + ''' + import numpy as np + + x=vecarrayconvert(xyz[0]) + y=vecarrayconvert(xyz[1]) + z=vecarrayconvert(xyz[2]) + nrm=1.0/vecarraynorm([x,y,z]) + denom=nrm/np.sqrt(1.0+abs(z*nrm)) + xproj = x * denom + yproj = -y * denom + + n=np.shape(x) + hemis = np.tile('S',n) + if n[0]>1.0: + hemis[z<0.0] = 'N' + else: + if z<0.0: + hemis = 'N' + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def reveatrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Reverse non-standard stereographic projection + ''' + import numpy as np + import numpy.linalg as npla + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + n = np.shape(xproj) + x = np.zeros(n) + y = np.zeros(n) + z = np.zeros(n) + + aa = abc[0]*nrmabc + bb = abc[1]*nrmabc + cc = abc[2]*nrmabc + dd = fed[0]*nrmdef + ee = fed[1]*nrmdef + ff = fed[2]*nrmdef + uu = uvw[0]*nrmuvw + vv = uvw[1]*nrmuvw + ww = uvw[2]*nrmuvw + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + + cosal = (1.0 - R) / (1.0 + R) + denom = (np.absolute(cosal) + 1.0)**2.0 + cosmu = xproj * denom + cosdl = yproj * denom + + # For each case we determine xyz by solving a system of linear equations + for i in range(0, np.shape(cosal)[0]): + xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) + xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) + xyz = npla.solve(xyzcoef, xyzsoln) + x[i]= xyz[0] + y[i]=-xyz[1] + z[i]= xyz[2] + + # Correct z for the hemisphere + z = z * m + + return x,y,z + +#------------------------------------------------------------------------------- + +def reveatransstandard(xyh): + ''' + Performs the reverse stereographic transform on the projected x and y + coordinates measured from the stereographic projection. + + Reverse of De Graef and McHenry procedure. + + Returns the cartesian x, y, and z values of the normalized vector. + ''' + import numpy as np + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + z = (m - m * R) / (1.0 + R) + s = (m * z + 1.0)**2.0 + x = xproj * s + y = -yproj * s + + return x,y,z + +#------------------------------------------------------------------------------- + +def gnomonictrans(xyz,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + Perform the gnomonic projection, allowing different projection views + ''' + import numpy as np + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xyz = vecarrayconvert(xyz) + nrmxyz = 1.0/vecarraynorm(xyz) + + xx = xyz[0]*nrmxyz + yy = xyz[1]*nrmxyz + zz = xyz[2]*nrmxyz + + n = np.shape(xx) + + aa = np.tile(abc[0]*nrmabc,n) + bb = np.tile(abc[1]*nrmabc,n) + cc = np.tile(abc[2]*nrmabc,n) + dd = np.tile(fed[0]*nrmdef,n) + ee = np.tile(fed[1]*nrmdef,n) + ff = np.tile(fed[2]*nrmdef,n) + uu = np.tile(uvw[0]*nrmuvw,n) + vv = np.tile(uvw[1]*nrmuvw,n) + ww = np.tile(uvw[2]*nrmuvw,n) + + cosdl = vecarraydot([xx,yy,zz],[dd,ee,ff]) + cosmu = vecarraydot([xx,yy,zz],[uu,vv,ww]) + cosal = vecarraydot([xx,yy,zz],[aa,bb,cc]) + + denom = 1.0/np.absolute(cosal) + + xproj = cosmu * denom + yproj = -cosdl * denom + + hemis = np.tile('S',n) + if np.array(n)>1.0: + hemis[cosal<0.0] = 'N' + else: + if cosal<0: + hemis = np.tile('N',n) + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def gnomonictransstandard(xyz): + ''' + Perform the standard gnomonic projection + + Returns projected x, projected y, and the hemisphere of the projection + ''' + import numpy as np + + x=vecarrayconvert(xyz[0]) + y=vecarrayconvert(xyz[1]) + z=vecarrayconvert(xyz[2]) + denom=1.0/np.absolute(z) + xproj = x * denom + yproj = -y * denom + + n=np.shape(x) + hemis = np.tile('S',n) + if n[0]>1.0: + hemis[z<0.0] = 'N' + else: + if z<0.0: + hemis = 'N' + + return xproj, yproj, hemis + +#------------------------------------------------------------------------------- + +def revgnomonictrans(xyh,center=[0,0,1],south=[0,1,0],east=[1,0,0]): + ''' + reverse gnomonic projection allowing different projection views + ''' + import numpy as np + import numpy.linalg as npla + + abc = vecarrayconvert(center) + nrmabc = 1.0/vecarraynorm(abc) + + uvw = vecarrayconvert(east) + nrmuvw = 1.0/vecarraynorm(uvw) + + fed = vecarrayconvert(south) # 'def' is reserved in python + nrmdef = 1.0/vecarraynorm(fed) + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + n = np.shape(xproj) + x = np.zeros(n) + y = np.zeros(n) + z = np.zeros(n) + + aa = abc[0]*nrmabc + bb = abc[1]*nrmabc + cc = abc[2]*nrmabc + dd = fed[0]*nrmdef + ee = fed[1]*nrmdef + ff = fed[2]*nrmdef + uu = uvw[0]*nrmuvw + vv = uvw[1]*nrmuvw + ww = uvw[2]*nrmuvw + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + + cosal = (1.0 - R) / (1.0 + R) + denom = np.absolute(cosal) + cosmu = xproj * denom + cosdl = yproj * denom + + # For each case we determine xyz by solving a system of linear equations + for i in range(0,np.shape(cosal)[0]): + xyzcoef = np.squeeze(np.array([[dd,ee,ff],[uu,vv,ww],[aa,bb,cc]])) + xyzsoln = np.array([cosdl[i], cosmu[i], cosal[i]]) + xyz = npla.solve(xyzcoef, xyzsoln) + x[i]= xyz[0] + y[i]=-xyz[1] + z[i]= xyz[2] + + # Correct z for the hemisphere + z = z * m + + return x,y,z + +#------------------------------------------------------------------------------- + +def revgnomonictransstandard(xyh): + ''' + performs the reverse standard gnomonic projection + ''' + import numpy as np + + xproj = vecarrayconvert(xyh[0]) + yproj = vecarrayconvert(xyh[1]) + hemis = np.array(np.squeeze(xyh[2])) + + R = xproj*xproj + yproj*yproj + + m = np.ones(np.shape(hemis)) + m[hemis=='N'] = -1.0 + z = (m - m * R) / (1.0 + R) + s = m * z + x = xproj * s + y = -yproj * s + + return x,y,z + +#------------------------------------------------------------------------------- + +def xtaldot(p1=1,p2=0,p3=0, + g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1, + q1=1,q2=0,q3=0): + ''' implements the dot product within the crystallographic reference frame: + p*G\q where p and q are vectors and G is a metric matrix ''' + + p1 = vecarrayconvert(p1) + p2 = vecarrayconvert(p2) + p3 = vecarrayconvert(p3) + q1 = vecarrayconvert(q1) + q2 = vecarrayconvert(q2) + q3 = vecarrayconvert(q3) + g11 = vecarrayconvert(g11) + g12 = vecarrayconvert(g12) + g13 = vecarrayconvert(g13) + g21 = vecarrayconvert(g21) + g22 = vecarrayconvert(g22) + g23 = vecarrayconvert(g23) + g31 = vecarrayconvert(g31) + g32 = vecarrayconvert(g32) + g33 = vecarrayconvert(g33) + + return (g11*q1+g12*q2+g13*q3)*p1+ \ + (g21*q1+g22*q2+g23*q3)*p2+ \ + (g31*q1+g32*q2+g33*q3)*p3 + +#------------------------------------------------------------------------------- + +def xtalangle(p1=1,p2=0,p3=0,q1=1,q2=0,q3=0, + g11=1,g12=0,g13=0,g21=0,g22=1,g23=0,g31=0,g32=0,g33=1): + ''' compute the angle between two directions in a crystal''' + + import numpy as np + + p1 = vecarrayconvert(p1) + p2 = vecarrayconvert(p2) + p3 = vecarrayconvert(p3) + q1 = vecarrayconvert(q1) + q2 = vecarrayconvert(q2) + q3 = vecarrayconvert(q3) + g11 = vecarrayconvert(g11) + g12 = vecarrayconvert(g12) + g13 = vecarrayconvert(g13) + g21 = vecarrayconvert(g21) + g22 = vecarrayconvert(g22) + g23 = vecarrayconvert(g23) + g31 = vecarrayconvert(g31) + g32 = vecarrayconvert(g32) + g33 = vecarrayconvert(g33) + + nrm=1.0/(vecarraynorm([p1,p2,p3])*vecarraynorm([q1,q2,q3])) + + return np.arccos(nrm * + xtaldot(p1,p2,p3,g11,g12,g13,g21,g22,g23,g31,g32,g33,q1,q2,q3)) + +#------------------------------------------------------------------------------- + +def rationalize(v,maxval=9): + ''' produce rational indices from fractions ''' + import numpy as np + import copy + + v0 = vecarrayconvert(v[0]) + v1 = vecarrayconvert(v[1]) + v2 = vecarrayconvert(v[2]) + nrm=1.0/vecarraynorm([v0,v1,v2]) + v0 = v0 * nrm + v1 = v1 * nrm + v2 = v2 * nrm + + if np.shape(v0)==np.shape(v1)==np.shape(v2): + if np.size(v0)==1: + v0=np.array([v0]) + v1=np.array([v1]) + v2=np.array([v2]) + + n=np.size(v[0]) + vi=np.zeros([n,maxval]) + vj=copy.copy(vi); vk=copy.copy(vi); vq=copy.copy(vi) + for i in range(1,maxval+1): + vx = np.around(np.float64(i) * v0) + vy = np.around(np.float64(i) * v1) + vz = np.around(np.float64(i) * v2) + tmpx = np.absolute(vx); tmpy = np.absolute(vy); tmpz = np.absolute(vz) + + # calculate the greatest common divisor between tmpx, tmpy, and tmpz + # we have to do this manually because fractions.gcd doesn't work on + # arrays and numpy doesn't yet have a gcd function implemented + div = 1.0/vecarraygcd(vecarraygcd(tmpx,tmpy),tmpz) + + # multipy the irrational indices by the greatest common divisor + vi[:,i-1] = vx * div + vj[:,i-1] = vy * div + vk[:,i-1] = vz * div + nrm=1.0/vecarraynorm([vi[:,i-1],vj[:,i-1],vk[:,i-1]]) + vq[:,i-1] = np.arccos(np.amin([ + np.tile(1.0,n), + vecarraydot([ vi[:,i-1]*nrm,vj[:,i-1]*nrm,vk[:,i-1]*nrm], + [v[0],v[1],v[2]]) + ],axis=0)) + + # extract the best match rational values + loc=np.argmin(vq.T, axis=0) + vi=vi[range(0, n),loc] + vj=vj[range(0, n),loc] + vk=vk[range(0, n),loc] + vq=vq[range(0, n),loc] + + return vi, vj, vk,vq + + else: + print ("rationalize error:"+ \ + "check that the lengths of arguments are equal.") + +#------------------------------------------------------------------------------- + +def degsymbol(): + '''returns the degree symbol for plotting''' + return unichr(176).encode("latin-1") + +#------------------------------------------------------------------------------- + +def degrees(a): + '''converts radians to degrees''' + import numpy as np + + a=vecarrayconvert(a) + return a*180.0/np.pi + +#------------------------------------------------------------------------------- + +def radians(a): + '''converts degrees to radians''' + import numpy as np + + a=vecarrayconvert(a) + return a*np.pi/180.0 + +#------------------------------------------------------------------------------ + +def uniquerows(aa): + ''' returns the number of unique rows in an array. + + Parameters + ---------- + aa : numpy array + + Returns + ------- + cc : numpy array + Rows in aa without repetitions + ia : numpy Bool array + Indexing array such that cc = aa[ia] + ic : numpy index array + Indexing array such that aa = cc[ic] + + Notes + ----- + Mimics behavior of matlab function 'unique' with optional parameter 'rows'. + Algorithm modified from a stack overflow posting [1]_. + + References + --------- + .. [1] http://stackoverflow.com/questions/8560440/, Accessed 2012-09-10 + ''' + import numpy as np + + ind = np.lexsort(np.fliplr(aa).T) # indices for aa sorted by rows + rev = np.argsort(ind) # reverse of the sorting indices + ab = aa[ind] # ab is sorted version of aa + dd = np.diff(ab, axis=0) # get differences between the rows + ui = np.ones(np.shape(ab)[0], 'bool') # preallocate boolean array + ui[1:] = (dd != 0).any(axis=1) # if difference is zero, row is not unique + + ia = ui[rev] # positions of unique rows in aa (original, unsorted array) + cc = aa[ia] # unique rows in aa in original order + + loc = np.cumsum(np.uint64(ui))-1 # cumulative sum := locs of repeats in ab + + # - rev[ia] gives us the indices of the unique rows in aa + # - argsort(rev[ia]) gives us the indices of the corresponding rows in cc + # - argsort(rev[ia])[loc] gives us the indexing relationship for ab from cc + # - np.argsort(rev[ia])[loc][rev] give indexing reln in original order + ic = np.argsort(rev[ia])[loc][rev] + + return cc, ia, ic + +#------------------------------------------------------------------------------ + +def sortrows(a): + ''' + sorts an array by rows + + Notes + ----- + Mimics the behavior of matlab's function of the same name. + ''' + import numpy as np + order = np.lexsort(np.fliplr(a).T) + return a[order] + +#------------------------------------------------------------------------------ + +def sigdec(a, n=1): + ''' + Rounds the elements of a to n decimals. + A slight modification of Peter J. Acklam's Matlab function FIXDIG + ''' + import numpy as np + a = vecarrayconvert(a) + n = vecarrayconvert(n) + f = np.array(10.**n) + s = np.sign(a) + + return s * np.around(np.absolute(a) * f) / f + +#------------------------------------------------------------------------------- +def tic(): + ''' Timing function (like tic/toc in matlab). See also: toc''' + import time + return time.time() + +def toc(t): + ''' Timing function (like tic/toc in matlab). See also: tic''' + import time + t2 = time.time() + print (str(t2 - t) + ' seconds elapsed.') + return t2 - t + +#------------------------------------------------------------------------------- +class progbar: + ''' console progress indicator + + Parameters + ---------- + finalcount : int + the last number in the range + period : float + the amount of time that is ignored for updates to reduce slowdowns + caused by frequent calls to the function + message : string + the message to be displayed above the bar in the console + + Returns + ------- + None + + Notes + ----- + This is a modification of the console progress indicator class recipe by + Larry Bates [1]_ to include ideas from Ben Mitch's matlab progbar [2]_. + + References + ---------- + .. [1] L. Bates, "Console progress indicator class." Accessed 2013-02-13. + URL: http://code.activestate.com/recipes/299207-console-text-progress-indicator-class/ + .. [2] B. Mitch, "progbar.m: general purpose progress bar for matlab." + Accessed 2013-02-13. + URL: http://www.mathworks.com/matlabcentral/fileexchange/869 + + Examples + -------- + >>> # Create the progbar object + >>> pb = progbar(finalcount=10, period=0.2, message='Progress indicator') + >>> for i in range(1,11,1): + ... pb.update(i) # pass the progress values to the update method + >>> pb.update(-1) # update a negative value to signal process is complete + # and trigger the output of the elapsed time. + + + ''' + def __init__(self, finalcount=1, period=0.2, + progresschar=None, message=''): + import time + import sys + import numpy as np + + self.finalcount = finalcount + self.blockcount = 0 + + # See if caller passed me a character to use on the + # progress bar (like "*"). If not use the block + # character that makes it look like a real progress + # bar. + if not progresschar: self.block = chr(149) + else: self.block = progresschar + + # Get pointer to sys.stdout so I can use the write/flush + # methods to display the progress bar. + self.f = sys.stdout + + # Store the current time and minimum period between updates + self.ctim = time.time() + self.itim = time.time() # initial time + self.period = period + self.finished = False + + # If the final count is zero, don't start the progress gauge + if not self.finalcount: return + if len(message) < 50: + val = max(np.floor(0.5 * (50 - len(message)))-1, 0) + else: + val = 0 + self.f.write('\n'+' '*val+message+'\n') + self.f.write('|_____________ P R O G R E S S ________________|\n') + self.f.write('|----1----2----3----4----5----6----7----8----9---|' + \ + ' FINISH!\n') + return + + def update(self, count): + import time + import numpy as np + + # Check if the elapsed time is greater than the period. If not, return. + etim = time.time() + if (etim - self.ctim < self.period and count != -1): return + + # If the elapsed time is greater than the period, update the bar + self.ctim = etim + + # Make sure I don't try to go off the end (e.g. >100%) + if count == -1: count = self.finalcount + count=min(count, self.finalcount) + + # If finalcount is zero, I don't start + if self.finalcount: + percentcomplete=int(np.around(100*count/self.finalcount)) + if percentcomplete < 1: percentcomplete = 1 + else: + percentcomplete = 100 + + blockcount = int(percentcomplete/2) + if blockcount > self.blockcount: + for i in range(self.blockcount,blockcount): + self.f.write(self.block) + self.f.flush() + + if percentcomplete == 100 or count == self.finalcount: + if self.finished == False: + self.f.write(" ELAPSED TIME: " + \ + time.strftime('%H:%M:%S', \ + time.gmtime(etim-self.itim)) + '\n\n') + self.finished = True + + self.blockcount = blockcount + return + +#------------------------------------------------------------------------------- + +def iseven(x): + ''' is it an even number? + + Returns True if x is even and False if x is odd + + Parameters + ---------- + x : int + + Returns + ------- + b : boolean + ''' + import numpy as np + + # using np.absolute in case input is array + xa = vecarrayconvert(x) + b = np.mod(xa, 1) == 0 and np.absolute(np.mod(xa, 2)) <= np.spacing(1) + return b + +#------------------------------------------------------------------------------ + +def fileconvert_eurodecimals_to_international(filepath, outputfilepath=None, \ + inplace=False): + ''' convert commas to points in numbers in text data files + + Converts a text file where the decimals are given by commas into one + where the decimals are given by decimal points. + + Parameters + ---------- + filepath : string + the path to the file + outputfilepath : string (optional) + The path where the new file should be saved. The default is the same + directory where the original file was stored. + inplace : Boolean + Flag for whether original file should be overwritten (Default=False) + + Notes + ----- + If inplace is true, it will overwrite your file! You might want to have + a backup somewhere in this case. + ''' + import time + + if outputfilepath==None: + outputfilepath = \ + time.strftime('_%Y%m%d_%H%M%S.').join(filepath.split('.')) + + if inplace==True: + outputfilepath = filepath + + with open(filepath,'r') as f: + + newf = open(outputfilepath,'r+') + + for line in f: + ll = line.split() + newll = [] + for item in ll: + if item[0].isdigit() and item[-1].isdigit() and item.find(','): + newll.append(item.replace(',','.')) + else: + newll.append(item) + newf.write('\t'.join(newll)+'\n') + newf.close() + +#------------------------------------------------------------------------------ + +def fileconvert_international_to_eurodecimals(filepath, outputfilepath=None, \ + inplace=False): + ''' convert points to commas in numbers in text data files + + Converts a text file where the decimals are given by decimal points into + one where the decimals are given by commas. + + Parameters + ---------- + filepath : string + the path to the file + outputfilepath : string (optional) + The path where the new file should be saved. The default is the same + directory where the original file was stored. + inplace : Boolean + Flag for whether original file should be overwritten (Default=False) + + Notes + ----- + If inplace is true, it will overwrite your file! You might want to have + a backup somewhere in this case. + ''' + import time + + if outputfilepath==None: + outputfilepath = \ + time.strftime('_%Y%m%d_%H%M%S.').join(filepath.split('.')) + + if inplace==True: + outputfilepath = filepath + + with open(filepath, 'r') as f: + + newf = open(outputfilepath, 'r+') + + for line in f: + ll = line.split() + newll = [] + for item in ll: + if item[0].isdigit() and item[-1].isdigit() and item.find(','): + newll.append(item.replace('.',',')) + else: + newll.append(item) + newf.write('\t'.join(newll)+'\n') + newf.close() + +#------------------------------------------------------------------------------ + +def polar(x=0, y=0, z=None): + ''' polar coordinates from cartesian coordinates + + Converts cartesian coordinates to polar coordinates using a right-handed + coordinate system that aligns with the standard definitions of polar + coordinates in 2D, i.e. theta is the azimuthal angle measured from the + positive x axis, r is the radius of the circle. This definition is extended + to the 3D case such that r corresponds to the radius of the sphere for 3D + data and the additional parameter rho is the polar angle measured from the + positive z direction. This allows the 3D spherical coordinates to be + projected in the x-y plane as polar coordinates with r_2D = r_3D*cos(rho). + + Parameters + ---------- + x : numpy array + y : numpy array + z : numpy array (optional) + + Returns + ------- + theta : numpy array + r : numpy array + rho : numpy array (variable argument out) + Rho is returned only when z values are passed as input parameters + + Notes + ----- + The conventions used here differ from those generally used by physicists + for spherical coordinates, which are specified in the ISO standard + 31-11 [1]_; however, the conventions used here are consistent with the + standard conventions used in polar coordinates (2D). + + References + ---------- + [1] "Spherical coordinate system." Wikipedia. Accessed 2013-03-27. + ''' + import numpy as np + + theta = np.arctan2(y, x) + loc = np.array(theta < 0.0) * np.array(theta > 2.0*np.pi) + theta[loc] = np.mod(theta[loc], 2.0*np.pi) + + loc = np.absolute(theta) < np.sqrt(np.spacing(1)) + theta[loc] = 0.0 + + if z==None: + r = np.sqrt(x**2.0 + y**2.0) + return theta, r + else: + r = np.sqrt(x**2.0 + y**2.0 + z**2.0) + rho = np.arccos(z / r) + loc = np.array(rho < 0.0) * np.array(rho > 2.0*np.pi) + rho[loc] = np.mod(rho[loc], 2.0*np.pi) + + loc = np.absolute(rho) < np.sqrt(np.spacing(1)) + rho[loc] = 0.0 + + return theta, r, rho + +#------------------------------------------------------------------------------ + +def cart(theta=0, r=0, rho=None): + ''' converts polar coordinates to cartesian coordinates in 2D + + Notes + ----- + uses a right-handed 3D coordinate system that aligns with the standard + definitions of polar coordinates in 2D, i.e. theta is the azimuthal angle + measured from the positive x axis and rho is the polar angle measured from + the positive z direction. + ''' + import numpy as np + + if rho==None: + x = r * np.cos(theta) + y = r * np.sin(theta) + return x, y + else: + x = r * np.sin(rho) * np.cos(theta) + y = r * np.sin(rho) * np.sin(theta) + z = r * np.cos(rho) + return x, y, z + +#------------------------------------------------------------------------------ + +def rand_vonMisesFisherM(n, kappa=0, mu=[1.0, 0.0, 0.0, 0.0]): + """ random number generation from von Mises-Fisher matrix distribution + + Return n samples of random unit directions centered around mu with + dispersion parameter kappa + + Parameters + ---------- + n : int + number of samples + + kappa : float > 0 + concentration parameter + + mu : iterable of floats + central vector; length determines dimensionality m + + Returns + ------- + x : n x m numpy array + rows correspond to random unit vectors from distribution + + Notes + ----- + This is a python translation of Sungkyu Jung's matlab code published on + his website, version dated 3 Feb 2010 [1]_. It uses the modified Ulrich's + algorithm from Wood [2]_. + + References + ---------- + .. [1] S. Jung, M. Foskey, J.S. Marron, "Principal Arc Analysis on Direct + Product Manifolds," Annnals of Applied Statistics (2011). + .. [2] A.T.A. Wood, "Simulation of the von Mises Fisher distribution," Commun. + Statist. 23 (1994). + """ + import numpy as np + import scipy.stats as stats + + # Convert mu to a 2d numpy array + mu = np.atleast_2d(np.squeeze(np.asarray(mu).ravel())) + + # the dimensionality is always given by the size of mu + m = mu.size + + b = (-2.0 * kappa + np.sqrt(4.0 * kappa**2.0 + (m - 1.0)**2.0)) / (m - 1.0) + x0 = (1.0 - b) / (1.0 + b) + c = kappa * x0 + (m - 1.0)*np.log(1.0 - x0**2.0) + + # steps 1 & 2 from [2] + nnow = n + ww = [] + while True: + ntrial = np.amax([np.around(nnow * 1.2), nnow + 10.0]) + z = stats.beta.rvs((m - 1.0) / 2.0, (m - 1.0) / 2.0, size=ntrial) + u = np.random.rand(ntrial) + w = (1.0 - (1.0 + b) * z) / (1.0 - (1.0 - b) * z) + + indicator = kappa * w + (m - 1.0) * np.log(1.0 - x0 * w) - c >= np.log(u) + if np.sum(indicator) >= nnow: + w1 = w[indicator] + ww = np.hstack([ww, w1[0:nnow]]) + break + else: + ww = np.hstack([ww, w[indicator]]) + nnow = nnow - np.sum[indicator] + + # step 3 from [2]: generate n uniformly distributed m dimensional random + # directions, using the logic: "directions of normal distribution are + # uniform on the sphere." + v = np.zeros([m - 1, n]) + nr = stats.norm.rvs(1.0, size=[m - 1, n]) + for i in range(0, n): + while True: + ni = np.dot(nr[:, i], nr[:, i]) # length of ith vector + # exclude too small values to avoid numerical discretization + if ni < np.sqrt(np.spacing(np.float64(1))): + # repeat randomization + nr[:, i] = stats.norm.rvs(1.0, size=[m - 1, 1]) + else: + v[:, i] = nr[:, i] / np.sqrt(ni) + break + + x = np.vstack([np.tile(np.sqrt(1.0 - ww**2.0), [m - 1, 1]) * v, + np.atleast_2d(ww)]) + + + # Get the rotation matrix that rotates the data to be centered at mu + d = mu.size + a = np.zeros(d) + a[-1] = 1.0 + a = np.atleast_2d(a) + ab = np.dot(a, mu.T) + alpha = np.arccos(ab) + ii = np.eye(d) + + if np.abs(ab - 1) < 1e-15: + rot = ii + elif np.abs(ab + 1) < 1e-15: + rot = -ii + else: + c = mu - a * ab + c = c / np.linalg.norm(c) + aa = np.dot(a.T, c) - np.dot(c.T, a) + rot = ii + np.sin(alpha)*aa + (np.cos(alpha) - 1.0)*(np.dot(a.T, a) + np.dot(c.T, c)) + + return np.dot(rot.T, x).T + +#------------------------------------------------------------------------------ + +def h5disp(f, quiet=False): + """ + Displays the groups, datasets, and attributes in an h5 file similar to the same command in Matlab + + Parameters + ---------- + f :: h5py file class + quiet :: True/False (False default), determines whether written to console + + Returns + ------- + out :: text output string (optional) + + Example + ------- + >>> import h5py + >>> f = h5py.File('my_file.h5') + >>> out = h5disp(f) + + """ + import h5py + + out = '\n\nHDF5 file: {0}\n----\n'.format(f.filename) + + mylist = [] + def func(name, obj): + if isinstance(obj, h5py.Dataset): + mylist.append(name) + f.visititems(func) # get dataset and group names + + oldparent = '' + tabs = '' + rootGroupUsed = 0 + for item in mylist: + newparent = f[item].parent.name + if newparent == '/': + tabs = '' + else: + tabs = ' ' * (len(f[mylist[0]].name.split('/')) - 1) + + if newparent != oldparent: + if newparent == '/': + if rootGroupUsed == 0: + out = out + '{0}Group \'{1}\'\n'.format(tabs, newparent) + rootGroupUsed = 1 + oldparent = newparent + else: + out = out + '{0}Group \'{1}\'\n'.format(tabs, newparent) + oldparent = newparent + + + out = out + "{0} Dataset \'{1}\': shape={2}\n".format(\ + tabs, f[item].name.split('/')[-1], f[item].shape) + + x = h5py.AttributeManager(f[item]).items() + if len(x) != 0: + out = out + "{0} Attributes:\n".format(tabs) + for a in x: + out = out+ "{0} \'{1}\': {2}\n".format(\ + tabs, a[0], a[1]) + if quiet==False: + print (out) + + return out + +#------------------------------------------------------------------------------ + +def ismember(A, B): + import numpy as np + return [ np.sum(a == B) for a in A ] + +#------------------------------------------------------------------------------ + +def ind2sub( sizes, index, num_indices ): + """ + Map a scalar index of a flat 1D array to the equivalent + d-dimensional index + + Notes + ----- + | 1 4 7 | | 1,1 1,2 1,3 | + | 2 5 8 | --> | 2,1 2,2 2,3 | + | 3 6 9 | | 3,1 3,2 3,3 | + """ + import numpy as np + + denom = num_indices + num_dims = sizes.shape[0] + multi_index = np.empty( ( num_dims ), np.int32 ) + for i in xrange( num_dims - 1, -1, -1 ): + denom /= sizes[i] + multi_index[i] = index / denom + index = index % denom + return multi_index + +#------------------------------------------------------------------------------ + +def sub2ind( sizes, multi_index ): + """ + Map a d-dimensional index to the scalar index of the equivalent flat array + + Notes + ----- + | 1,1 1,2 1,3 | | 1 4 7 | + | 2,1 2,2 2,3 | --> | 2 5 8 | + | 3,1 3,2 3,3 | | 3 6 9 | + """ + import numpy as np + + num_dims = sizes.shape[0] + index = 0 + shift = 1 + for i in np.arange( num_dims ): + index += shift * multi_index[i] + shift *= sizes[i] + return index + +#------------------------------------------------------------------------------ + +def ssign(x): + """ returns the sign for each item in an array + """ + import numpy as np + + y = np.ones(np.shape(x)) + y[x<0] = -1 + return y + +#------------------------------------------------------------------------------ + +def isnegligible(x, factor=10.0): + """ returns the indices of an array that are effectively zero + + Parameters + ---------- + x : numpy array + factor : float + factor over float spacing to consider effectively zero + + Returns + ------- + numpy bool array + """ + import numpy as np + + epsilon = np.spacing(1, dtype=x.dtype) + return np.abs(x) <= factor * epsilon + +#------------------------------------------------------------------------------ + +def fullfile(x): + """ builds full filename from parts + + Parameters + --------- + x : list of strings + path parts, folders, and filename + + Returns + ------- + string + + Notes + ----- + Provides same functionality as Matlab's FULLFILE function + + Example + ------- + >>> import os + >>> print(os.getcwd()) # print the current working directory + >>> x = os.getcwd().split(os.sep) + >>> print(x) # show cwd as a list of parts + >>> cwd_path = fullfile(x) + >>> print(cwd_path) # assemble back together into full path + """ + import os + return os.sep.join(x) + +#------------------------------------------------------------------------------ + +class helper(object): + """ Provides system-specific information to cryspy + """ + def __init__(self): + + import tempfile, os, platform, warnings, inspect + + # get platform-dependent temporary directory + tmp_path = tempfile.gettempdir() + + # Determine the path to cryspy + cryspy_path = os.sep.join(inspect.getfile(inspect.currentframe()).split(os.sep)[0:-2]) + + # Get the bits used by the platform + bits, linkage = platform.architecture() + bits = bits[0:2] + + # determine the operating system platform + plat = platform.system().lower() + + # parse the platform bits and OS + result = [] + extension = '' + if plat == 'windows': + result = 'win' + bits + extension = '.exe' + elif plat == 'darwin' and bits == '64': + result = 'maci' + bits + elif plat == 'darwin' and bits == '32': + result = 'maci' + elif (plat == 'linux' or plat == 'linux2') and bits == '32': + result = 'glnx86' + elif (plat == 'linux' or plat == 'linux2') and bits == '64': + result = 'glnxa64' + else: + warnings.warn('Required c binary not compiled for your system.') + + # Create the path to our platform-dependent binary executables + prgpth = fullfile([cryspy_path, 'c', 'bin', result]) + + # Store this info in our helper object + self.tmpdir = tmp_path + self.arch = result + self.ext = extension + self.prgpth = prgpth diff --git a/cryspy/vis.py b/cryspy/vis.py index 876f9d4..259c042 100755 --- a/cryspy/vis.py +++ b/cryspy/vis.py @@ -5,48 +5,43 @@ """ # TODO: hide all these imports in the classes and methods -from cryspy.xtal import lattvec, miller, ipfgrid, fundzonePF, symm,\ +from cryspy3.xtal import lattvec, miller, ipfgrid, fundzonePF, symm,\ unitcell, interpret_point_group_name -from cryspy import util -import righthand as rh -import cryspy.ebsd as ebsd +from cryspy3.util import stereotrans, revstereotrans, revstereotransstandard, \ + vecarraynorm, vecarraydot, vecarraycross, radians,\ + rationalize, eatrans, reveatransstandard, reveatrans, \ + polar, cart, vecarrayconvert, tic, toc +from cryspy3.ebsd import triverts import numpy as np import numpy.linalg as npla import matplotlib.pyplot as plt import matplotlib as mpl import matplotlib.collections as mplcollections +import matplotlib.tri as mpltriang +#import matplotlib.delaunay as mpltriang #import visvis import inspect class stereoproj(object): ''' stereoproj() - + A stereographic projection represents the planes and the directions of a crystal. A pole figure is a stereographic projection of the planes in the figure. - - References - ---------- - .. [1] Matlab polar plot function. Revision 5.22.4.9, 2007/08/27, Copyright Mathworks Inc 1984-2007 - .. [2] TH Kosel, "Computational Techniques for Stereographic Projection" J Mater Sci 19 (1984) p4106. ''' - - # FIXME: Add a buffer value so the edges of the grid don't cover up the circle around the plot - - def __init__(self, figure=None, subplot=None, - center=[0,0,1], south=[1,0,0], east=[0,1,0], **kwargs): - - import righthand as rh - + + def __init__(self, figure=None, subplot=None, + center=[0,0,1], south=[0,1,0], east=[1,0,0], **kwargs): + if not 'figsize' in kwargs: - kwargs['figsize'] = (rh.jfigw('double column'), rh.jfigw('double column')) - + kwargs['figsize'] = (8, 8) + # Open the figure if figure: self._figure = figure else: self._figure = plt.figure(**kwargs) - + # Create first axis instance if subplot: self._ax = self._figure.add_subplot(subplot) @@ -54,65 +49,60 @@ def __init__(self, figure=None, subplot=None, else: self._ax = self._figure.add_subplot(111) self._subplot = 111 - + # Create the background - plt.axes(self._ax).set_xbound(-1.05, 1.05) - plt.axes(self._ax).set_ybound(-1.05, 1.05) - circ1=plt.Circle((0, 0), 1, facecolor='w', - edgecolor='none', alpha=1, clip_on=False, zorder=0) - self._ax.add_patch(circ1) - circ2=plt.Circle((0, 0), 1, facecolor='none', - edgecolor='k', alpha=1, linewidth=1, clip_on=False, zorder=1000) - self._ax.add_patch(circ2) - + plt.axes(self._ax).set_xbound(-1.05,1.05) + plt.axes(self._ax).set_ybound(-1.05,1.05) + circ=plt.Circle((0,0),1,facecolor='w', + edgecolor='k', linewidth=1, alpha=1, clip_on=False) + self._ax.add_patch(circ) plt.axes(self._ax).set_aspect('equal') plt.axes(self._ax).axison = False - - south = util.vecarrayconvert(south) - east = util.vecarrayconvert(east) - center = util.vecarrayconvert(center) - - if abs(util.vecarraydot(south, east)) < np.sqrt(np.spacing(1)) and \ - abs(util.vecarraydot(east, center)) < np.sqrt(np.spacing(1)) and \ - abs(util.vecarraydot(center, south)) < np.sqrt(np.spacing(1)): - - self._center = center - self._south = south - self._east = east + + south = vecarrayconvert(south) + east = vecarrayconvert(east) + center = vecarrayconvert(center) + + if abs(vecarraydot(south,east)) 1: - hemis[cosal < 0.0] = 'S' + + hemis = np.tile('N',n) + if np.array(n)>1: + hemis[cosal<0.0] = 'S' else: - if cosal < 0.0: - hemis = np.tile('S', n) - - denom = np.sqrt(xproj * xproj + yproj * yproj) - denom[denom==0.0] = 1.0 - cosrho = xproj / denom - sinrho = yproj / denom - cosrho[denom==0.0] = 0.0 - sinrho[denom==0.0] = 0.0 - + if cosal<0.0: + hemis = np.tile('S',n) + + denom = np.sqrt(xproj*xproj+yproj*yproj) + denom[denom==0.0]=1.0 + cosrho = xproj/denom + sinrho = yproj/denom + cosrho[denom==0.0]=0.0 + sinrho[denom==0.0]=0.0 + alpha = np.arccos(cosal) - - - i = -1 + + + i=-1 for almx in alpha: - + # 1/cos and tan have problems near pi/2 - if (abs(almx) - np.pi / 2.0) < np.sqrt(np.spacing(1)): - almx = abs(almx) - np.sqrt(np.spacing(1)) - - - rgc = 1.0 / np.cos(almx) + if (abs(almx)-np.pi/2.0)180.0) or any(alpha<0.0): - print('valid range for grid_verticalgreatcircles \ - is between 0 and 180')# TODO: check into using warnings package + print ('valid range for grid_verticalgreatcircles \ + is between 0 and 180') # Put data in our valid range - alpha = alpha * np.pi / 180.0 - alpha = alpha[alpha > 0.0] - alpha = alpha[alpha < np.pi] - alpha = alpha[alpha!=np.pi / 2.0] - poleclip = np.pi / 2.0 - rh.to_radians(poleclip) + np.spacing(1) - - rho = rh.to_radians(rotation) + alpha = alpha * np.pi/180.0 + alpha = alpha[alpha>0.0] + alpha = alpha[alpha=np.pi/2: rgc = 1.0/np.cos(almx) ogc = np.tan(almx) @@ -863,12 +816,12 @@ def grid_greatcircles(self,degreestep=45.0, v1=v1/npla.norm(v1) v2=np.array([x2-ogc,y2]) v2=v2/npla.norm(v2) - pc=np.arccos(np.dot(v1, v2)) - + pc=np.arccos(np.dot(v1, v2)) + thi=3.0*np.pi/2.0-almx+pc thf=np.pi/2.0+almx-pc ths=(thf-thi)/resolution - + th = np.arange(thi,thf+0.5*ths,ths) x = rgc*np.cos(th)+ogc y = rgc*np.sin(th) @@ -893,7 +846,7 @@ def grid_greatcircles(self,degreestep=45.0, antialiased=antialiased, zorder=zorder, **kwargs) - + plt.axes(plt.gca()).set_xbound(-1.05,1.05) plt.axes(plt.gca()).set_ybound(-1.05,1.05) @@ -925,58 +878,58 @@ def grid_smallcircles(self,degreestep=45.0, **kwargs): plt.subplot(self._subplot) - + # assign relevant defaults from mpl.rcParams - if linewidth is None : + if linewidth is None : linewidth=mpl.rcParams['lines.linewidth'] - if linestyle is None : + if linestyle is None : linestyle=mpl.rcParams['lines.linestyle'] - if marker is None : + if marker is None : marker=mpl.rcParams['lines.marker'] - if color is None : + if color is None : color=mpl.rcParams['lines.color'] - if markersize is None : + if markersize is None : markersize=mpl.rcParams['lines.markersize'] - if dash_capstyle is None : + if dash_capstyle is None : dash_capstyle=mpl.rcParams['lines.dash_capstyle'] - if dash_joinstyle is None : + if dash_joinstyle is None : dash_joinstyle=mpl.rcParams['lines.dash_joinstyle'] - if solid_capstyle is None : + if solid_capstyle is None : solid_capstyle=mpl.rcParams['lines.solid_capstyle'] - if solid_joinstyle is None : + if solid_joinstyle is None : solid_joinstyle=mpl.rcParams['lines.solid_joinstyle'] - if drawstyle is None : + if drawstyle is None : drawstyle='default' - if antialiased is None : + if antialiased is None : antialiased=mpl.rcParams['lines.antialiased'] - - # Create the alpha range + + # Create the alpha range beta = np.arange(mindegrees,maxdegrees+degreestep,degreestep) if any(beta>180.0) or any(beta<0.0): - print('valid range for grid_verticalgreatcircles \ - is between 0 and 180') # TODO: check into using warnings package + print ('valid range for grid_verticalgreatcircles \ + is between 0 and 180') # Put data in our valid range - beta = beta * np.pi/180.0 + beta = beta * np.pi/180.0 beta = beta[beta>0.0] beta = beta[beta=np.pi/2.0: + + if bemx>=np.pi/2: rsc = 1.0/np.tan(bemx) org = 1.0/np.sin(bemx) thi = bemx-np.pi/2.0 thf = 3.0*np.pi/2.0-bemx ths = (thf-thi)/resolution th = -np.arange(thi,thf+0.5*ths,ths) - + x = rsc*np.cos(th) y = -rsc*np.sin(th)+org plt.plot(x*cosrho-y*sinrho,y*cosrho+x*sinrho, @@ -1032,7 +985,7 @@ def grid_smallcircles(self,degreestep=45.0, antialiased=antialiased, zorder=zorder, **kwargs) - + plt.axes(plt.gca()).set_xbound(-1.05,1.05) plt.axes(plt.gca()).set_ybound(-1.05,1.05) @@ -1063,39 +1016,39 @@ def grid_rings(self,degreestep=45.0, **kwargs): plt.subplot(self._subplot) - + # assign relevant defaults from mpl.rcParams - if linewidth is None : + if linewidth is None : linewidth=mpl.rcParams['lines.linewidth'] - if linestyle is None : + if linestyle is None : linestyle=mpl.rcParams['lines.linestyle'] - if marker is None : + if marker is None : marker=mpl.rcParams['lines.marker'] - if color is None : + if color is None : color=mpl.rcParams['lines.color'] - if markersize is None : + if markersize is None : markersize=mpl.rcParams['lines.markersize'] - if dash_capstyle is None : + if dash_capstyle is None : dash_capstyle=mpl.rcParams['lines.dash_capstyle'] - if dash_joinstyle is None : + if dash_joinstyle is None : dash_joinstyle=mpl.rcParams['lines.dash_joinstyle'] - if solid_capstyle is None : + if solid_capstyle is None : solid_capstyle=mpl.rcParams['lines.solid_capstyle'] - if solid_joinstyle is None : + if solid_joinstyle is None : solid_joinstyle=mpl.rcParams['lines.solid_joinstyle'] - if drawstyle is None : + if drawstyle is None : drawstyle='default' - if antialiased is None : + if antialiased is None : antialiased=mpl.rcParams['lines.antialiased'] - - # Create the alpha range + + # Create the alpha range r = np.arange(mindegrees,maxdegrees+degreestep,degreestep) if any(r>90.0) or any(r<0.0): - print('valid range for grid_rings is between 0 and 90') # TODO: check into using warnings package + print ('valid range for grid_rings is between 0 and 90') # Put data in our valid range - r = r * np.pi/180.0 + r = r * np.pi/180.0 r = r[r>0.0] r = r[r1: hemis[cosal<0.0] = 'S' else: if cosal<0.0: hemis = np.tile('S',n) - + denom = np.sqrt(xproj*xproj+yproj*yproj) denom[denom==0.0]=1.0 cosrho = xproj/denom sinrho = yproj/denom cosrho[denom==0.0]=0.0 sinrho[denom==0.0]=0.0 - + alpha = np.arccos(cosal) - - + + i=-1 for almx in alpha: - + # 1/cos and tan have problems near pi/2 if (abs(almx)-np.pi/2.0)>> from cryspy import cio + >>> import cryspy >>> import matplotlib.pyplot as plt - >>> data = cio.loadang('my_file.ang') + >>> data = loadang('my_file.ang') >>> fig = plt.figure() >>> ax = fig.add_subplot(111) >>> col = mplebsdmap(data, data.pq) >>> ax.add_collection(col) >>> plt.axis('equal') >>> plt.show() - ''' - + ''' + # convert the kernfaces and kernverts data to a sequence of xy tuples for # plotting a collection of polygons. tmpa = np.zeros([np.shape(ovdat.f)[1],np.shape(ovdat.f)[0],2]) @@ -2419,17 +2373,19 @@ def mplebsdmap(ovdat, prop): if np.amax(prop)>1: prop = prop/np.amax(prop) + + if np.size(np.shape(prop)) == 1: + ncr = 3 + else: + ncr = 1 - if np.size(np.shape(prop)) == 1: # otherwise, assume RGB triplets - prop = np.tile(prop, [3, 1]).T - - x = 0; del x # prevents redefine bug flag on next line - clr = [tuple(x) for x in prop] - - t = rh.tic() + x = 0; del x # prevents redefine bug flag on next line + clr = [tuple(x) for x in np.tile(prop, [ncr,1]).T] + + t = tic() col = mplcollections.PolyCollection(verts, facecolors=clr, edgecolors=clr) - rh.toc(t) - + toc(t) + return col #------------------------------------------------------------------------------ @@ -2438,79 +2394,78 @@ def mplebsdmap(ovdat, prop): # ''' # EBSDMAP uses the visvis library to interact with opengl for fast and # interactive plotting. -# +# # For publication-quality plots, use of mplebsdmap is recommended. -# +# # BASIC USAGE: -# import ovlib as ov +# import ovlib as ov # data = ov.loadang('my_file.ang') -# ebsdmap(data,data.pq) -# +# ebsdmap(data,data.pq) +# # ''' # h = figure # hax = axes # del figure, axes -# +# # if h == None: # h = visvis.figure() # else: # visvis.figure(h) -# +# # if hax == None: # hax = visvis.gca() # else: # visvis.axes(hax) -# +# # if np.size(np.shape(c)) == 1: # c = np.tile(c,[3,1]).T -# +# # if np.amax(c) > 1: # print 'c values must range between zero and one. \ # Proceeding by normalizing values.' # c = c / np.amax(c) -# -# # To use visvis, we need to break our grid up into triangular -# # elements -# +# +# # To use visvis, we need to break our grid up into triangular +# # elements +# # if np.size(ovdat.cols) == 2: # hexagonal grid -# # FIXME: c2 = np.reshape(np.tile(c, [1,2]), [-1, 3]) should probably work -# c = np.reshape(np.tile(c.T,[12,1]).T, [-1, 3]) # XXX: This must only have worked for one color value??? +# c = np.reshape(np.tile(c.T,[12,1]).T, [-1, 3]) # else: # square grid -# c = np.reshape(np.tile(c.T,[6,1]).T, [-1, 3]) +# c = np.reshape(np.tile(c.T,[6,1]).T, [-1, 3]) # -# if hasattr(ovdat,'tv') and hasattr(ovdat,'vf'): +# if hasattr(ovdat,'tv') and hasattr(ovdat,'vf'): # tv = ovdat.tv -# vf = ovdat.vf +# vf = ovdat.vf # else: # tv, vf = triverts(ovdat.nr, ovdat.v, ovdat.f, ovdat.cols) -# +# # m = visvis.mesh(vertices=tv, faces=vf) # visvis.processing.unwindFaces(m) # m.SetValues(c) -# +# # # Flat painting # visvis.Mesh.shininess.fset(m,0) # visvis.Mesh.diffuse.fset(m,1) # visvis.Mesh.specular.fset(m,0) -# -# h.bgcolor = [1,1,1] +# +# h.bgcolor = [1,1,1] # # hax.cameraType = '2d' # da = hax.daspect # hax.daspect = da[0], -abs(da[1]), da[2] # visvis.axis('off') -# visvis.Axes.SetLimits(hax, rangeX=(np.amin(ovdat.x), np.amax(ovdat.x)), +# visvis.Axes.SetLimits(hax, rangeX=(np.amin(ovdat.x), np.amax(ovdat.x)), # rangeY=(np.amin(ovdat.y), np.amax(ovdat.y))) # -# +# # return h, hax #------------------------------------------------------------------------------ -#def ipfkey(symmetry, resolution=0.5, uc=None, +#def ipfkey(symmetry, resolution=0.5, uc=None, # trans='stereo', figure=None, axes=None): # ''' plot an ipf color key -# +# # Plots an inverse pole figure color key for interpreting ebsd orientation # maps. # @@ -2518,41 +2473,41 @@ def mplebsdmap(ovdat, prop): # ----------- # pointgroup : point group identifier # the point group. default notation is numeric. -# +# # Options # ------- # resolution : float # the approximate angular resolution required -# +# # uc : unit cell class object # a unit cell object containing the unit cell parameters -# +# # trans : str # transformation: 'ea' for equal area; 'stereo' for stereographic -# +# # notation : str # the notation of the point group input. Default is numeric. -# +# # figure : visvis figure handle -# +# # axes : visvis axis handle -# +# # Returns # ------- # h : visvis figure handle -# +# # hax : visvis axis handle # ''' # # h = figure # hax = axes # del figure, axes -# +# # if h == None: # h = visvis.figure() # else: # visvis.figure(h) -# +# # if hax == None: # hax = visvis.gca() # else: @@ -2560,132 +2515,56 @@ def mplebsdmap(ovdat, prop): # # if uc==None: # uc = unitcell() -# -# xproj, yproj, hem, edge = ipfgrid(symmetry.point_group_number, -# resolution=resolution, +# +# xproj, yproj, hem, edge = ipfgrid(symmetry.point_group_number, +# resolution=resolution, # uc=None, trans=trans) -# -# cens, edg, tri, neigh = mpl.tri.Triangulation(xproj, yproj) -# +# +# cens, edg, tri, neigh = mpltriang.delaunay(xproj, yproj) +# # zproj = np.zeros(np.shape(xproj)) # tv2 = np.array([xproj, yproj, zproj]).T # vf2 = np.array(tri, dtype=np.uint32) -# m = visvis.mesh(vertices=tv2, faces=vf2, verticesPerFace=3) # TODO: This was changed without checking if it works, due to depreciation of matplotlib.Delaunay +# m = visvis.mesh(vertices=tv2, faces=vf2, verticesPerFace=3) # #visvis.processing.unwindFaces(m) # # # This next part seems a little ridiculous... we are converting in and out of things perhaps unnecessarily -# if trans=='ea': +# if trans=='ea': # x,y,z = reveatransstandard( (xproj, yproj, hem) ) # elif trans=='stereo': # x,y,z = revstereotransstandard( (xproj, yproj, hem) ) -# +# # mil, dev = miller.from_cartesian( (x, y, z), uc, rationalizevals=False) -# c = ipfcolor(mil, symmetry, uc) +# c = ipfcolor(mil, symmetry, uc) # m.SetValues(c) -# +# # # Flat painting # visvis.Mesh.shininess.fset(m, 0) # visvis.Mesh.diffuse.fset(m, 1) # visvis.Mesh.specular.fset(m, 0) -# -# h.bgcolor = [1,1,1] -# +# +# h.bgcolor = [1,1,1] +# # hax.cameraType = '2d' # da = hax.daspect # hax.daspect = da[0], -abs(da[1]), da[2] # visvis.axis('off') -# visvis.Axes.SetLimits(hax, rangeX=(np.amin(xproj), np.amax(xproj)), +# visvis.Axes.SetLimits(hax, rangeX=(np.amin(xproj), np.amax(xproj)), # rangeY=(np.amin(yproj), np.amax(yproj))) -# +# # return h, hax - -#------------------------------------------------------------------------------ - -def mplipfkey(symmetry, resolution=0.5, uc=None, - trans='stereo', figure=None, axes=None): - ''' plot an ipf color key - - Plots an inverse pole figure color key for interpreting ebsd orientation - maps. - - Parameters - ----------- - pointgroup : point group identifier - the point group. default notation is numeric. - - Options - ------- - resolution : float - the approximate angular resolution required - - uc : unit cell class object - a unit cell object containing the unit cell parameters - - trans : str - transformation: 'ea' for equal area; 'stereo' for stereographic - - notation : str - the notation of the point group input. Default is numeric. - - figure : visvis figure handle - - axes : visvis axis handle - - Returns - ------- - h : visvis figure handle - - hax : visvis axis handle - ''' - - h = figure - hax = axes - del figure, axes - - if h == None: - h = plt.figure() - else: - plt.figure(h) - - if hax == None: - hax = h.add_subplot(1, 1, 1) - else: - plt.axes(hax) - - if uc==None: - uc = unitcell() - - xproj, yproj, hem, edge = ipfgrid(symmetry.point_group_number, - resolution=resolution, - uc=uc, trans=trans) - - triang = mpl.tri.Triangulation(x=xproj, y=yproj) - - if trans=='ea': - x,y,z = util.reveatransstandard( (xproj, yproj, hem) ) - elif trans=='stereo': - x,y,z = util.revstereotransstandard( (xproj, yproj, hem) ) - - mil, dev = miller.from_cartesian( (x, y, z), uc, rationalizevals=False) - c = ipfcolor(mil, symmetry, uc) - c2 = np.split(c, np.shape(c)[0]) - - hax.tripcolor(triang, facecolors=c2) - - return h, hax - #------------------------------------------------------------------------------ def ipfcolor(m, symmetry, uc): """ color by directions in the external reference frame - + Parameters ---------- m : miller object symmetry : symmetry object uc : unit cell object - + Returns ------- c : n x 3 numpy array of floats between 0.0 and 1.0 @@ -2695,69 +2574,69 @@ def ipfcolor(m, symmetry, uc): lcs = symmetry.laueclass() h = m.to_fundzone(lcs, uc) x, y, z = h.to_cartesian(uc) - nrm = 1.0/util.vecarraynorm([x, y, z]) + nrm = 1.0/vecarraynorm([x, y, z]) x = x * nrm y = y * nrm z = z * nrm - q, r, p = rh.polar(x, y, z) + q, r, p = polar(x, y, z) #del r - + #visvis.figure() #x, y, z = cart(q, r, p) #visvis.plot(x,y,z,ms='.', ls='') #visvis.figure() - + # Treat m-3 as a special case if lcs.point_group_number == 28: pm = p > np.pi/4.0 p[pm] = np.pi/2.0 - p[pm] - + # get limits of the fundamental zone of the stereographic projection mintheta, maxtheta, minrho, maxrho = fundzonePF(lcs.point_group_number) - + if inspect.isfunction(maxrho): pmax = maxrho(q) # for m-3 and m-3m else: pmax = maxrho # for everything else - + # TSL colors the upper and lower halves identically for triclinic if lcs.point_group_number==1: q = np.arccos(np.cos(q)) maxtheta = maxtheta * 0.5 - + # normalize rho and theta p = p / (pmax + np.spacing(1)) q = q / (maxtheta + np.spacing(1)) - + scale = [2.6,2.0,2.0]#[2.6, 2.0, 2.0] pwr = [0.8,1.0,1.0]#[0.8, 1.0, 1.0] shift = 0.4#0.4 - + # assign red tmp = np.sqrt(scale[0]) r = tmp * (tmp - np.absolute(q - shift)) * (1.0 - p) r[r > 1.0] = 1.0 - + # assign green tmp = scale[1] * p tmp[tmp>1.0] = 1.0 g = scale[1] * (1.0 - q) * tmp g[g > 1.0] = 1.0 - + # assign blue tmp = scale[2] * p tmp[tmp>1.0] = 1.0 b = scale[2] * q * tmp b[b > 1.0] = 1.0 - + # adjust colors r = r**pwr[0] g = g**pwr[1] b = b**pwr[2] - + # fix out-of-range values r[r < 0] = 0.0 g[g < 0] = 0.0 b[b < 0] = 0.0 - + return np.squeeze(np.array([r, g, b]).T) \ No newline at end of file diff --git a/cryspy/vis.zip b/cryspy/vis.zip deleted file mode 100755 index da83062dabf8b048687c066cd9d02695af7bfdf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14957 zcmZ|0V{k6P)_@z^wr$(kv2EMdj%_D9wr$(ov7NlJ?c9CN`TpFhd#9>r^{npc`88G3 z&ofI=1{4ep2nYxYs5@j^Sx@d&{D&I|D6Rn*2;yJX-O7c*(d)|BCV7)1?bPE3CZ@}p z%1y;uDzd48^InxA`SN&2>Hx>b-9=^*P)Jod!X~~zR+84;^7EAo8bAgV1Xk|ua~Xpq zF*7zW5Pae*bQ=`IATg|?RA}5A^uQwU%VS3vb%5%vR9_n2n{?#X3*6)E0i|9+T-rr7X`A;&r!;6%YNIR zkNCYYdT0=_5Q=6@JfTEOv0Dz6%g-l^yz9~K(hjr%)NHehBqsOvkgj_h4fnDkJ=s=8 z|Ck>kjk_n%e@f@h9=3eXI9ob>R0+#xp1up!3(!-eQLLoAw(R7!7c*2k&NAcs%|Mu?v?7;BGAQhh?f;4d~FmdgCZ*rM@_E6V{ zM{Kw}dl}aKrf~0{%m?KB`(3Y5&I8;`lIV>yurG4RgBNRz?Z}9NHn6~K`LziX(?{&dRe3H;1vq|!@Hz<)K<^I%YsnZRdMFJu?+`g`72Jr`0UN2EhVYy zgUexucpTDfamWcl`=DN|e87??m(K=P7ItFl4?>J`Bo~&F}$^pozQ&7?5SD>pS_2_#wUNGBFX_Y zAwbzo%Wh5+vB%N^I%*i=sdbG1MF!CURUGGG(@j;1q>dQl5vo;+f|%b@e_7k#(w{J_ za|{aYoqEf_1kH^Hvd3wt81vu}0Qys!i}<`}2-BuWHMx?AK}h24b%V4oddDoQTPWJX z@jHlaY%o`rUFuS3?ELX9Kc*iP9>|vhDgYAyfMTW}1CPr=u?qTE_>ylrLZ&g=W!e?O zY&x)2W^R5Whg7zy4wEHYn{d{LZ~z2v2ci+0<+c#0ek%$bZlG-g)kCAu3fOG-StgER z5fs}zu-anqpHei*LT*pVjWHS(yR@SYvB)duKn!_(Q?b$?x+qWEAWH#zrjyp&n2Jai z5nOW3z`r06SI;gaanbYP^d*t+s87HZGq6Df;q=p%1J$bg+;iHNu7&D*-^5M4FW0jk zd2GkYEsZiNe=_Cj;U8Q<)7YhyR|W}@Guon~baj$fs?;>1(P=@BN0?!FTFAfwG%OcE z>)z}5<>HjgpanBL{^nq!09*DXoUPs>caEUiAWZaQo(ra^*P~hwr6usr`}-N4T^a0l zqjE2lP+A+ysP{H7cnes|FdcA*f_bVA!N^}${J1zZKr?L8!ZV1;K^fz!K<6YXz|&!} z=#HQnFmoUo&w(8CR5d0NMQBTDzo_fkV^_-UELHpMq>7e$8jEE(0;hpbGdtKGZ&)1A zukSe)G|hH;tmvBA>kH!Xne=r-^wGS%>dylfLa`Yl(ogp3f3*}EB6?O9pyKdumCkcL z7eqPv?{bmG=zMfh}1IAFS8@GCf{tAr)?BS7R<|sLAxz z!L1TvtC_ejsbUvLInz!S*4Aml$)?HP*hCGdY^AapOKU|#-v|aZ%dr;O%>o6NTMF%> zgk0I^fwf{pP)+IXXzJmvuVC-O@j`;MZZ2=AS>#qM+V9f#A%Z-2?d|C3#iL@=q`pa0 zw+380=A1_?gXe4q2`!y>?m)kU=BhzrhCuE@k=vvdg4{GtlPiHjT-&Kw;-LN9N1*?% zgEFoY@=xWh7-5Jl7V6AD`DUV=2C-Ig-Lo-S)j}K4} zwzX+#Y@^q%F@mpA4^XZODz>(g37Bh9+_gVw$rh!zwb9XYDSyzS)4s|T#m`@=Gz`~4 zE_8GjhY+#zSgT&j#=HX1^70)>wxsj&k|hQ?8rLr2g+4dZlQ>~*OyxujTVNiWMcLPT zc(u#GCm5?2O%6nQ)fd2dIL2R7tff^zrYSM9FwR2ykWuVTMG+zPc-B9J51HdO2M&QK zPM^&FosE@=8;k_gPu(a@%E?A>EQK{KD*?}zKkhDF#bC0>hXz}QmZV1=(KxME45vlQ zyuzszL9teQ@I2=Vi#xomG*%SVz^!c~DksHj)_27%MxRYcp;i2n+uPmu{u^@XoGJCxi1mF;_Vmen!|~dGCd=Wf z$B@h@!*;~He!udUE4G7;^5aPBt~{q z&L~)+TWyJY@`>#U_omgN?yhe1T4A-Y+}ZdJ9@T{&>ifS?PU8YHp9yLMyO({%XTfWi z=>rAnXM8qWd45>T4Sr>wHIPyXMOnj*oReoZNnw5OKyTTczPX?Vax03G^`E{@nK+V7 zCV}V1beXCI*O~pW`L$cXFSd;-4qq?HU(MjL&(1}RBvsNK+!%vODlR?B_JB=J7bot% zo%%XhU(rVW7K2`4YPx4m@ONe}5BVcC#P96!#h^S?FXy)ir*EFx9t0(eWS^Ue4X&6k z%QhGq#=OxShqJ6&Y%PfaV%;z$O+5I|AML##m)x7|lcxQj(nX`OADrZ!xF7c1aPpsv z>34s%?;F`^X4p{%#xWzo=w{svQ_v<@I(Fi*l}k=yrUkMMBQZ|_pXW8J_KIG_#t}A; zUxZY^s|1wlwq(4LS>0i?7%V4asts#xLDXHuxO^vM*}A4*JHLt>wINCDiWamhbV|x) zp$TYdmaQCo99iej*z9g#C}>ib@3|pg&PrBaTgh3!wq5RP6iy8i4v&AQxt;c@C!QQ_ za=)1`Zi$NgG#;~=jd{D|S3Ap{`ncrh7frv>*&tLe|8T##%lEFcy&xfcswO+QuY)+8 zrDp_Y0^uUy?r#R)`|?3xLIpaq2L`&1X}Mc4bmXGqzvXks<-y%!t@Q>OV~7MG zGo-`ZHiNFcpzmox-dd9b4Si~C2T(2pA%K8;ZUA@d)AlzZ>}|^fL1B4-J49I_`JuwwpF+cQD~Hz}=gdldN6SAzivN#0-UA zvm<+Q{Cha&_+V(6Z&8d9`tU^SK^X9NN1@NB($aZWAmmmmT)x&Aqn2F;x;50FEEExt z0woxu+?>3e(O@9#sH}y3M~J(GZZZ`uHVsbq54XTgw1bVKL0O>Gh#IVCJ%9N)}BhqN0lF{nuQvX8!#4GEFq!2tbf*9Wdc0 z=SK9z=q6-4@(v43D|aO5Rte-}bd}65>ImIfm2d7<^pk`90>s5eY6WBGupI|dRZN*wAxbouHHz8I zG)3UzKg-x=XC2Z-bdSV{OYOW=nC3;3u44E_eRcg=_%*og+GY($vv3 ztFBIUO}aMJLApN0ZX~bZDQ#YvTX!$pdhOm>;6%PbuHV0d%&$0N-debMUl0IqmtT8Cmp*rT=y1#je+q=mIlN9g})wtZlXDkBKjHlI>(l=hJTe(m1 z`8@e1?87<_q$Q;m=C?W$u(sGwpXH>?vYOq?OC2!3+YO9+Epxb?c$i*&3P5*hgSfg^ zdt}a1h`!_>Wvl652U2rq9O`T+4vXxHWs?B&;J)#f?n;kDA(z1W%0QXo^@gbIAen-F zoh#sPRxbCHpAY<0z=O_9VoiGPmBsbT^k^1p*Foi& z@9TTnmNY1ORKVartl?%aaaQFs$G6UBE$d-a%5h%W2|v+OmWK{i1bIVIkA26HKowdL zp~T?HnfL;b**7?^e>dW80h@7>v8f?vD+KRSzh*&bBv14(XjGsz>LHX+-N*_QTVt(v z#8!ryo={i*`vh?*Z^)?r8rbk-=?G{fYP)+!E3g8bQV(uTwy6M0b9h(BVw*g6FSlF$ zt}~BHtzOfvk-{L`dO5K&`|?)R6kd>fB-~z4c~LJ()3>fryL%b)THdvdm9|6%Xu%pB zly*l2(W2M?suXk_*gd@s8{hmymOLBOlui$3p+~PT+KOa#&zi%_AgUV74-VL>H$Teq z(%|%{HfVZR8#g@bj~L%fM2+lJq01m5YS6jw)M{4q88m2m4H!2(MvN$Xk748!`pCVn zI|=a4T8zCtxMi0xQZ|JOEa}t$6^{s34(Z|wYXr4byQH0A#Vt`k>MIL+NzNV8)%gZ% z93kIvYP`%{pkJW*|Dcy*V`hZ)Vn&ALJd3Mk*O)18K?=)KxW*#0UBmfw*K3n>64kjA zq4x}mVsfv~-&Cjb+sI;mY+~RfgCXljH_CkBl#oca7`l-eGN z8u29QX?FXW)jX=IAml7a?rNwC3v|w;BEpknUAs%DjG%^I<1{SjrtF|lm+$0XB{p@p znoS@V*9fa#a-&TaeD=XII}o5eyc%m2`v7v%>6~mV)}vQN7|9b7QcC zCBYsmgy5h-M1i_-+(oscj|vETiUK}EN~(=R*>LoNeThLHo52#l$kf`g5K>*B?z~P# zExo>IaZOE6cooOc6qi&E=t6A`PpyGCB=_P_dQ9{6jFip|7kx}HPrZ$S3c3 zrQ-)p`uKEL0h56OjvevR&LlP&O0-G1&^Q2lR}>-(pA*WCS_)mKWQod*hru6D8I5ICR?1+ z_`bfrx^zLg?*hGXANa<3t^)Ax7^eQ)%_H48iFn~Yn2~uN7~Ec;Ix#Niy}&*xeO6-) z`}$N8UE_MMCij%Pp=aSXpJ80t-x;N+me$)*87#AWn33-Li5h_Aby95$iO%d{IUQ&5 z3hUsAxsYS(-eFKk-sml6t#U<>C_xHdR9L&BN+V=xYr2bqM<05a{O=m~0F}IRYvl1)hxqd5R5sdiC@2^7ZlYMfv*q_4%dW z>&GbAR~G+Q-EMl<`S9alIMlLiYqL>HhfNBxp1b$FmLU&TN|9%7xA9IrC$KQx?|yMi)Mo_j<2!LtF63 z!Y&2I6!mk1rhG%{h;!yPU77$#v*zWMRm0lmim_EglR`=!#Ub<*CApv-&!<9S9tS?} zlmafC#hX`J9!HI@qD$P_|n#;11T^}lIl{8vEZ_`+!gi)gFDPH zm)zlpV#84|WWedMxJj`}Q7o%QCqzu)X=rauuMF$~hh^-5yeyJM$HW7ayNA|vYIaKX z_Z=YCI>uxaO?U&-F%#S1NYe5Jrq@>~;+7@8p7q)McPM^OrTZB@I~>{}VY-Zk?*fhW z$sYtwMx0GwbrCd!O>_rMLq{xHW-A#%!}lO^vAJ&p_)Y!Kqeg&fkHtCqQ*{+Rm^-i$ z>hKP9BQeW5qnmJzwvpW%p%6y)_6YR)&pE=k+6yyktXU>OHD2BWy=sQgFzIFU;A7R| zW@zH2etG@lyeRmloE+@}{5o z)SY&nT@Ty;HmRcViuU|2@dr|{Ez3vcC0$gSm{%8iJZLA8#n9^Ve~M;~M+82i^sgKD zqax~w)NF(7q{jC?O@)H0X)j=WOwi%{3I6{o_4RL(DA zI2Z)>2nGXIY+yj|iL8VO$Pg^k2`CV#U`#)AWGWAP9Dn}=f(;%hfep?G>Jg=|`%HpH z?!$ou4*BUu?omp$!k8d(_v-+gu~gJQ_^=Qj4BeD_f{RMk_yWL-$`taXPx=Dj#s|@R zztN5X)#%W7oR5GaIcEif7!c#lfow0PrYa@{(wm4Iqlqq?ImT`@{f4QDg{e^%moKa) zjNgoqi`N1~FRxK%fgnKII+K=DfRm$>i`CU#gP)gnW-zTJyj>S}*ylnlt^5clKlc>7 zK93I{5A@5EPp$a7CwbuoAP%&V?0NIQxT$yB#6)d%PPDh5+YC?v9s5X`&!1fbAVdK} zcODAjffHS$OM@8Xfz10E*Wm?Bt8*oOQ)}Z_~X~aTLUy3Lr6Sy}Cuh%yT&sQQ* zVcWksePvb&Jw*9PiLJuni8d$FjBofL?raMbnY}ekmkOI#eH1TlE*)wv+%3)I5p-1U zTGM0srU^`vv{A1ep7)m0QGlBMQXQ{+wRUIcR8D)>A~HU=A{=C^hH% zv2$=AJ2`EI7*h|?1}guHXil`a&~bYG&Vo5U!sYMhARsf(^t)y3Ixb1QWVQ0;2B7|yW?p$bGA+J7Z$(Nh9-sk7NTlr2d+ zU=$&o(TVKF)qpuw@)QT+bluQpkEAcw5^2F($TeVWeAH6djJzyr24D~{o}#jDN-%d0&_ zt1jIzuc}BLnssl4lpn%=TPts&`*Jd3x3uf7hH<(=1RrNMn3c4mJ3MM?nf26Ha$r`H z@*CFS3(;|Gpm{vK&iOG%BS7kBlJ~(U0UQDiZkehM>k~HGLPoOfSsh>zd|`T>kubSG z^xcm}uw($cefpu;OJxwcjT8iTO{DD`^M4KpT#2=s^4+0Y#D;+wiQr7UM-9i+Jghmt zuQr(wqT3uYApZhq1paC#UN*12o1QX0kl#9!%a93Ntl$k)$+XexXXanbu;0%dHmu~C zquQ>9_nFE+dS&ZsE#I?uG&rHw{)KAQ)N8OHgjc(U&_z;YwnCCO+M!*Mp{Tewe``t! zD(Ba@A!3Vf?j_tU8i5!L=pT-|<6dbw+J{&)E?PRTvejc>Fwpq_(qbY_Kkep`ho(?$ zr0u>hakQSX9&xlh^S|$mw~J_?N^4c=fGYt6ZbJK7#ue{;dyZOL>5pWH3 z>`9VH!8N@FLn=tsza-X7#+(Y=odD>_@O?;FCXkKJq*3NGvOmtsD6Zt32CdckxuFOQ8DB~nJ+Ge$w~=C zQy+1E6cu@=F^?L|=+QjhmIVVmjql;5t%MUE7@AJ576KQ>s856bW2piuyt7S5CG{_$jj@n{3OPX8WS9H5E-5}%|ohyGjeedaS6 zU71kfYK{FE8$69wvD&|5#6pPIY$ZxXxQ3w23su=ejB+&UapKuatcOr~^5sk`!SH9H zBNP~b3m#MywjuOa5a@MOX=#eb!fqSonF}mb)=X?Xh2cE*kGt)iT83(=%@I@_|Ym zf6~znHp5X(uh#x~95<`~dK~!0|9YILuK#+R$F~1@9Qgn9I7R>SI354zaXS9zab7F@ z_JzGD3$A>@vPQr)Q(iDY;`^HBJPm{&+!&)i>jTaoxEW;K;|CkS^is7oCssruxw1^x zaV&vfYVKbEJ8(hzS76P5H#yy{<~qtoD+j4kqI1*~CYCQa>@1y(b&wvX=4m%M*<$0; ztfOw{${%Z=<;SNu-CgD?p3*A$QR1I7BtDi*`nF6~S3k(U{pfFBY+%3B>`+Plr&@ne z`mbvDX7z#B*d05FkKkrB-*g5;_+Q}t=B2o5*by4rrQhjG>;pgrp!;*m;qZR1V8OY6 z)->4Az3Eu6eeS$<8JIT|tw_9MMKF}kYQSP0oc*rYKf#qZ$RwS0q2vVhjdAQ0H~72% z2+>TFfaM9Bp={*_l0gF;{LDaI+sDje>-VQ{Xji(TvdHcoCPxP8&<_Z2al74b>9u4(oCa_VOz2IL6!eEhT zsNt?xzdlL$_;<@c=*@NO64{}11-U{Xebe!TNZZ2+#3OOKNt;djn1{DzR^f0EUvp4%WbFARmP?Etx|c8QAClt624{q$ zDRF4gHTyY}2GMxAKbvS|t%b@t=B<#Ns5$b{&$Why%36Tj!`D20yu3bL!NpyEiMNko zEO2gnglwHLEE{yMxcj`KPmURWvQa7vRH{ru$ZZJWCuZ6r651Dl2|k9Ph1 zC9&+Xh8DPVRY0u>0+5iDxSZb;&$b#5j}f?><$6LAy*02lXShk42xBim+tUfHW2Alj%R7EQxx2Y)ZGh{3yRCks!;*nLN5zfjfAGH(d(Q>n z8$imL7T%&uld#AkxvN2BVV6*q3Ix44qZ1xpC;|}(!fh9iKN_9xdb z*I&L(wHCIk4_knRCto**8#N?K9r;bbGm+!(P@f^i;qz!t$Sr(OntB2Q7BCjyRU{@t z@(?f)P=vu@9zovo;b69qE?2r>&sS%f=rx#$Aou&M3C|!O9qu{+|7(_$EL}in<#Km) z$$a0c{Gn4itHvx&&L5d~AMvuiq;G_*RE$TDxZRSDfg83;1=fG$AEf@-&Ba?-oGHKr z7HEiwoka=9hzp0Pk!)bQaP75w8nQT>Aa~qw|D-D*V2g25Nm4lH2 z*MV@am3E41?U)Rq8}9SEXnnHzob zeZ~Q$RKD`5$OvtJAbh{m4j#%mE7lh|Atx1p7RwCuQZpDh5FUpXr2bU!?$AP3pGYZ; zGj@}E=a>z5$13FtjKSKRf7(}GK*x{CnF1IVM=(>; zWsoBpF8xx$;7<{M8gv~%28|MsT4-FOl$lb7bgm*5jrE&YY}C9g&`wPQN7po!dfp(L z_(e5YTTX>rK!^L3WagYQQHYb}&FyVUK6U;YJ9F5m^@mvFv_VL;)^kB5mIB5o1u@yI z6HieP?F-W}rQ}2;t}(kkzlbi1ZZRaqc0s(he#$UUMmm}I zd!uYh&_V<3?33GX+Yfj26Xk2|YlSzZ%;0w@<|C*ghSNX7uNO@ide)6Dr99`LJiEDB zF=8i^q?=G(Q^nPe)$-~sM=iXGnp8N>CbE3rKnuH_5Q5R~&h3=rdJ)n#6-p~oWrBlt zw*+jJ^J5Q{ji4x8@5*kMINvI5rE}a2Pnh*~s%2-wb5$&xNge4f{T-9&0{d&WvsH_= zkJRY+Wg>MMq@c;9ht-s0q5%>xQmt%^I2|?tl3xNZF8w$YNd0d#ao6OyYN3fOM32lt z=_qx#VGK7YCZD%+^{z_$4L0hhk^>pc#)#@LAWsG(h;-HTy#y95xPu7LMuTXU&)FY>SH1u*nmJU7}Y5Jn`Ldv#QL!mDa&+V#dSzPqo5ZF?8%WT zo!mMkrN~DD84TC(AJWI-Eb<*$!@j~C{d_4^>5j4sL2z> z=?4`+RKYb}zlM%pu4TRKZ773`Q3Q+u;NnIfSKzWuJj27NssdP6IoZ2?c{c-F ztCH}_Ns|RoJpvMBFr+~+Xb>O6(g?Yv(3|A;S4p-30n59@NDBXL~Xk_$_$HSIwSb?=mItqm9zQC4pz+`cuEj9jx zfV(1S02_zvvu!Levd%g01r=(Ba303$;O{JlE9n%gQPIcfV3-ieGX`atZ_7&x{HK3WZ8{JBoRBV8>nv zC9&uT#Id$M7ej@$n#$-B{>%`;ab1q3LimAPyJPtX%?7rCOq!w?oxGUx4TtYmJivsZ z(9t`qu22j3zN_;5fWHdDYP@RAF)>=yNJjJx?2+*m`*gn0A#EZEhGgwaI6ko5$GPTo zhrsLyoJH%&r|EN~Wn7H?Y1#Af+DrNf*fN1A6*>-INn_C-JgoeGA%&>_A%zkst$fRY zoPZRyt^Xqm-0{p0q>|YEAtZlT*Dt+_?)l`$gscq~!EX_iJncXeOH3S5H8zR(_+N~<9{mDTgukw>cg3&w04L;OR9DDVS?NMK4C zanbSMq09Ul6hI=@39&6+n?u_(>;bhlE17P2-8xt=XxH`=741EF>|Ay1Y^^7Qq@{S- z_$R)gcO<0811q}Fmm!xg)7iMnbRQzv2~9|q*oORtTXy< zn0+4X6T2y`B(C#vjU@XGKR#gN!d+q2ymC~jMP(gyEna(e(q|_CdUadgr!BAm@OVW8i>2 z+IQRFbhBl7n$-G_Aud}za3mH=*zJ&^s9O74s&XE4MyR$_X5-T4+#e^XAD%PdROQ`| zDrRxmci5NY0#uS2uD;R|JmxjOQaBYiRQ;!(5XioE_~zHZW@LWR@BGA~<_zR_nA^UkaLRep zDK+XBsAj<~Clb`JzLP}*q~f1uq25PMQ5_bkG>@xl(h-urCTVqlx9|a!)W?jJS~M(k zy_ny=|JMD0s3hKR{~9ons3M0+=i_-_tm-2fSJ>?xWUu4qYh->So*2)hpc9dG^z=hM z__$DH^oT@!)`v1Vf$~z|YP~@uc#b1@U^v{LN@rY$@et%aK1&SQWSf9F2$yopGC+Ax zC#Z-#16cl6b7a#0Z9`ZYS2V2Sb$8XjPOuB20B*wN6y_GZPY+bMKiX=KztqO3%LAiP z@edfdEuJ-p0o6hv8I?BZrMC*m??(O^rSn5LPKJ`uZtcw zyIyz@3E}NcdIVBGA%5EHXt1|}0cYN~S09aO?+NNLIeRywd+)lgg=mH11(t{MdPR#o4!Mk z^p-7a&`&D4CuROZY>wZ}w`&J_-}dv$O5KRWL(L`IUGV2-BCw1>>o>4yH+I}^{Yrv8 z<&XTBV}E$`JfOWl1qBL%32xlkLuMt>LuQY7Y@i??HqJ^-a8M5dW2RyttbN~L4*?R_ za`(hkt3gFF9;2av#DrYHB3j)x4gZ->-F-0nwl??>9F3hNWF_Y?(UW2HgnQYJz& zsKr`)+li*>^MClbL~T)BkWA+0^^VNQqljRbY;tW%#oLti`1i^f<$KUElY6P+!~^)X*4tM!K! z8E`&-cVluYuZFXDTcByXWuMHP`(R5rLzoY7O3;QsMo<9avBA&NN~-MYYQ znS=@mG6hh=30<+Fe2zl^eNiy?0{N$h$~MTy+)Had&<>u;7Rqc!4HsptBHW>`V;zv2 zlExn9-t(j=XmpGtCnZ-d$;i;|dh8Ovxgo_x3#rG44w5OCg+F|ok+68?RPkip12Eu= z=~&?j$Wz8jQ6IY#p124j>hh;zJPzJeV)|7-#bvPNdvs(#AngEg@k!4S??_@U1)R(8 z;Nv#=jy1;c{7{fSLt1qY(gZVaEWg35pl<-fAHVWsHdZq)gy4BJr!Y z<8(^6W_Vq^b}IbUZ=t4WQAey`ssq@fe+U?T(eMro${i8EVo>zBucT(>MaAeI3*vpR zAhjFG0-Ujtj??u5+zI4w0b=yKb34k!hler>Y|lWP11sZph+kw^rYJi9V?`&VdhR{a zT4x>e8>~NCCS^bMnYdN<{R8|d`0W5UQj#5fP@@R&D=t7{+hq$&u}H6EEXJcELzCoP zgBeY@dK$f$x3hpAiEv>jAEfkyu+uhO6mzk6+6+W9f!vZ+bo{uiH?=J zQTSwU>7J;-*GMjIkZsp=D4!b|G;0g8XJ1-?)v7wUrGai)b8zIRu{sXg6~$zAtCBeb zwL>=y3!Buj!BXBs0mT9TtA=gMK@?B$M6KcOoA7+FO(Lu{$DnPEJMhLr6_8jDp?Wo= zSkE_#*bc*UTy-?2`Cu-(ptd_xgQuE_JVlY*(&B*|PO-Sxw>ot`Rq8hh!okPk z#Rp8lq?i@n)t9CC=HJ=>NUDI4ie}$%?3`JpkQ7~$R0#W`;rht~;U484{u6ev@)dvn z9~)dy4|Q{TKO&V>&9%zmQBCqbM4MEp(2edRDA zQ`an+rKU+Lut{stsWVAc$qUSpltiHUsAdPC&L~9{ZDN%WU6oSx=;0K4(h0Pvv!-m@ zv5MT8r>Y~<%{8KM3f(mu`7cy8K55OO8e5a49=!;MY-aJu5oKhfb8IBWW=9yq`ff0- znp;G6aPY`*Egu(i5?DH+R4o`gW>z+UfC_)nYGq|(&!yRNGMoU`j<@>h%6_pZPnt{H zh-l?Iuu#@l?#Nq-ZVjmya0#T81hvbC!G!LYIwNG3_t#e zhWT|>CUa2zSsMJP1ohsN1YS4OwD71y-u|O9+Dj6^D}o>DVhiYT{6_dQN&Y8dhQoxX zJqoaPnO{4;21?h%Duu4Q)H4=`$Te7?8a(MmL%&7;$JwO*AixLTqI_&BZwhzr27EoP zw?8CTV(uDYzhMv74h_WP>A@xTwsaz`4xGJl#9+?khy3ii)i~#2|edQFiuo^fmDg)zt-cuMvi6V2ZSL21DNnH#imT{yaR;&$gu+ z(d0ZbW(bd_VT(GdzwjQ_597uzsJB6jb~ws>Kt%Ge3f760P0)SKZI# z5uyz4A|McrT@Mc2nRz1^d3aiPH=4v;Cs^@`v>xW``h+VF78Z2}`R)*%c@yL+=hj}0 zyQ=!+@)o6dw5hSqER!iet7AT1jR>hnW|c3&h9%#m&se+g`?uJ7pa+z)kwbt|JKkud z`lvC-_vA=CQ~wVq=3~qr^>=+J!I|%`1P09HyVdqXlyAfY&#*0de=M^0Nt;J*%0ouR zy&q?>d?}D{^bo579tY$*hntlF9|GRJUfe|vH1Fm!#>jto34@dbdECgi3NzVWc zUDH2(W(KAiX?f@s8>PzXmN_jvJ-~ccUS7aIeP~R303GbKwl{=LNK1&u_A6`bQ17Ds zjE4OZxDR9;_bpV0fB_Uox 32: - - print('Invalid point group number. Returning 32.') # TODO: check into using warnings package + print ('Invalid point group number. Returning 32.') laue = 32 if pgid <= 29: @@ -167,16 +161,16 @@ def laueclass(pointgroup, notation='international'): def ctf_laue_from_laue_group(laueclass): ''' channel text file laue group interpretation - + Converts between the identification scheme for Laue groups used in ovlib (which is the point group corresponding to the Laue class) to the scheme used in Channel Text Files (*.ctf). - + Parameters ---------- laueclass : int Point group number corresponding to the laue class of the phase. - + Returns ------- ctflaue : int @@ -217,33 +211,33 @@ def ctf_laue_from_laue_group(laueclass): def rotationelements(point_group_name, notation='international'): """ Function for generating rotational symmetries - + Parameters ---------- point_group_name : str The name of the point group. - + notation : string (optional) Name of convention used: {'schoenflies', 'geo', 'international', 'tsl'} Default: 'international' - + Returns ------- rotsymm : quaternion class The set of rotational symmetries. - + Notes ----- As the name suggests, this function returns only the rotational symmetry elements and NOT the full symmetry, which may include rotoinversions. Thus, this should be used carefully and only applied to orientations. Many symmetry-related operations on vectors or miller indices should use the - full set of symmetry operations. + full set of symmetry operations. """ import numpy as np import numpy.linalg as npla - import righthand as rh - + import cryspy.util as util + pge = pointgroupelements(point_group_name, notation) n = pge.numel() a = pge.to_array() @@ -253,8 +247,8 @@ def rotationelements(point_group_name, notation='international'): for i in range(0,n): determinant[i] = npla.det(a[i,:].reshape(3,3)) - determinant = rh.sigdec(determinant, 1) - + determinant = util.sigdec(determinant, 1) + if np.shape(determinant)[0] > 1: rotsymm = pge[np.squeeze(determinant == 1)] else: @@ -266,7 +260,7 @@ def rotationelements(point_group_name, notation='international'): def interpret_point_group_number(point_group_number, notation='international'): """ convert point group number to point group name - + Parses a point group number and converts it to the point group name in the desired notation. @@ -438,7 +432,7 @@ def interpret_point_group_number(point_group_number, notation='international'): def interpret_point_group_name(point_group_name, notation='international'): ''' convert point group name to number - + Parses a point group name and converts it to the point group number. Parameters @@ -625,10 +619,10 @@ def interpret_point_group_name(point_group_name, notation='international'): def pointgroupelements(point_group_name, notation='international'): ''' generate point group elements - - Function for generating point group symmetry elements containing + + Function for generating point group symmetry elements containing subfunctions for creating the point group symmetries. - + Parameters ---------- point_group_name : {str, int} @@ -636,8 +630,8 @@ def pointgroupelements(point_group_name, notation='international'): notation : str Name of convention used: {'schoenflies', 'geo', 'international', 'tsl'} - Default: 'international' - + Default: 'international' + Returns ------- pge : rmat class @@ -646,7 +640,7 @@ def pointgroupelements(point_group_name, notation='international'): Notes ----- This is a Python translation of the codes from [1]_. - + References ---------- .. [1] M. De Graef, "Introduction to Conventional Transmission Electron @@ -655,7 +649,7 @@ def pointgroupelements(point_group_name, notation='international'): def make_generator_string(point_group_id_number): """ - + Notes: - Need to check that point groups 39 and 90 are correct. - The matrices produced may be incorrect for space groups 115-120, @@ -709,7 +703,7 @@ def make_generator_string(point_group_id_number): def interpret_generator_string(generator_string): import numpy as np - + n = len(generator_string) generator_matrices = np.zeros([n,9]) for i in range(0,n): @@ -770,16 +764,16 @@ def generate_point_group_elements(generator_matrices): I have some concerns that the exact matrices produced in the present code may, however, not all be correct. See "notes" in make_generator_string. - + Parameters ---------- generator_matrices : numpy array - + Returns ------- - + ''' - import righthand as rh + import cryspy.util as util import numpy as np n1 = 0 @@ -794,7 +788,7 @@ def generate_point_group_elements(generator_matrices): generator_matrices = np.concatenate((generator_matrices, tmp.reshape(1,9)), axis=0) - generator_matrices, loc, tmp = rh.uniquerows(generator_matrices) + generator_matrices, loc, tmp = util.uniquerows(generator_matrices) n1 = n2 n2 = generator_matrices.shape[0]-1 @@ -863,8 +857,8 @@ def __init__(self, x=0, y=0, z=0): self.z = util.vecarrayconvert(z) else: - print("lattvec construction error: check that the lengths of u,"\ - " v, and w are all the same.") # TODO: check into using warnings package + print ("lattvec construction error: check that the lengths of u,"\ + " v, and w are all the same.") return None #------------------------------------------------------------------------------- @@ -878,7 +872,7 @@ def rotate(self, q2): ''' import numpy as np import cryspy.rot as rot - + if isinstance(q2, rot.quat): t2 = q2.a * q2.b t3 = q2.a * q2.c @@ -909,7 +903,7 @@ def to_cartesian(self, unit_cell): multiply by direct structure matrix to get cartesian vector ''' if isinstance(unit_cell, unitcell): - + d = unit_cell.d vx = d[0, 0] * self.x + d[0, 1] * self.y + d[0, 2] * self.z vy = d[1, 0] * self.x + d[1, 1] * self.y + d[1, 2] * self.z @@ -925,7 +919,7 @@ def from_cartesian(cls, arg, unit_cell, maxval=9): multiply by inverse transpose direct structure matrix ''' import cryspy.util as util - + if isinstance(unit_cell, unitcell): x = arg[0] y = arg[1] @@ -968,7 +962,7 @@ def angle(self, site2, unit_cell, origin): ''' import cryspy.util as util import numpy as np - + if isinstance(site2, lattsite) and isinstance(origin, lattsite) and \ isinstance(unit_cell, unitcell): @@ -1009,7 +1003,7 @@ def __repr__(self): import numpy as np qlist=np.array([self.x,self.y,self.z]).T - print('\n Lattice site coordinates [x y z]') # TODO: check into using warnings package + print ('\n Lattice site coordinates [x y z]') np.set_printoptions(precision=3,suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1064,8 +1058,8 @@ def __init__(self, u=0, v=0, w=0): #self.w_ = self.w * nrm else: - print("lattvec construction error: check that the lengths of u,"\ - " v, and w are all the same.") # TODO: check into using warnings package + print ("lattvec construction error: check that the lengths of u,"\ + " v, and w are all the same.") return None #------------------------------------------------------------------------------- @@ -1100,39 +1094,23 @@ def dot(self, q2): #------------------------------------------------------------------------------- - def rotate(self, r): + def rotate(self, q2): ''' rotate lattice vector using quaternion rotation object - - Parameters - ---------- - q : quat object - object containing quaternions or rotation matrices - - Returns - ------- - v : lattvec object - rotated lattice vector - - Examples - -------- - >>> v = lattvec(1,0,4) - >>> s = pointgroupelements('m-3m') - >>> ms = v.rotate(s) ''' - from cryspy.rot import quat, rmat + from cryspy.rot import quat import numpy as np - if isinstance(r, quat): - t2 = r.a * r.b - t3 = r.a * r.c - t4 = r.a * r.d - t5 = -r.b * r.b - t6 = r.b * r.c - t7 = r.b * r.d - t8 = -r.c * r.c - t9 = r.c * r.d - t10 = -r.d * r.d + if isinstance(q2, quat): + t2 = q2.a * q2.b + t3 = q2.a * q2.c + t4 = q2.a * q2.d + t5 = -q2.b * q2.b + t6 = q2.b * q2.c + t7 = q2.b * q2.d + t8 = -q2.c * q2.c + t9 = q2.c * q2.d + t10 = -q2.d * q2.d v1new = np.array([2.0 * ( (t8 + t10) * self.u + (t6 - t4) * self.v + @@ -1145,14 +1123,6 @@ def rotate(self, r): (t5 + t8) * self.w ) + self.w]) return lattvec(u=v1new, v=v2new, w=v3new) - - if isinstance(r, rmat): - - v1new = self.u * r.g11 + self.v * r.g21 + self.w * r.g31 - v2new = self.u * r.g12 + self.v * r.g22 + self.w * r.g32 - v3new = self.u * r.g13 + self.v * r.g23 + self.w * r.g33 - - return lattvec(u=v1new, v=v2new, w=v3new) #------------------------------------------------------------------------------- @@ -1162,13 +1132,13 @@ def to_cartesian(self, unit_cell): ''' import cryspy.util as util if isinstance(unit_cell, unitcell): - + d = unit_cell.d vx = d[0, 0] * self.u + d[0, 1] * self.v + d[0, 2] * self.w vy = d[1, 0] * self.u + d[1, 1] * self.v + d[1, 2] * self.w vz = d[2, 0] * self.u + d[2, 1] * self.v + d[2, 2] * self.w nrm = 1.0 / util.vecarraynorm([vx, vy, vz]) - + return [vx, vy, vz] * nrm #------------------------------------------------------------------------------- @@ -1200,7 +1170,7 @@ def length(self, unit_cell): import cryspy.util as util import numpy as np if isinstance(unit_cell, unitcell): - + m = unit_cell.m return np.sqrt(util.xtaldot(p1=self.u, p2=self.v, p3=self.w, g11=m[0, 0], g12=m[0, 1], g13=m[0, 2], @@ -1228,120 +1198,6 @@ def angle(self, vec2, unit_cell): return np.arccos(nrm * pgq) -# ---------------------------------------------------------------------------- - -# def to_fundzone(self, symmetry, unit_cell, fundzone=None): -# TODO: Copied from miller class but not yet translated to lattvec -# ''' put miller object into the fundamental zone -# -# Parameters -# ---------- -# symmetry : symm object -# unit_cell : unitcell object -# fundzone : quat object (optional) -# Quaternion describing rotation required to put the Miller indices -# into the desired fundamental zone. Defaults to identity quaternion. -# -# Returns -# ------- -# mf : miller object -# miller indices projected into the fundamental zone for the given -# symmetry -# -# Examples -# -------- -# >>> import numpy as np -# >>> from cryspy.util import stereoproj -# >>> pgn = 'm-3m' -# >>> s = symm(pgn) -# >>> pointgroupnumber = interpret_point_group_name(pgn) -# >>> maxval = 49 -# >>> numvals = 500 -# >>> h = maxval * np.random.rand(numvals) - maxval / 2.0 -# >>> k = maxval * np.random.rand(numvals) - maxval / 2.0 -# >>> l = maxval * np.random.rand(numvals) - maxval / 2.0 -# >>> m = miller(h, k, l) # create miller -# >>> uc = unitcell() -# >>> m2 = m.to_fundzone(s, uc) -# >>> pf = stereoproj() -# >>> pf.add_miller(m, uc, uppermarkerfacecolor='r', -# ... lowermarkerfacecolor='w', -# ... uppermarkeredgecolor='r', -# ... lowermarkeredgecolor='r') -# >>> pf.add_miller(m2, uc, uppermarkerfacecolor='b', -# ... lowermarkerfacecolor='w', -# ... uppermarkeredgecolor='b', -# ... lowermarkeredgecolor='b') -# ''' -# import inspect -# import cryspy.rot as rot -# import cryspy.util as util -# import righthand as rh -# import numpy as np -# -# if fundzone==None: -# fundzone = rot.quat() -# -# ms = self.symmetrize(symmetry) -# -# # Consolidate symmetrized list into a single array of cartesian vectors -# x = np.zeros([self.size, np.size(ms)]) -# y = np.zeros([self.size, np.size(ms)]) -# z = np.zeros([self.size, np.size(ms)]) -# k = 0 -# for item in ms: -# tmp = item.to_cartesian(unit_cell) -# x[0:self.size, k] = tmp[0] -# y[0:self.size, k] = tmp[1] -# z[0:self.size, k] = tmp[2] -# k += 1 -# -# # Normalize cartesian vectors -# nrm = 1.0 / util.vecarraynorm([x, y, z]) -# x = x * nrm -# y = y * nrm -# z = z * nrm -# -# # Convert to spherical coordinates -# theta, r, rho = rh.polar(x, y, z) -# -# # get limits of the fundamental zone of the stereographic projection -# mintheta, maxtheta, minrho, maxrho = fundzonePF( -# symmetry.point_group_number) -# -# # handle theta cases on edges of fundamental zone -# cutoff = np.sqrt(np.spacing(1)) -# loc = np.absolute(theta - mintheta) < cutoff -# theta[loc] = mintheta -# loc = np.absolute(theta - maxtheta) < cutoff -# theta[loc] = maxtheta -# -# if inspect.isfunction(maxrho): -# mxr = maxrho(theta) -# loc = np.absolute(rho - minrho) < cutoff -# rho[loc] = minrho -# loc = np.absolute(rho - mxr) < cutoff -# rho[loc] = mxr[loc] -# crt = np.array(theta <= maxtheta) * np.array(rho <= mxr) * \ -# np.array(theta >= mintheta) * np.array(rho >= minrho) -# else: -# crt = np.array(theta <= maxtheta) * np.array(rho <= maxrho) * \ -# np.array(theta >= mintheta) * np.array(rho >= minrho) -# loc = np.argmax(crt, axis=1) -# -# # extract the symmetrically-equivalent miller indices in the fundamental zone -# h = np.zeros(self.size) -# k = np.zeros(self.size) -# l = np.zeros(self.size) -# -# for i in range(0, np.size(loc)): -# h[i] = np.squeeze(ms[loc[i]].h)[i] -# k[i] = np.squeeze(ms[loc[i]].k)[i] -# l[i] = np.squeeze(ms[loc[i]].l)[i] -# -# # create a new miller object and rotate it into the desired zone -# return lattvec(x, y, z).rotate(fundzone) - #------------------------------------------------------------------------------- def __sub__(self,vec2): @@ -1362,7 +1218,7 @@ def __repr__(self): import numpy as np qlist=np.array([self.u,self.v,self.w]).T - print('\n Lattice direction vectors [u v w]') + print ('\n Lattice direction vectors [u v w]') np.set_printoptions(precision=3,suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1440,8 +1296,8 @@ def __init__(self, h=0, k=0, l=1): self.size = np.size(h) else: - print("lattvec construction error: check that the lengths of u,"\ - " v, and w are all the same.") # TODO: check into using warnings package + print ("lattvec construction error: check that the lengths of u,"\ + " v, and w are all the same.") return None #------------------------------------------------------------------------------- @@ -1552,7 +1408,7 @@ def from_cartesian(cls, arg, unit_cell, maxval=9, rationalizevals=True): import numpy as np import cryspy.util as util if isinstance(unit_cell,unitcell): - + x = arg[0] y = arg[1] z = arg[2] @@ -1595,10 +1451,10 @@ def angle(self, plane2, unit_cell): -------- >>> plane1.angle(plane2, my_unit_cell) #TODO: Fix example ''' - + import cryspy.util as util import numpy as np - + if isinstance(unit_cell, unitcell) and isinstance(plane2, miller): m = unit_cell.minv @@ -1644,7 +1500,6 @@ def cross(self, plane2): >>> common_dir = my_first_plane.cross( my_second_plane ) #TODO: Fix example ''' import cryspy.util as util - if isinstance(plane2, miller): u, v, w = util.vecarraycross( [self.h, self.k, self.l], @@ -1670,13 +1525,13 @@ def rotate(self, r): Examples -------- - >>> m = miller(1, 0, 4) + >>> m = miller(1,0,4) >>> s = pointgroupelements('m-3m') - >>> ms = m.rotate(s) + >>> ms= m * s ''' from cryspy.rot import quat, rmat import numpy as np - + if isinstance(r, quat): t2 = r.a * r.b t3 = r.a * r.c @@ -1803,7 +1658,6 @@ def to_fundzone(self, symmetry, unit_cell, fundzone=None): import inspect import cryspy.rot as rot import cryspy.util as util - import righthand as rh import numpy as np if fundzone==None: @@ -1830,7 +1684,7 @@ def to_fundzone(self, symmetry, unit_cell, fundzone=None): z = z * nrm # Convert to spherical coordinates - theta, r, rho = rh.polar(x, y, z) + theta, r, rho = util.polar(x, y, z) # get limits of the fundamental zone of the stereographic projection mintheta, maxtheta, minrho, maxrho = fundzonePF( @@ -1860,7 +1714,7 @@ def to_fundzone(self, symmetry, unit_cell, fundzone=None): h = np.zeros(self.size) k = np.zeros(self.size) l = np.zeros(self.size) - + for i in range(0, np.size(loc)): h[i] = np.squeeze(ms[loc[i]].h)[i] k[i] = np.squeeze(ms[loc[i]].k)[i] @@ -1893,7 +1747,7 @@ def __repr__(self): import numpy as np qlist=np.array(np.squeeze([self.h, self.k, self.l])).T - print('\n Miller indices (h k l)') # TODO: check into using warnings package + print ('\n Miller indices (h k l)') np.set_printoptions(precision=3, suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1929,7 +1783,7 @@ def __init__(self, a=1.0, b=1.0, c=1.0, # always type g11=..., g12=... etc. import numpy as np import numpy.linalg as npla - import righthand as rh + import cryspy.util as util # check that the shapes of all are the same if np.shape( a)==np.shape( b)==np.shape( c)==\ @@ -1942,9 +1796,9 @@ def __init__(self, a=1.0, b=1.0, c=1.0, self.a = np.float64( np.squeeze( np.array( list( (a,) )))) self.b = np.float64( np.squeeze( np.array( list( (b,) )))) self.c = np.float64( np.squeeze( np.array( list( (c,) )))) - self.alpha = rh.to_radians(np.float64( np.squeeze( np.array( list( (alpha,) ))))) - self.beta = rh.to_radians(np.float64( np.squeeze( np.array( list( (beta,) ))))) - self.gamma = rh.to_radians(np.float64( np.squeeze( np.array( list( (gamma,) ))))) + self.alpha = util.radians(np.float64( np.squeeze( np.array( list( (alpha,) ))))) + self.beta = util.radians(np.float64( np.squeeze( np.array( list( (beta,) ))))) + self.gamma = util.radians(np.float64( np.squeeze( np.array( list( (gamma,) ))))) # calculate the metric matrix and its inverse ca = np.cos(self.alpha) @@ -1980,7 +1834,7 @@ def __repr__(self): import numpy as np qlist=np.array([self.a,self.b,self.c,self.alpha,self.beta,self.gamma]).T - print('\n Unit cell a, b, c, alpha, beta, gamma') # TODO: check into using warnings package + print ('\n Unit cell a, b, c, alpha, beta, gamma') np.set_printoptions(precision=3,suppress=True) if np.shape(qlist)[0]==3: return ' '.join(str(x) for x in qlist) @@ -1991,27 +1845,27 @@ def __repr__(self): class symm(object): """symmetry object - + Wraps up the symmetry elements for a given point group into a single object. Pure rotations are described in quaternion form while the complete set of symmetry operations (i.e., including improper rotations) is given as rotation matrices. - + Parameters ---------- pointgroup : str point group of the crystal notation : {'international', 'schoenflies', 'geo', 'tsl', 'numeric'} Default: 'international' - + Notes ----- - No .size method is provided for this class because the number of symmetry elements is different from the number of rotation operations. Both of these parameters are stored within the symm object and have their own - .size methods. + .size methods. """ - + def __init__(self, pointgroup='1', notation='international'): import cryspy.rot as rot @@ -2022,7 +1876,7 @@ def __init__(self, pointgroup='1', notation='international'): self.notation = 'international' else: self.point_group_name = str(pointgroup) - + # Use string arguments to construct symm object attributes self.point_group_number = interpret_point_group_name( self.point_group_name, @@ -2055,39 +1909,32 @@ def __init__(self, quaternions=None, pointgroupnumbers=None, reference=None): import cryspy.rot as rot import cryspy.xtal as xtal - import cryspy.util as util import numpy as np import warnings - if reference is None: + if reference==None: reference = rot.quat() - if quaternions is None: + if quaternions==None: quaternions = rot.quat() - if pointgroupnumbers is None: - pointgroupnumbers = np.tile(1, quaternions.size) - - pointgroupnumbers = util.vecarrayconvert(pointgroupnumbers) + if pointgroupnumbers==None: + pointgroupnumbers = 1 if isinstance(quaternions, rot.quat): self.rotations = quaternions else: self.rotations = rot.quat() - loc = np.where(np.logical_or(pointgroupnumbers > 32, pointgroupnumbers < 1))[0] - if loc.size != 0: - pointgroupnumbers[loc] = 1 - #FIXME: make a warning warnings.warn() - print('\nWARNING: Invalid point group numbers at:\n{0}'.format(loc)) + loc = np.where(np.logical_or(pointgroupnumbers > 32, pointgroupnumbers < 1)) + pointgroupnumbers[loc] = 1 + warnings.warn('Invalid point group numbers at:\n{0}'.format(loc)) self.symmetry = pointgroupnumbers if isinstance(reference, rot.quat): self.reference = reference - else: - warnings.warn('reference needs to be quaternion') - + self.size = self.rotations.size # Add two parameters to the orientation object. One is a list of @@ -2100,18 +1947,17 @@ def __init__(self, quaternions=None, for item in u: cs_set = xtal.symm(item, 'numeric') cs.append(cs_set) - cslocs.append(np.where(self.symmetry==item)[0]) + cslocs = np.where(self.symmetry==item) self.cs = cs self.cslocs = cslocs - def __getitem__(self, index): - + def __getitem__(self,index): return orientation(quaternions = self.rotations[index], pointgroupnumbers = self.symmetry[index], - reference = self.reference) + reference = self.reference()) def __repr__(self): - + import numpy as np ar= self.reference.a[0] @@ -2121,107 +1967,48 @@ def __repr__(self): repstr = '{0:^45s}\n'.format('orientation object') repstr += '{0:-^45s}\n'.format('----') - repstr += ' {0:^7s}{1:^7s}{2:^7s}{3:^7s} # {4: 6.0f}\n' if np.shape(self.rotations.a)[0] > 15: - + for i in np.arange(0, 5): - repstr += fmtstr.format(self.rotations.a[i], - self.rotations.b[i], - self.rotations.c[i], - self.rotations.d[i], + repstr += fmtstr.format(self.rotations.a[i], + self.rotations.b[i], + self.rotations.c[i], + self.rotations.d[i], self.symmetry[i]) for i in np.arange(0, 3): repstr += ' {0:^7s}{0:^7s}{0:^7s}{0:^7s} \n'.format('.') n = np.shape(self.reference.a)[0]-1 for i in np.arange(n-5, n): - repstr += fmtstr.format(self.rotations.a[i], - self.rotations.b[i], - self.rotations.c[i], - self.rotations.d[i], - self.symmetry[i]) + repstr += fmtstr.format(self.rotations.a[i], + self.rotations.b[i], + self.rotations.c[i], + self.rotations.d[i], + self.symmetry[i]) else: + for i in np.arange(0, self.size): - repstr += fmtstr.format(self.rotations.a[i], - self.rotations.b[i], - self.rotations.c[i], - self.rotations.d[i], + repstr += fmtstr.format(self.rotations.a[i], + self.rotations.b[i], + self.rotations.c[i], + self.rotations.d[i], self.symmetry[i]) - - repstr += '{0:-^45s}\n'.format('----') + + repstr += '{0:-^45s}\n'.format('----') namestr = 'reference q = ' namestr += '<{0: >7.3f}{1: >7.3f}{2: >7.3f}{3: >7.3f} >'.format(ar, br, cr, dr) - repstr += '{0: Date: Mon, 11 Aug 2025 07:22:31 -0400 Subject: [PATCH 3/4] Update licensing --- LICENSE.md | 674 ++++++++++++++++++++++++++++++++++++++++ LICENSE.txt | 14 - README.txt => README.md | 0 docs/conf.py | 21 +- setup.py | 8 +- 5 files changed, 688 insertions(+), 29 deletions(-) create mode 100644 LICENSE.md delete mode 100755 LICENSE.txt rename README.txt => README.md (100%) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE.md @@ -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 +. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100755 index 1d137ec..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,14 +0,0 @@ -''' -Portions of this package written after August 2015 were produced by Eric Payton as part of the -in-house research of the Metallic Materials and Processes Research Team, Metals Branch, -Structural Materials Division, Materials and Manufacturing Directorate, Air Force Research Laboratory. - -As a work product prepared by an employee of the United States government during the -course of that employee's official duties, those portions of this work are not entitled to domestic -copyright protection according to section 105 of the Copyright Act, and is thus part of the public domain. - -Portions of this package written prior to August 2015 are (C) Eric Payton. - -Portions of this package included from MTEX are (C) Ralf Hielscher and Florian Bachmann and are -covered by GPL2 license. -''' \ No newline at end of file diff --git a/README.txt b/README.md similarity index 100% rename from README.txt rename to README.md diff --git a/docs/conf.py b/docs/conf.py index 67da5d2..d8c0fc9 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# ovlib documentation build configuration file, created by +# cryspy documentation build configuration file, created by # sphinx-quickstart on Tue Mar 26 18:32:41 2013. # # This file is execfile()d with the current directory set to its containing dir. @@ -40,9 +40,8 @@ master_doc = 'index' # General information about the project. -project = u'ovlib' -copyright = u'2013, Eric Payton, Federal Institute for Materials Research \ -and Testing, Berlin' +project = u'cryspy' +copyright = u'2013-2025 Eric Payton, University of Cincinnati' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -165,7 +164,7 @@ #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'ovlibdoc' +htmlhelp_basename = 'cryspydoc' # -- Options for LaTeX output -------------------------------------------------- @@ -184,7 +183,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'ovlib.tex', u'ovlib Documentation', + ('index', 'cryspy.tex', u'ovlib Documentation', u'Author', 'manual'), ] @@ -214,7 +213,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'ovlib', u'ovlib Documentation', + ('index', 'cryspy', u'cryspy Documentation', [u'Author'], 1) ] @@ -228,8 +227,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'ovlib', u'ovlib Documentation', - u'Author', 'ovlib', 'One line description of project.', + ('index', 'cryspy', u'cryspy Documentation', + u'Author', 'cryspy', 'One line description of project.', 'Miscellaneous'), ] @@ -246,10 +245,10 @@ # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -epub_title = u'ovlib' +epub_title = u'cryspy' epub_author = u'Author' epub_publisher = u'Author' -epub_copyright = u'2013, Author' +epub_copyright = u'2013-2025, Author' # The language of the text. It defaults to the language option # or en if the language is not set. diff --git a/setup.py b/setup.py index 123cb36..b284c7e 100755 --- a/setup.py +++ b/setup.py @@ -4,15 +4,15 @@ name='cryspy', version='0.0.0', author='E. J. Payton', - author_email='eric dot payton at us dot af dot mil', + author_email='paytonej at ucmail dot uc dot edu', packages=['cryspy'], scripts=['bin/DeGraefHenry.py', 'bin/TESTS.py', 'bin/TUTORIAL.py'], - url='http://www.wpafb.af.mil/afrl/rx/', - license='LICENSE.txt', + url='https://github.com/paytonej/cryspy/', + license='LICENSE.md', description='Visualization and postprocessing of EBSD data.', - long_description=open('README.txt').read(), + long_description=open('README.md').read(), install_requires=[ 'numpy >= 1.0.4', 'scipy == 0.6.0', From 79ae7bfad6fe427ac6be3e9d30756cccf6966bd4 Mon Sep 17 00:00:00 2001 From: Eric Payton Date: Mon, 11 Aug 2025 07:26:10 -0400 Subject: [PATCH 4/4] Update formatting for readme --- README.md | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 20c4b1b..713ec61 100755 --- a/README.md +++ b/README.md @@ -1,26 +1,19 @@ -============================================================================== -Cryspy: Computational Crystallography in Python:: -*A Python Toolbox for EBSD Data Analysis* -============================================================================== +# Cryspy: Computational Crystallography in Python: *A Python Toolbox for (some, specialized) EBSD Data Analyses* Cryspy is a toolbox for computational crystallography in Python. It intends to provide a set of open source tools for visualization, analysis, and postprocessing of EBSD data. -============================================================================== +## COMPONENTS -COMPONENTS +* io : modules for import and export of EBSD data +* rot : modules for rotation representation, manipulation, and conversion +* vis : modules for data visualization +* ebsd : modules for EBSD data analysis and manipulation +* xtal : modules for crystallography and symmetry calculations +* util : utilities -* io :: modules for import and export of EBSD data -* rot :: modules for rotation representation, manipulation, and conversion -* vis :: modules for data visualization -* ebsd :: modules for EBSD data analysis and manipulation -* xtal :: modules for crystallography and symmetry calculations -* util :: utilities - -============================================================================== - -CONTENT HIGHLIGHTS +## CONTENT HIGHLIGHTS io ------------------------------------------------------------------------------ @@ -28,25 +21,25 @@ io rot ------------------------------------------------------------------------------ -* angax :: class for the angle/axis convention -* bunge :: class for Bunge Euler angles -* quat :: class for quaternion representations -* rodri :: class for Rodrigues vector representations -* tvec :: class for transformation vectors (reshaped rotation matrices) +* angax : class for the angle/axis convention +* bunge : class for Bunge Euler angles +* quat : class for quaternion representations +* rodri : class for Rodrigues vector representations +* tvec : class for transformation vectors (reshaped rotation matrices) xtal ------------------------------------------------------------------------------ -* lattvec :: class for representing lattice vectors -* miller :: class for representing lattice plane normals -* unitcell:: class for representing unit cells -* rotsymm :: class for rotational symmetry objects +* lattvec : class for representing lattice vectors +* miller : class for representing lattice plane normals +* unitcell: class for representing unit cells +* rotsymm : class for rotational symmetry objects vis ------------------------------------------------------------------------------ -* stereoproj :: class for plotting stereographic projections -* eaproj :: class for plotting equal area projections +* stereoproj : class for plotting stereographic projections +* eaproj : class for plotting equal area projections util ------------------------------------------------------------------------------ -* rationalize :: function for turning fractional vectors into rational indices -* sigdec :: function for rounding to n significant digits +* rationalize : function for turning fractional vectors into rational indices +* sigdec : function for rounding to n significant digits