diff --git a/.github/old-workflows/win-selfinstall-test.yml b/.github/old-workflows/win-selfinstall-test.yml new file mode 100644 index 000000000..5c38a9fda --- /dev/null +++ b/.github/old-workflows/win-selfinstall-test.yml @@ -0,0 +1,73 @@ +name: install self-installer on Windows + +on: + workflow_dispatch: # Allows running this workflow manually + # Runs on pushes targeting the default branch +# push: +# branches-ignore: +# - master + # branches: ['main'] +# pull_request: +# branches: ['main'] + + +jobs: + test-GSAS-II: + #strategy: + # fail-fast: false + runs-on: 'windows-latest' + steps: + - uses: suisei-cn/actions-download-file@818d6b7dc8fe73f2f924b6241f2b1134ca1377d9 # 1.6.0 + id: downloadfile + name: Download the file + with: + url: "https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/gsas2new-5804-Windows-x86_64.exe" + #target: download/ + #- name: find it + # shell: bash + # run: | + # ls -R + + - name: GSAS-II install + shell: pwsh + run: Start-Process -FilePath .\gsas2new-5804-Windows-x86_64.exe -ArgumentList "/S /AddToPath=1 " -NoNewWindow -Wait + + #- name: find install + # if: always() + # shell: bash + # run: | + # ls -R + + #- name: GSAS-II find GSAS-II + # if: always() + # shell: bash + # run: | + # cd c:/ProgramData/gsas2new/GSAS-II + # ls + + - name: try all tests + if: always() + shell: cmd + run: | + call c:\ProgramData\gsas2new\Scripts\activate + python -VV + call conda install -y --quiet pytest + call cd c:\ProgramData\gsas2new\GSAS-II + dir + python -m pytest c:/ProgramData/gsas2new/GSAS-II + + # #python -v -c "import sys; print(sys.path)" + # python -c "import pyspg" + + # - name: try ldd + # if: always() + # shell: bash -el {0} + # run: | + # #conda create -n ntldd python=3.12 numpy=2.2 m2w64-ntldd-git + # conda create -n ntldd python=3.11 numpy=1.26 m2w64-ntldd-git + # conda activate ntldd + # cd GSAS-II/GSASII-bin/win_64_p3.11_n1.26 + # #cd GSAS-II/GSASII-bin/win_64_p3.12_n2.2 + # ls + # ntldd *.pyd + # python -c "import pyspg" diff --git a/.github/old-workflows/win_test.yml b/.github/old-workflows/win_test.yml new file mode 100644 index 000000000..c60feec13 --- /dev/null +++ b/.github/old-workflows/win_test.yml @@ -0,0 +1,97 @@ +name: debug self-tests on Windows w/3.11 + +on: + workflow_dispatch: # Allows running this workflow manually + # Runs on pushes targeting the default branch +# push: +# branches-ignore: +# - master + # branches: ['main'] +# pull_request: +# branches: ['main'] + + +jobs: + test-GSAS-II: + strategy: + fail-fast: false + matrix: +# python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11"] +# python-version: ["3.12"] + runs-on: 'windows-latest' + steps: + - uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda install + shell: bash -el {0} + run: | + # tailor the numpy version to match the GSAS-II binaries + if [ "${{ matrix.python-version }}" == "3.13" ]; then + npver="2.2 python-gil" + elif [ "${{ matrix.python-version }}" == "3.12" ]; then + npver=2.2 + elif [ "${{ matrix.python-version }}" == "3.11" ]; then + npver=1.26 + fi + #conda list + #conda info + conda install python=${{ matrix.python-version }} numpy=${npver} scipy requests pytest git gitpython pycifrw -c conda-forge --override-channels --quiet + #conda install --quiet numpy=${npver} requests pytest briantoby::gsas2pkg -c conda-forge + conda info + conda list + - name: GSAS-II install + shell: bash -el {0} + run: | + #mkdir G2 + #cd G2 + curl -L -O https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/raw/main/install/gitstrap.py + python gitstrap.py --nocheck --noshortcut --branch=main + + - name: find files + shell: bash -el {0} + run: | + ls -l GSAS-II/GSASII-bin/win_64_p3.11_n1.26 + #ls -l GSAS-II/GSASII-bin/win_64_p3.12_n2.2 + ls -l GSAS-II/tests + + - name: GSAS-II single test + if: always() + shell: bash -el {0} + run: | + cd GSAS-II + python tests/test_elm.py + python tests/test_spg.py + + # - name: GSAS-II all tests + # if: always() + # shell: bash -el {0} + # run: | + # cd GSAS-II + # ls + # python -m pytest + + - name: try pyspg + if: always() + shell: bash -el {0} + run: | + cd GSAS-II/GSASII-bin/win_64_p3.11_n1.26 + #cd GSAS-II/GSASII-bin/win_64_p3.12_n2.2 + python -VV + python -c "import sys; print(sys.path)" + python -c "import pyspg" + + - name: try ldd + if: always() + shell: bash -el {0} + run: | + #conda create -n ntldd python=3.12 numpy=2.2 m2w64-ntldd-git + conda create -n ntldd python=3.11 numpy=1.26 m2w64-ntldd-git + conda activate ntldd + cd GSAS-II/GSASII-bin/win_64_p3.11_n1.26 + #cd GSAS-II/GSASII-bin/win_64_p3.12_n2.2 + ls + ntldd *.pyd + python -c "import sys; sys.path.insert(0,'.'); import pyspg" diff --git a/.github/workflows/TriggerRemote.yml b/.github/workflows/TriggerRemote.yml index 273a2bb6d..36b79238d 100644 --- a/.github/workflows/TriggerRemote.yml +++ b/.github/workflows/TriggerRemote.yml @@ -1,14 +1,24 @@ # when run, this starts GitHub Actions workflow in another repo (GSAS-II-buildtools) -# 1) note that the GitHub Actions to be run must be on: repository_dispatch: -# 2) in the organizaion owning the remote repo, one must create a Personal Access Token -# (Org level) (Settings/Developer Settings/Personal Access Settings->Tokens -# got this to work w/classic having this access allowed: -# repo:status, repo_deployment, public_repo, repo:invite, security_events, manage_runners:org -# better w/new style token, I guess -# 3) save the created token in @ project-level: Settings/Secrets and vars/Actions: Repo secret +# 1) This will trigger all GitHub Actions in workflows that has +# on: repository_dispatch: +# 2) an authorized user (Brian, or perhaps organization) creates a +# personal access token. +# Do this by clicking on "picture (upper rt) +# then "Settings" in drop-down and +# then select "Developer settings" (at bottom). +# Finally select "Fine-grained tokens" +# Personal access token settings: +# select owner: APS; +# select APS/G2-bld; +# Repo permissions: contents r/w & metadata: r/o. +# 3) Save the created token in this (G2) project. +# Use repo-level settings on 2nd line from top (may be in ... menu). +# Select Actions in Secrets & variables and create a repository secret. +# Name assigned must match ${{ secrets.XXX }} in workflow's curl +# call ("Authorization:" setting) -name: Trigger on GSASII-buildtools +name: Test of triggering GSASII-buildtools actions on: workflow_dispatch diff --git a/.github/workflows/smoke_test.yml b/.github/workflows/smoke_test.yml new file mode 100644 index 000000000..8296ec734 --- /dev/null +++ b/.github/workflows/smoke_test.yml @@ -0,0 +1,51 @@ +name: smoke test + +on: + # Runs on pushes targeting the default branch + push: + branches-ignore: + - master + # branches: ['develop'] + pull_request: + branches: ['main'] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + +# Default to bash +defaults: + run: + shell: bash + + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - name: 'ubuntu-22.04' + - name: 'macos-latest' + - name: 'windows-latest' +# cmd_extra: '-win' + env: ['py310', 'py311', 'py312', 'py313'] + runs-on: ${{ matrix.os.name }} + steps: + # checkout repo + - uses: actions/checkout@v2 + + # set up pixi + - uses: prefix-dev/setup-pixi@v0.8.1 + with: + environments: ${{ matrix.env }} + pixi-version: v0.39.5 + manifest-path: pixi/pixi.toml + + # run install to see if it works + - name: Build + # run: pixi run --manifest-path pixi/pixi.toml -e ${{ matrix.env }} install${{ matrix.os.cmd_extra }} + run: pixi run --manifest-path pixi/pixi.toml -e ${{ matrix.env }} install + + - name: test + run: pixi run --manifest-path pixi/pixi.toml -e ${{ matrix.env }} test diff --git a/.github/workflows/win-gitstrap-selftest.yml b/.github/workflows/win-gitstrap-selftest.yml new file mode 100644 index 000000000..2f6bd4498 --- /dev/null +++ b/.github/workflows/win-gitstrap-selftest.yml @@ -0,0 +1,52 @@ +name: GSAS-II self-tests on Windows via gitstrap + +on: + workflow_dispatch: # Allows running this workflow manually + # Runs on pushes targeting the default branch + # push: + # branches-ignore: + # - master + # branches: ['main'] +# pull_request: +# branches: ['main'] + + +jobs: + test-GSAS-II: + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13"] +# python-version: ["3.11", "3.12", "3.13"] +# python-version: ["3.13"] + runs-on: 'windows-latest' + steps: + - uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + - name: Conda install + shell: bash -el {0} + run: | + # tailor the numpy version to match the GSAS-II binaries + if [ "${{ matrix.python-version }}" == "3.13" ]; then + npver="2.2 python-gil" + elif [ "${{ matrix.python-version }}" == "3.12" ]; then + npver=2.2 + elif [ "${{ matrix.python-version }}" == "3.11" ]; then + npver=1.26 + fi + conda install python=${{ matrix.python-version }} numpy=${npver} scipy requests pytest git gitpython pycifrw -c conda-forge --override-channels --quiet + #conda info + #conda list + - name: GSAS-II install + shell: bash -el {0} + run: | + curl -L -O https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/raw/main/install/gitstrap.py + python gitstrap.py --nocheck --noshortcut --branch=main + + - name: GSAS-II all tests + shell: bash -el {0} + run: | + cd GSAS-II + python -m pytest diff --git a/.gitignore b/.gitignore index 786081ff3..40b286e21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -# GSAS-II settings, perhaps to be moved +# obsolete GSAS-II settings file config.py # local code for debugging debug_setup.py.example debug_setup.py +# created by install/saved-versions.py +GSASII/saved_version.py + # created on MacOS by makeMacApp.py GSAS-II.app/ GSAS-II.py @@ -25,8 +28,20 @@ __pycache__ # from Spyder .spyproject + # GSAS binaries GSASII/bindist +GSASII/bin # Local dev env files -.vscode \ No newline at end of file +.vscode + + +dist +build + +# +*.gpx +*.lst + +*~ diff --git a/GSASII/Absorb.py b/GSASII/Absorb.py index ca0457458..6ce0c0dcb 100644 --- a/GSASII/Absorb.py +++ b/GSASII/Absorb.py @@ -13,10 +13,11 @@ import numpy as np import sys import matplotlib as mpl -import GSASIIpath -import GSASIIElem as G2elem -import GSASIIElemGUI as G2elemGUI -import GSASIIctrlGUI as G2G +from . import GSASIIpath +from . import GSASIIElem as G2elem +from . import GSASIIElemGUI as G2elemGUI +from . import GSASIIctrlGUI as G2G +from . import GSASIIpwd as G2pwd try: wx.NewIdRef @@ -33,7 +34,7 @@ Pwrm2 = chr(0x207b)+chr(0x0b2) Pwrm6 = chr(0x207b)+chr(0x2076) Pwrm4 = chr(0x207b)+chr(0x2074) -Angstr = chr(0x00c5) +Angstr = chr(0x00c5) Gkmu = chr(0x3bc) Pwr3 = chr(0x0b3) Pwr4 = chr(0x2074) @@ -44,13 +45,13 @@ wxID_RESULTS,wxID_SLIDER1, wxID_SPINBUTTON, wxID_NUMELEM, wxID_SPINTEXT5,wxID_SPINTEXT6, ] = [wx.NewId() for _init_ctrls in range(11)] -[wxID_EXIT, wxID_DELETE, wxID_NEW, +[wxID_EXIT, wxID_DELETE, wxID_NEW, ] = [wx.NewId() for _init_coll_ABSORB_Items in range(3)] - -[wxID_KALPHAAGKA, wxID_KALPHACOKA, wxID_KALPHACRKA, - wxID_KALPHACUKA, wxID_KALPHAFEKA, wxID_KALPHAMNKA, + +[wxID_KALPHAAGKA, wxID_KALPHACOKA, wxID_KALPHACRKA, + wxID_KALPHACUKA, wxID_KALPHAFEKA, wxID_KALPHAMNKA, wxID_KALPHAMOKA, wxID_KALPHANIKA, wxID_KALPHAZNKA, -wxID_KALPHAGAKA,wxID_KALPHAINKA, +wxID_KALPHAGAKA,wxID_KALPHAINKA, ] = [wx.NewId() for _init_coll_KALPHA_Items in range(11)] [wxID_ABSORBABOUT] = [wx.NewId() for _init_coll_ABOUT_Items in range(1)] @@ -87,6 +88,8 @@ class Absorb(wx.Frame): Zcell = 1 Pack = 0.50 Radius = 0.4 + muT = 0.0 + muR = 0.0 def _init_coll_ABOUT_Items(self, parent): parent.Append(wxID_ABSORBABOUT,'About') @@ -102,37 +105,37 @@ def _init_coll_KALPHA_Items(self, parent): "Set of characteristic radiation from sealed tube sources" def OnCrkaMenu(event): self.SetWaveEnergy(2.28962) - + def OnMnkaMenu(event): self.SetWaveEnergy(2.10174) - + def OnFekaMenu(event): self.SetWaveEnergy(1.93597) - + def OnCokaMenu(event): self.SetWaveEnergy(1.78896) - + def OnNikaMenu(event): self.SetWaveEnergy(1.65784) - + def OnCukaMenu(event): self.SetWaveEnergy(1.54052) - + def OnZnkaMenu(event): self.SetWaveEnergy(1.43510) - + def OnGakaMenu(event): self.SetWaveEnergy(1.34134) - + def OnMokaMenu(event): self.SetWaveEnergy(0.70926) - + def OnAgkaMenu(event): self.SetWaveEnergy(0.55936) - + def OnInkaMenu(event): self.SetWaveEnergy(0.51357) - + parent.Append(wxID_KALPHACRKA, 'CrKa') parent.Append(wxID_KALPHAMNKA, 'MnKa') parent.Append(wxID_KALPHAFEKA, 'FeKa') @@ -164,7 +167,7 @@ def _init_coll_ABSORB_Items(self, parent): self.Bind(wx.EVT_MENU, self.OnExitMenu, id=wxID_EXIT) self.Bind(wx.EVT_MENU, self.OnNewMenu, id=wxID_NEW) self.Bind(wx.EVT_MENU, self.OnDeleteMenu, id=wxID_DELETE) - + def _init_utils(self): self.ABSORB = wx.Menu(title='') @@ -182,11 +185,11 @@ def _init_utils(self): def _init_ctrls(self, parent): wx.Frame.__init__(self, parent=parent, - size=wx.Size(500, 400),style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX, title='Absorb') + size=wx.Size(500, 400),style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX, title='Absorb') self._init_utils() self.SetMenuBar(self.menuBar1) self.DrawPanel() - + def SetSize(self): w,h = self.GetClientSize() self.panel.SetSize(wx.Size(w,h)) @@ -200,7 +203,7 @@ def DrawPanel(self): self.Results.SetEditable(False) mainSizer.Add(self.Results,1,wx.EXPAND) mainSizer.Add((10,15),0) - + if self.Elems: lablSizer = wx.BoxSizer(wx.HORIZONTAL) lablSizer.Add((5,10),0) @@ -218,26 +221,26 @@ def DrawPanel(self): compSizer.Add(numElem,0) numElem.Bind(wx.EVT_TEXT_ENTER, self.OnNumElem, id=wxID_NUMELEM) mainSizer.Add(compSizer,0) - mainSizer.Add((10,15),0) + mainSizer.Add((10,15),0) selSizer = wx.BoxSizer(wx.HORIZONTAL) selSizer.Add((5,10),0) selSizer.Add(wx.StaticText(parent=self.panel, label='Wavelength:'),0,wx.EXPAND) selSizer.Add((5,10),0) - self.SpinText1 = wx.TextCtrl(id=wxID_SPINTEXT1, parent=self.panel, + self.SpinText1 = wx.TextCtrl(id=wxID_SPINTEXT1, parent=self.panel, size=wx.Size(100,20), value = "%.4f" % (self.Wave),style=wx.TE_PROCESS_ENTER ) selSizer.Add(self.SpinText1,0) selSizer.Add((5,10),0) self.SpinText1.Bind(wx.EVT_TEXT_ENTER, self.OnSpinText1, id=wxID_SPINTEXT1) - + selSizer.Add(wx.StaticText(parent=self.panel, label='Energy:'),0,wx.EXPAND) selSizer.Add((5,10),0) - self.SpinText2 = wx.TextCtrl(id=wxID_SPINTEXT2, parent=self.panel, - size=wx.Size(100,20), value = "%.4f" % (self.Energy),style=wx.TE_PROCESS_ENTER) + self.SpinText2 = wx.TextCtrl(id=wxID_SPINTEXT2, parent=self.panel, + size=wx.Size(100,20), value = "%.4f" % (self.Energy),style=wx.TE_PROCESS_ENTER) selSizer.Add(self.SpinText2,0) selSizer.Add((5,10),0) self.SpinText2.Bind(wx.EVT_TEXT_ENTER, self.OnSpinText2, id=wxID_SPINTEXT2) - + selSizer.Add(wx.StaticText(parent=self.panel, label='Plot scale:'),0,wx.EXPAND) selSizer.Add((5,10),0) self.choice1 = wx.ComboBox(id=wxID_CHOICE1, parent=self.panel, value='Wavelength', @@ -247,9 +250,9 @@ def DrawPanel(self): self.choice1.Bind(wx.EVT_COMBOBOX, self.OnChoice1, id=wxID_CHOICE1) mainSizer.Add(selSizer,0) mainSizer.Add((10,10),0) - + slideSizer = wx.BoxSizer(wx.HORIZONTAL) - self.SpinButton = wx.SpinButton(id=wxID_SPINBUTTON, parent=self.panel, + self.SpinButton = wx.SpinButton(id=wxID_SPINBUTTON, parent=self.panel, size=wx.Size(25,24), style=wx.SP_VERTICAL | wx.SP_ARROW_KEYS) slideSizer.Add(self.SpinButton) self.SpinButton.SetRange(-1,1) @@ -263,30 +266,30 @@ def DrawPanel(self): self.slider1.Bind(wx.EVT_SLIDER, self.OnSlider1, id=wxID_SLIDER1) mainSizer.Add(slideSizer,0,wx.EXPAND) mainSizer.Add((10,10),0) - + cellSizer = wx.BoxSizer(wx.HORIZONTAL) cellSizer.Add((5,10),0) cellSizer.Add(wx.StaticText(parent=self.panel, label='Volume:'),0,wx.EXPAND) cellSizer.Add((5,10),0) - self.SpinText3 = wx.TextCtrl(id=wxID_SPINTEXT3, parent=self.panel, + self.SpinText3 = wx.TextCtrl(id=wxID_SPINTEXT3, parent=self.panel, size=wx.Size(100,20), value = "%.2f" % (self.Volume),style=wx.TE_PROCESS_ENTER ) cellSizer.Add(self.SpinText3,0) cellSizer.Add((5,10),0) self.SpinText3.Bind(wx.EVT_TEXT_ENTER, self.OnSpinText3, id=wxID_SPINTEXT3) - + cellSizer.Add((5,10),0) cellSizer.Add(wx.StaticText(parent=self.panel, label='Z(vol):'),0,wx.EXPAND) cellSizer.Add((5,10),0) - self.SpinText4 = wx.TextCtrl(id=wxID_SPINTEXT4, parent=self.panel, + self.SpinText4 = wx.TextCtrl(id=wxID_SPINTEXT4, parent=self.panel, size=wx.Size(50,20), value = "%d" % (self.Zcell),style=wx.TE_PROCESS_ENTER ) cellSizer.Add(self.SpinText4,0) cellSizer.Add((5,10),0) self.SpinText4.Bind(wx.EVT_TEXT_ENTER, self.OnSpinText4, id=wxID_SPINTEXT4) - + cellSizer.Add((5,10),0) cellSizer.Add(wx.StaticText(parent=self.panel, label='Sample R:'),0,wx.EXPAND) cellSizer.Add((5,10),0) - self.SpinText5 = wx.TextCtrl(id=wxID_SPINTEXT5, parent=self.panel, + self.SpinText5 = wx.TextCtrl(id=wxID_SPINTEXT5, parent=self.panel, size=wx.Size(50,20), value = "%.2f" % (self.Radius),style=wx.TE_PROCESS_ENTER ) cellSizer.Add(self.SpinText5,0) cellSizer.Add((5,10),0) @@ -295,7 +298,7 @@ def DrawPanel(self): cellSizer.Add((5,10),0) cellSizer.Add(wx.StaticText(parent=self.panel, label='packing:'),0,wx.EXPAND) cellSizer.Add((5,10),0) - self.SpinText6 = wx.TextCtrl(id=wxID_SPINTEXT6, parent=self.panel, + self.SpinText6 = wx.TextCtrl(id=wxID_SPINTEXT6, parent=self.panel, size=wx.Size(50,20), value = "%.2f" % (self.Pack),style=wx.TE_PROCESS_ENTER ) cellSizer.Add(self.SpinText6,0) cellSizer.Add((5,10),0) @@ -346,7 +349,7 @@ def OnNewMenu(self, event): self.DrawPanel() self.NewFPPlot = True self.SetWaveEnergy(self.Wave) - + def OnDeleteMenu(self, event): if len(self.Elems): ElList = [] @@ -367,38 +370,38 @@ def OnDeleteMenu(self, event): self.ifVol = False self.NewFPPlot = True self.SetWaveEnergy(self.Wave) - + def OnNumElem(self, event): for Elem in self.Elems: if event.GetEventObject().GetName() == Elem[0]: Elem[2] = float(event.GetEventObject().GetValue()) event.GetEventObject().SetValue("%8.2f" % (Elem[2])) self.ifVol = False - self.SetWaveEnergy(self.Wave) - + self.SetWaveEnergy(self.Wave) + def OnSpinText1(self, event): self.SetWaveEnergy(float(self.SpinText1.GetValue())) - + def OnSpinText2(self, event): self.SetWaveEnergy(self.Kev/(float(self.SpinText2.GetValue()))) - + def OnSpinText3(self,event): self.Volume = max(10.,float(self.SpinText3.GetValue())) self.ifVol = True self.SetWaveEnergy(self.Wave) - + def OnSpinText4(self,event): self.Zcell = max(1,float(self.SpinText4.GetValue())) self.SetWaveEnergy(self.Wave) - + def OnSpinText5(self, event): self.Radius = max(0.01,float(self.SpinText5.GetValue())) self.SetWaveEnergy(self.Wave) - + def OnSpinText6(self, event): self.Pack = min(1.0,max(0.01,float(self.SpinText6.GetValue()))) self.SetWaveEnergy(self.Wave) - + def OnSpinButton(self, event): move = self.SpinButton.GetValue()/10000. self.Wave = min(max(self.Wave+move,self.Wmin),self.Wmax) @@ -411,7 +414,7 @@ def OnSlider1(self, event): else: Wave = self.Kev/(float(self.slider1.GetValue())/1000.) self.SetWaveEnergy(Wave) - + def SetWaveEnergy(self,Wave): self.Wave = Wave self.Energy = self.Kev/self.Wave @@ -460,23 +463,28 @@ def SetWaveEnergy(self,Wave): 'Element: '+str(Els),"N = ",Elem[2]," f'=",(r1[0]+r2[0])/2.0, ' f"=',(r1[1]+r2[1])/2.0,' '+Gkmu+'=',mu,'barns') muT += mu - + self.muT = muT + if self.Volume: + self.muR = self.Radius*self.Pack*muT/(10.0*self.Volume) Text += "%s %s%10.4g %s" % ("Total",' '+Gkmu+' =',self.Pack*muT/self.Volume,'cm'+Pwrm1+', ') - Text += "%s%10.4g%s" % ('Total '+Gkmu+'R =',self.Radius*self.Pack*muT/(10.0*self.Volume),', ') + Text += "%s%10.4g%s" % ('Total '+Gkmu+'R =',self.muR,', ') Text += "%s%10.4f%s\n" % ('Transmission exp(-2'+Gkmu+'R) =', \ 100.0*math.exp(-2.*self.Radius*self.Pack*muT/(10.0*self.Volume)),'%') if muT > 0.: pene = 10./(self.Pack*muT/self.Volume) - if pene > 0.01: + if pene > 0.01: Text += "%s %10.4g mm\n"%("1/e (~27.2%) penetration depth = ",pene) else: Text += "%s %10.4g %sm\n"%("1/e (~27.2%) penetration depth = ",1000.0*pene,Gkmu) + Tth = np.array([0.0,140.0]) + Xabs = 1./G2pwd.Absorb('Cylinder',self.muR,Tth) + Text += "%s %10.4g to %10.4g\n"%("Cylinder absorption correction extremes:",Xabs[0],Xabs[1]) self.Results.SetValue(Text) - den = Mass/(0.602*self.Volume) + den = Mass/(0.602*self.Volume) if self.ifVol: Text += '%s' % ('Theor. density =') - else: + else: Text += '%s' % ('Est. density =') Text += '%6.3f %s%.3f %s\n' % (den,'g/cm'+Pwr3+', Powder density =',self.Pack*den,'g/cm'+Pwr3) Text += '%s%10.2f%s\n'%('X-ray small angle scattering contrast',(28.179*Fo/self.Volume)**2,'*10'+Pwr20+'/cm'+Pwr4) @@ -566,7 +574,7 @@ def OnChoice1(self, event): if len(self.Elems): self.CalcFPPS() self.UpDateAbsPlot(self.Wave,rePlot=False) - + def OnKeyPress(self,event): if event.key == 'g': mpl.rcParams['axes.grid'] = not mpl.rcParams['axes.grid'] @@ -587,23 +595,30 @@ def UpDateAbsPlot(self,Wave,rePlot=True): self.Page.canvas.mpl_connect('motion_notify_event', self.OnMotion) self.Page.canvas.mpl_connect('key_press_event', self.OnKeyPress) newPlot = True - self.ax = self.Page.figure.add_subplot(111,label='absorb') + GS_kw = {'width_ratios':[1,1],} + self.ax,self.bx = self.Page.figure.subplots(1,2,gridspec_kw=GS_kw) self.fplot.set_visible(False) self.Page.Choice = (' key press','g: toggle grid',) - self.Page.keyPress = self.OnKeyPress + self.Page.keyPress = self.OnKeyPress self.ax.clear() + self.bx.clear() self.ax.set_title('X-Ray Absorption',x=0,ha='left') self.ax.set_ylabel(r"$\mu R$",fontsize=14) + self.bx.set_title(r"Cylinder abs. corr. for $\lambda= %.4f\AA$"%self.Wave,x=0,ha='left') + self.bx.set_xlabel(r"$2\theta$",fontsize=14) + Tth = np.arange(0.,140.0,0.1) + Xabs = 1./G2pwd.Absorb('Cylinder',self.muR,Tth) + self.bx.plot(Tth,Xabs) Ymin = 0.0 Ymax = 0.0 - if self.FPPS: + if self.FPPS: for Fpps in self.FPPS: Ymin = min(Ymin,min(Fpps[2])) Ymax = max(Ymax,max(Fpps[2])) fppsP1 = np.array(Fpps[1]) fppsP2 = np.array(Fpps[2]) self.ax.plot(fppsP1,fppsP2,label=r'$\mu R$ '+Fpps[0]) - if self.ifWave: + if self.ifWave: self.ax.set_xlabel(r'$\mathsf{\lambda, \AA}$',fontsize=14) self.ax.axvline(x=Wave,picker=3,color='black') else: @@ -627,10 +642,10 @@ def UpDateAbsPlot(self,Wave,rePlot=True): xylim = [] tb.push_current() self.Page.canvas.draw() - + def OnPick(self, event): self.linePicked = event.artist - + def OnMotion(self,event): xpos = event.xdata if xpos and xpos>0.1: @@ -643,7 +658,7 @@ def OnMotion(self,event): self.parent.G2plotNB.status.SetStatusText('Wavelength: %.4f, Energy: %.3f, %sR: %.3f'%(Wave,self.Kev/Wave,Gkmu,ypos),1) if self.linePicked: self.SetWaveEnergy(Wave) - + def OnRelease(self, event): if self.linePicked is None: return self.linePicked = None @@ -652,9 +667,9 @@ def OnRelease(self, event): if self.ifWave: Wave = xpos else: - Wave = self.Kev/xpos + Wave = self.Kev/xpos self.SetWaveEnergy(Wave) - + def OnABOUTItems0Menu(self, event): '''Displays the About window ''' @@ -667,19 +682,19 @@ def OnABOUTItems0Menu(self, event): info.Copyright = ''' Robert B. Von Dreele, 2009(C) Argonne National Laboratory -This product includes software developed -by the UChicago Argonne, LLC, as +This product includes software developed +by the UChicago Argonne, LLC, as Operator of Argonne National Laboratory. ''' info.Description = ''' -For calculating X-ray absorption factors to 250keV for cylindrical -powder samples; based on Fortran program Fprime of Cromer & Liberman +For calculating X-ray absorption factors to 250keV for cylindrical +powder samples; based on Fortran program Fprime of Cromer & Liberman (D. T. Cromer and D. A. Liberman, Acta Cryst. (1981). A37, 267-268.) corrected for Kissel & Pratt energy term; Jensen term not included ''' wxadv.AboutBox(info) if __name__ == "__main__": - import GSASIIplot as G2plt + from . import GSASIIplot as G2plt app = wx.App() GSASIIpath.InvokeDebugOpts() frm = wx.Frame(None) # create a frame diff --git a/GSASII/CifFile/CifFile.py b/GSASII/CifFile/CifFile.py deleted file mode 100755 index 5d3564a76..000000000 --- a/GSASII/CifFile/CifFile.py +++ /dev/null @@ -1,2995 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -try: - from cStringIO import StringIO -except ImportError: - from io import StringIO - -# Python 2,3 compatibility -new -try: - from urllib import urlopen # for arbitrary opening - from urlparse import urlparse, urlunparse,urljoin -except: - from urllib.request import urlopen - from urllib.parse import urlparse,urlunparse,urljoin - -# The unicode type does not exist in Python3 as the str type -# encompasses unicode. PyCIFRW tests for 'unicode' would fail -# Suggestions for a better approach welcome. - -if isinstance(u"abc",str): #Python3 - unicode = str - -__copyright = """ -PYCIFRW License Agreement (Python License, Version 2) ------------------------------------------------------ - -1. This LICENSE AGREEMENT is between the Australian Nuclear Science -and Technology Organisation ("ANSTO"), and the Individual or -Organization ("Licensee") accessing and otherwise using this software -("PyCIFRW") in source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, -ANSTO hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use PyCIFRW alone -or in any derivative version, provided, however, that this License -Agreement and ANSTO's notice of copyright, i.e., "Copyright (c) -2001-2014 ANSTO; All Rights Reserved" are retained in PyCIFRW alone or -in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates PyCIFRW or any part thereof, and wants to make the -derivative work available to others as provided herein, then Licensee -hereby agrees to include in any such work a brief summary of the -changes made to PyCIFRW. - -4. ANSTO is making PyCIFRW available to Licensee on an "AS IS" -basis. ANSTO MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, ANSTO MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYCIFRW WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. ANSTO SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYCIFRW -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A -RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYCIFRW, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between ANSTO -and Licensee. This License Agreement does not grant permission to use -ANSTO trademarks or trade name in a trademark sense to endorse or -promote products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using PyCIFRW, Licensee agrees -to be bound by the terms and conditions of this License Agreement. - -""" - - -import re,sys -from . import StarFile -from .StarFile import StarList #put in global scope for exec statement -try: - import numpy #put in global scope for exec statement - from .drel import drel_runtime #put in global scope for exec statement -except ImportError: - pass #will fail when using dictionaries for calcs -from copy import copy #must be in global scope for exec statement - -def track_recursion(in_this_func): - """Keep an eye on a function call to make sure that the key argument hasn't been - seen before""" - def wrapper(*args,**kwargs): - key_arg = args[1] - if key_arg in wrapper.called_list: - print('Recursion watch: %s already called %d times' % (key_arg,wrapper.called_list.count(key_arg))) - raise CifRecursionError( key_arg,wrapper.called_list[:]) #failure - if len(wrapper.called_list) == 0: #first time - wrapper.stored_use_defaults = kwargs.get("allow_defaults",False) - print('All recursive calls will set allow_defaults to ' + repr(wrapper.stored_use_defaults)) - else: - kwargs["allow_defaults"] = wrapper.stored_use_defaults - wrapper.called_list.append(key_arg) - print('Recursion watch: call stack: ' + repr(wrapper.called_list)) - try: - result = in_this_func(*args,**kwargs) - except StarFile.StarDerivationError as s: - if len(wrapper.called_list) == 1: #no more - raise StarFile.StarDerivationFailure(wrapper.called_list[0]) - else: - raise - finally: - wrapper.called_list.pop() - if len(wrapper.called_list) == 0: - wrapper.stored_used_defaults = 'error' - return result - wrapper.called_list = [] - return wrapper - -class CifBlock(StarFile.StarBlock): - """ - A class to hold a single block of a CIF file. A `CifBlock` object can be treated as - a Python dictionary, in particular, individual items can be accessed using square - brackets e.g. `b['_a_dataname']`. All other Python dictionary methods are also - available (e.g. `keys()`, `values()`). Looped datanames will return a list of values. - - ## Initialisation - - When provided, `data` should be another `CifBlock` whose contents will be copied to - this block. - - * if `strict` is set, maximum name lengths will be enforced - - * `maxoutlength` is the maximum length for output lines - - * `wraplength` is the ideal length to make output lines - - * When set, `overwrite` allows the values of datanames to be changed (otherwise an error - is raised). - - * `compat_mode` will allow deprecated behaviour of creating single-dataname loops using - the syntax `a[_dataname] = [1,2,3,4]`. This should now be done by calling `CreateLoop` - after setting the dataitem value. - """ - def __init__(self,data = (), strict = 1, compat_mode=False, **kwargs): - """When provided, `data` should be another CifBlock whose contents will be copied to - this block. - - * if `strict` is set, maximum name lengths will be enforced - - * `maxoutlength` is the maximum length for output lines - - * `wraplength` is the ideal length to make output lines - - * When set, `overwrite` allows the values of datanames to be changed (otherwise an error - is raised). - - * `compat_mode` will allow deprecated behaviour of creating single-dataname loops using - the syntax `a[_dataname] = [1,2,3,4]`. This should now be done by calling `CreateLoop` - after setting the dataitem value. - """ - if strict: maxnamelength=75 - else: - maxnamelength=-1 - super(CifBlock,self).__init__(data=data,maxnamelength=maxnamelength,**kwargs) - self.dictionary = None #DDL dictionary referring to this block - self.compat_mode = compat_mode #old-style behaviour of setitem - - def RemoveCifItem(self,itemname): - """Remove `itemname` from the CifBlock""" - self.RemoveItem(itemname) - - def __setitem__(self,key,value): - self.AddItem(key,value) - # for backwards compatibility make a single-element loop - if self.compat_mode: - if isinstance(value,(tuple,list)) and not isinstance(value,StarFile.StarList): - # single element loop - self.CreateLoop([key]) - - def copy(self): - newblock = super(CifBlock,self).copy() - return self.copy.im_class(newblock) #catch inheritance - - def AddCifItem(self,data): - """ *DEPRECATED*. Use `AddItem` instead.""" - # we accept only tuples, strings and lists!! - if not (isinstance(data[0],(unicode,tuple,list,str))): - raise TypeError('Cif datanames are either a string, tuple or list') - # we catch single item loops as well... - if isinstance(data[0],(unicode,str)): - self.AddSingleCifItem(data[0],list(data[1])) - if isinstance(data[1],(tuple,list)) and not isinstance(data[1],StarFile.StarList): # a single element loop - self.CreateLoop([data[0]]) - return - # otherwise, we loop over the datanames - keyvals = zip(data[0][0],[list(a) for a in data[1][0]]) - [self.AddSingleCifItem(a,b) for a,b in keyvals] - # and create the loop - self.CreateLoop(data[0][0]) - - def AddSingleCifItem(self,key,value): - """*Deprecated*. Use `AddItem` instead""" - """Add a single data item. If it is part of a loop, a separate call should be made""" - self.AddItem(key,value) - - def loopnames(self): - return [self.loops[a] for a in self.loops] - - -class CifFile(StarFile.StarFile): - def __init__(self,datasource=None,strict=1,standard='CIF',**kwargs): - super(CifFile,self).__init__(datasource=datasource,standard=standard, **kwargs) - self.strict = strict - self.header_comment = \ -""" -########################################################################## -# Crystallographic Information Format file -# Produced by PyCifRW module -# -# This is a CIF file. CIF has been adopted by the International -# Union of Crystallography as the standard for data archiving and -# transmission. -# -# For information on this file format, follow the CIF links at -# http://www.iucr.org -########################################################################## -""" - - -class CifError(Exception): - def __init__(self,value): - self.value = value - def __str__(self): - return '\nCif Format error: '+ self.value - -class ValidCifError(Exception): - def __init__(self,value): - self.value = value - def __str__(self): - return '\nCif Validity error: ' + self.value - -class CifRecursionError(Exception): - def __init__(self,key_value,call_stack): - self.key_value = key_value - self.call_stack = call_stack - def __str__(self): - return "Derivation has recursed, %s seen twice (call stack %s)" % (self.key_value,repr(self.call_stack)) - - -class CifDic(StarFile.StarFile): - """Create a Cif Dictionary object from the provided source, which can - be a filename/URL or a CifFile. Optional arguments (relevant to DDLm - only): - - * do_minimum (Boolean): - Do not set up the dREL system for auto-calculation or perform - imports. This implies do_imports=False and do_dREL=False - - * do_imports = No/Full/Contents/All: - If not 'No', replace _import.get statements with the imported contents for - Full mode/Contents mode/Both respectively. - - * do_dREL = True/False: - Parse and convert all dREL methods to Python. Implies do_imports=All - - """ - def __init__(self,dic,do_minimum=False,do_imports='All', do_dREL=True, - grammar='auto',**kwargs): - self.do_minimum = do_minimum - if do_minimum: - do_imports = 'No' - do_dREL = False - if do_dREL: do_imports = 'All' - self.template_cache = {} #for DDLm imports - self.ddlm_functions = {} #for DDLm functions - self.switch_numpy(False) #no Numpy arrays returned - super(CifDic,self).__init__(datasource=dic,grammar=grammar,**kwargs) - self.standard = 'Dic' #for correct output order - self.scoping = 'dictionary' - (self.dicname,self.diclang) = self.dic_determine() - print('%s is a %s dictionary' % (self.dicname,self.diclang)) - self.scopes_mandatory = {} - self.scopes_naughty = {} - # rename and expand out definitions using "_name" in DDL dictionaries - if self.diclang == "DDL1": - self.DDL1_normalise() #this removes any non-definition entries - self.create_def_block_table() #From now on, [] uses definition_id - if self.diclang == "DDL1": - self.ddl1_cat_load() - elif self.diclang == "DDL2": - self.DDL2_normalise() #iron out some DDL2 tricky bits - elif self.diclang == "DDLm": - self.scoping = 'dictionary' #expose all save frames - if do_imports != 'No': - self.ddlm_import(import_mode=do_imports)#recursively calls this routine - self.create_alias_table() - self.create_cat_obj_table() - self.create_cat_key_table() - if do_dREL: - print('Doing full dictionary initialisation') - self.initialise_drel() - self.add_category_info(full=do_dREL) - # initialise type information - self.typedic={} - self.primdic = {} #typecode<->primitive type translation - self.add_type_info() - self.install_validation_functions() - - def dic_determine(self): - if "on_this_dictionary" in self: - self.master_block = super(CifDic,self).__getitem__("on_this_dictionary") - self.def_id_spec = "_name" - self.cat_id_spec = "_category.id" #we add this ourselves - self.type_spec = "_type" - self.enum_spec = "_enumeration" - self.cat_spec = "_category" - self.esd_spec = "_type_conditions" - self.must_loop_spec = "_list" - self.must_exist_spec = "_list_mandatory" - self.list_ref_spec = "_list_reference" - self.key_spec = "_list_mandatory" - self.unique_spec = "_list_uniqueness" - self.child_spec = "_list_link_child" - self.parent_spec = "_list_link_parent" - self.related_func = "_related_function" - self.related_item = "_related_item" - self.primitive_type = "_type" - self.dep_spec = "xxx" - self.cat_list = [] #to save searching all the time - name = super(CifDic,self).__getitem__("on_this_dictionary")["_dictionary_name"] - version = super(CifDic,self).__getitem__("on_this_dictionary")["_dictionary_version"] - return (name+version,"DDL1") - elif len(self.get_roots()) == 1: # DDL2/DDLm - self.master_block = super(CifDic,self).__getitem__(self.get_roots()[0][0]) - # now change to dictionary scoping - self.scoping = 'dictionary' - name = self.master_block["_dictionary.title"] - version = self.master_block["_dictionary.version"] - if self.master_block.has_key("_dictionary.class"): #DDLm - self.enum_spec = '_enumeration_set.state' - self.key_spec = '_category.key_id' - self.must_exist_spec = None - self.cat_spec = '_name.category_id' - self.primitive_type = '_type.contents' - self.cat_id_spec = "_definition.id" - self.def_id_spec = "_definition.id" - return(name+version,"DDLm") - else: #DDL2 - self.cat_id_spec = "_category.id" - self.def_id_spec = "_item.name" - self.key_spec = "_category_mandatory.name" - self.type_spec = "_item_type.code" - self.enum_spec = "_item_enumeration.value" - self.esd_spec = "_item_type_conditions.code" - self.cat_spec = "_item.category_id" - self.loop_spec = "there_is_no_loop_spec!" - self.must_loop_spec = "xxx" - self.must_exist_spec = "_item.mandatory_code" - self.child_spec = "_item_linked.child_name" - self.parent_spec = "_item_linked.parent_name" - self.related_func = "_item_related.function_code" - self.related_item = "_item_related.related_name" - self.unique_spec = "_category_key.name" - self.list_ref_spec = "xxx" - self.primitive_type = "_type" - self.dep_spec = "_item_dependent.dependent_name" - return (name+version,"DDL2") - else: - raise CifError("Unable to determine dictionary DDL version") - - def DDL1_normalise(self): - # switch off block name collision checks - self.standard = None - # add default type information in DDL2 style - # initial types and constructs - base_types = ["char","numb","null"] - prim_types = base_types[:] - base_constructs = [".*", - '(-?(([0-9]*[.][0-9]+)|([0-9]+)[.]?)([(][0-9]+[)])?([eEdD][+-]?[0-9]+)?)|\?|\.', - "\"\" "] - for key,value in self.items(): - newnames = [key] #keep by default - if "_name" in value: - real_name = value["_name"] - if isinstance(real_name,list): #looped values - for looped_name in real_name: - new_value = value.copy() - new_value["_name"] = looped_name #only looped name - self[looped_name] = new_value - newnames = real_name - else: - self[real_name] = value - newnames = [real_name] - # delete the old one - if key not in newnames: - del self[key] - # loop again to normalise the contents of each definition - for key,value in self.items(): - #unlock the block - save_overwrite = value.overwrite - value.overwrite = True - # deal with a missing _list, _type_conditions - if "_list" not in value: value["_list"] = 'no' - if "_type_conditions" not in value: value["_type_conditions"] = 'none' - # deal with enumeration ranges - if "_enumeration_range" in value: - max,min = self.getmaxmin(value["_enumeration_range"]) - if min == ".": - self[key].AddLoopItem((("_item_range.maximum","_item_range.minimum"),((max,max),(max,min)))) - elif max == ".": - self[key].AddLoopItem((("_item_range.maximum","_item_range.minimum"),((max,min),(min,min)))) - else: - self[key].AddLoopItem((("_item_range.maximum","_item_range.minimum"),((max,max,min),(max,min,min)))) - #add any type construct information - if "_type_construct" in value: - base_types.append(value["_name"]+"_type") #ie dataname_type - base_constructs.append(value["_type_construct"]+"$") - prim_types.append(value["_type"]) #keep a record - value["_type"] = base_types[-1] #the new type name - - #make categories conform with ddl2 - #note that we must remove everything from the last underscore - if value.get("_category",None) == "category_overview": - last_under = value["_name"].rindex("_") - catid = value["_name"][1:last_under] - value["_category.id"] = catid #remove square bracks - if catid not in self.cat_list: self.cat_list.append(catid) - value.overwrite = save_overwrite - # we now add any missing categories before filling in the rest of the - # information - for key,value in self.items(): - #print('processing ddl1 definition %s' % key) - if "_category" in self[key]: - if self[key]["_category"] not in self.cat_list: - # rogue category, add it in - newcat = self[key]["_category"] - fake_name = "_" + newcat + "_[]" - newcatdata = CifBlock() - newcatdata["_category"] = "category_overview" - newcatdata["_category.id"] = newcat - newcatdata["_type"] = "null" - self[fake_name] = newcatdata - self.cat_list.append(newcat) - # write out the type information in DDL2 style - self.master_block.AddLoopItem(( - ("_item_type_list.code","_item_type_list.construct", - "_item_type_list.primitive_code"), - (base_types,base_constructs,prim_types) - )) - - def ddl1_cat_load(self): - deflist = self.keys() #slight optimization - cat_mand_dic = {} - cat_unique_dic = {} - # a function to extract any necessary information from each definition - def get_cat_info(single_def): - if self[single_def].get(self.must_exist_spec)=='yes': - thiscat = self[single_def]["_category"] - curval = cat_mand_dic.get(thiscat,[]) - curval.append(single_def) - cat_mand_dic[thiscat] = curval - # now the unique items... - # cif_core.dic throws us a curly one: the value of list_uniqueness is - # not the same as the defined item for publ_body_label, so we have - # to collect both together. We assume a non-listed entry, which - # is true for all current (May 2005) ddl1 dictionaries. - if self[single_def].get(self.unique_spec,None)!=None: - thiscat = self[single_def]["_category"] - new_unique = self[single_def][self.unique_spec] - uis = cat_unique_dic.get(thiscat,[]) - if single_def not in uis: uis.append(single_def) - if new_unique not in uis: uis.append(new_unique) - cat_unique_dic[thiscat] = uis - - [get_cat_info(a) for a in deflist] # apply the above function - for cat in cat_mand_dic.keys(): - self[cat]["_category_mandatory.name"] = cat_mand_dic[cat] - for cat in cat_unique_dic.keys(): - self[cat]["_category_key.name"] = cat_unique_dic[cat] - - def create_pcloop(self,definition): - old_children = self[definition].get('_item_linked.child_name',[]) - old_parents = self[definition].get('_item_linked.parent_name',[]) - if isinstance(old_children,unicode): - old_children = [old_children] - if isinstance(old_parents,unicode): - old_parents = [old_parents] - if (len(old_children)==0 and len(old_parents)==0) or \ - (len(old_children) > 1 and len(old_parents)>1): - return - if len(old_children)==0: - old_children = [definition]*len(old_parents) - if len(old_parents)==0: - old_parents = [definition]*len(old_children) - newloop = CifLoopBlock(dimension=1) - newloop.AddLoopItem(('_item_linked.parent_name',old_parents)) - newloop.AddLoopItem(('_item_linked.child_name',old_children)) - try: - del self[definition]['_item_linked.parent_name'] - del self[definition]['_item_linked.child_name'] - except KeyError: - pass - self[definition].insert_loop(newloop) - - - - def DDL2_normalise(self): - listed_defs = filter(lambda a:isinstance(self[a].get('_item.name'),list),self.keys()) - # now filter out all the single element lists! - dodgy_defs = filter(lambda a:len(self[a]['_item.name']) > 1, listed_defs) - for item_def in dodgy_defs: - # print("DDL2 norm: processing %s" % item_def) - thisdef = self[item_def] - packet_no = thisdef['_item.name'].index(item_def) - realcat = thisdef['_item.category_id'][packet_no] - realmand = thisdef['_item.mandatory_code'][packet_no] - # first add in all the missing categories - # we don't replace the entry in the list corresponding to the - # current item, as that would wipe out the information we want - for child_no in range(len(thisdef['_item.name'])): - if child_no == packet_no: continue - child_name = thisdef['_item.name'][child_no] - child_cat = thisdef['_item.category_id'][child_no] - child_mand = thisdef['_item.mandatory_code'][child_no] - if child_name not in self: - self[child_name] = CifBlock() - self[child_name]['_item.name'] = child_name - self[child_name]['_item.category_id'] = child_cat - self[child_name]['_item.mandatory_code'] = child_mand - self[item_def]['_item.name'] = item_def - self[item_def]['_item.category_id'] = realcat - self[item_def]['_item.mandatory_code'] = realmand - - target_defs = [a for a in self.keys() if '_item_linked.child_name' in self[a] or \ - '_item_linked.parent_name' in self[a]] - # now dodgy_defs contains all definition blocks with more than one child/parent link - for item_def in dodgy_defs: self.create_pcloop(item_def) #regularise appearance - for item_def in dodgy_defs: - print('Processing %s' % item_def) - thisdef = self[item_def] - child_list = thisdef['_item_linked.child_name'] - parents = thisdef['_item_linked.parent_name'] - # for each parent, find the list of children. - family = list(zip(parents,child_list)) - notmychildren = family #We aim to remove non-children - # Loop over the parents, relocating as necessary - while len(notmychildren): - # get all children of first entry - mychildren = [a for a in family if a[0]==notmychildren[0][0]] - print("Parent %s: %d children" % (notmychildren[0][0],len(mychildren))) - for parent,child in mychildren: #parent is the same for all - # Make sure that we simply add in the new entry for the child, not replace it, - # otherwise we might spoil the child entry loop structure - try: - childloop = self[child].GetLoop('_item_linked.parent_name') - except KeyError: - print('Creating new parent entry %s for definition %s' % (parent,child)) - self[child]['_item_linked.parent_name'] = [parent] - childloop = self[child].GetLoop('_item_linked.parent_name') - childloop.AddLoopItem(('_item_linked.child_name',[child])) - continue - else: - # A parent loop already exists and so will a child loop due to the - # call to create_pcloop above - pars = [a for a in childloop if getattr(a,'_item_linked.child_name','')==child] - goodpars = [a for a in pars if getattr(a,'_item_linked.parent_name','')==parent] - if len(goodpars)>0: #no need to add it - print('Skipping duplicated parent - child entry in %s: %s - %s' % (child,parent,child)) - continue - print('Adding %s to %s entry' % (parent,child)) - newpacket = childloop.GetPacket(0) #essentially a copy, I hope - setattr(newpacket,'_item_linked.child_name',child) - setattr(newpacket,'_item_linked.parent_name',parent) - childloop.AddPacket(newpacket) - # - # Make sure the parent also points to the children. We get - # the current entry, then add our - # new values if they are not there already - # - parent_name = mychildren[0][0] - old_children = self[parent_name].get('_item_linked.child_name',[]) - old_parents = self[parent_name].get('_item_linked.parent_name',[]) - oldfamily = zip(old_parents,old_children) - newfamily = [] - print('Old parents -> %s' % repr(old_parents)) - for jj, childname in mychildren: - alreadythere = [a for a in oldfamily if a[0]==parent_name and a[1] ==childname] - if len(alreadythere)>0: continue - 'Adding new child %s to parent definition at %s' % (childname,parent_name) - old_children.append(childname) - old_parents.append(parent_name) - # Now output the loop, blowing away previous definitions. If there is something - # else in this category, we are destroying it. - newloop = CifLoopBlock(dimension=1) - newloop.AddLoopItem(('_item_linked.parent_name',old_parents)) - newloop.AddLoopItem(('_item_linked.child_name',old_children)) - del self[parent_name]['_item_linked.parent_name'] - del self[parent_name]['_item_linked.child_name'] - self[parent_name].insert_loop(newloop) - print('New parents -> %s' % repr(self[parent_name]['_item_linked.parent_name'])) - # now make a new,smaller list - notmychildren = [a for a in notmychildren if a[0]!=mychildren[0][0]] - - # now flatten any single element lists - single_defs = filter(lambda a:len(self[a]['_item.name'])==1,listed_defs) - for flat_def in single_defs: - flat_keys = self[flat_def].GetLoop('_item.name').keys() - for flat_key in flat_keys: self[flat_def][flat_key] = self[flat_def][flat_key][0] - # now deal with the multiple lists - # next we do aliases - all_aliases = [a for a in self.keys() if self[a].has_key('_item_aliases.alias_name')] - for aliased in all_aliases: - my_aliases = listify(self[aliased]['_item_aliases.alias_name']) - for alias in my_aliases: - self[alias] = self[aliased].copy() #we are going to delete stuff... - del self[alias]["_item_aliases.alias_name"] - - def ddlm_parse_valid(self): - if "_dictionary_valid.application" not in self.master_block: - return - for scope_pack in self.master_block.GetLoop("_dictionary_valid.application"): - scope = getattr(scope_pack,"_dictionary_valid.application") - valid_info = getattr(scope_pack,"_dictionary_valid.attributes") - if scope[1] == "Mandatory": - self.scopes_mandatory[scope[0]] = self.expand_category_opt(valid_info) - elif scope[1] == "Prohibited": - self.scopes_naughty[scope[0]] = self.expand_category_opt(valid_info) - - def ddlm_import(self,import_mode='All'): - import_frames = list([(a,self[a]['_import.get']) for a in self.keys() if '_import.get' in self[a]]) - print ('Import mode %s applied to following frames' % import_mode) - print (str([a[0] for a in import_frames])) - if import_mode != 'All': - for i in range(len(import_frames)): - import_frames[i] = (import_frames[i][0],[a for a in import_frames[i][1] if a.get('mode','Contents') == import_mode]) - print('Importing following frames in mode %s' % import_mode) - print(str(import_frames)) - #resolve all references - for parent_block,import_list in import_frames: - for import_ref in import_list: - file_loc = import_ref["file"] - full_uri = self.resolve_path(file_loc) - if full_uri not in self.template_cache: - dic_as_cif = CifFile(urlopen(full_uri),grammar=self.grammar) - self.template_cache[full_uri] = CifDic(dic_as_cif,do_imports=import_mode,do_dREL=False) #this will recurse internal imports - print('Added %s to cached dictionaries' % full_uri) - import_from = self.template_cache[full_uri] - dupl = import_ref.get('dupl','Exit') - miss = import_ref.get('miss','Exit') - target_key = import_ref["save"] - try: - import_target = import_from[target_key] - except KeyError: - if miss == 'Exit': - raise CifError('Import frame %s not found in %s' % (target_key,full_uri)) - else: continue - # now import appropriately - mode = import_ref.get("mode",'Contents').lower() - if target_key in self and mode=='full': #so blockname will be duplicated - if dupl == 'Exit': - raise CifError('Import frame %s already in dictionary' % target_key) - elif dupl == 'Ignore': - continue - if mode == 'contents': #merge attributes only - self[parent_block].merge(import_target) - elif mode =="full": - # Do the syntactic merge - syntactic_head = self[self.get_parent(parent_block)] #root frame if no nesting - from_cat_head = import_target['_name.object_id'] - child_frames = import_from.ddlm_all_children(from_cat_head) - # Check for Head merging Head - if self[parent_block].get('_definition.class','Datum')=='Head' and \ - import_target.get('_definition.class','Datum')=='Head': - head_to_head = True - else: - head_to_head = False - child_frames.remove(from_cat_head) - # As we are in syntax land, we call the CifFile methods - child_blocks = list([import_from.block_id_table[a.lower()] for a in child_frames]) - child_blocks = super(CifDic,import_from).makebc(child_blocks) - # Prune out any datablocks that have identical definitions - from_defs = dict([(a,child_blocks[a].get('_definition.id','').lower()) for a in child_blocks.keys()]) - double_defs = list([b for b in from_defs.items() if self.has_key(b[1])]) - print ('Definitions for %s superseded' % repr(double_defs)) - for b in double_defs: - del child_blocks[b[0]] - super(CifDic,self).merge_fast(child_blocks,parent=syntactic_head) # - print('Syntactic merge of %s (%d defs) in %s mode, now have %d defs' % (target_key,len(child_frames), - mode,len(self))) - # Now the semantic merge - # First expand our definition <-> blockname tree - self.create_def_block_table() - merging_cat = self[parent_block]['_name.object_id'] #new parent - if head_to_head: - child_frames = self.ddlm_immediate_children(from_cat_head) #old children - #the new parent is the importing category for all old children - for f in child_frames: - self[f].overwrite = True - self[f]['_name.category_id'] = merging_cat - self[f].overwrite = False - # remove the old head - del self[from_cat_head] - print('Semantic merge: %d defs reparented from %s to %s' % (len(child_frames),from_cat_head,merging_cat)) - else: #imported category is only child - from_frame = import_from[target_key]['_definition.id'] #so we can find it - child_frame = [d for d in self.keys() if self[d]['_definition.id']==from_frame][0] - self[child_frame]['_name.category_id'] = merging_cat - print('Semantic merge: category for %s : now %s' % (from_frame,merging_cat)) - # it will never happen again... - del self[parent_block]["_import.get"] - - def resolve_path(self,file_loc): - url_comps = urlparse(file_loc) - if url_comps[0]: return file_loc #already full URI - new_url = urljoin(self.my_uri,file_loc) - #print("Transformed %s to %s for import " % (file_loc,new_url)) - return new_url - - - - def create_def_block_table(self): - """ Create an internal table matching definition to block id """ - proto_table = [(super(CifDic,self).__getitem__(a),a) for a in super(CifDic,self).keys()] - # now get the actual ids instead of blocks - proto_table = list([(a[0].get(self.cat_id_spec,a[0].get(self.def_id_spec,a[1])),a[1]) for a in proto_table]) - # remove non-definitions - if self.diclang != "DDL1": - top_blocks = list([a[0].lower() for a in self.get_roots()]) - else: - top_blocks = ["on_this_dictionary"] - # catch dodgy duplicates - uniques = set([a[0] for a in proto_table]) - if len(uniques)1] - raise CifError('Duplicate definitions in dictionary:' + repr(dodgy)) - self.block_id_table = dict([(a[0].lower(),a[1].lower()) for a in proto_table if a[1].lower() not in top_blocks]) - - def __getitem__(self,key): - """Access a datablock by definition id, after the lookup has been created""" - try: - return super(CifDic,self).__getitem__(self.block_id_table[key.lower()]) - except AttributeError: #block_id_table not present yet - return super(CifDic,self).__getitem__(key) - except KeyError: # key is missing - # print(Definition for %s not found, reverting to CifFile' % key) - return super(CifDic,self).__getitem__(key) - - def __setitem__(self,key,value): - """Add a new definition block""" - super(CifDic,self).__setitem__(key,value) - try: - self.block_id_table[value['_definition.id']]=key - except AttributeError: #does not exist yet - pass - - def __delitem__(self,key): - """Remove a definition""" - try: - super(CifDic,self).__delitem__(self.block_id_table[key.lower()]) - del self.block_id_table[key.lower()] - except (AttributeError,KeyError): #block_id_table not present yet - super(CifDic,self).__delitem__(key) - return - # fix other datastructures - # cat_obj table - - def keys(self): - """Return all definitions""" - try: - return self.block_id_table.keys() - except AttributeError: - return super(CifDic,self).keys() - - def has_key(self,key): - return key in self - - def __contains__(self,key): - try: - return key.lower() in self.block_id_table - except AttributeError: - return super(CifDic,self).__contains__(key) - - def items(self): - """Return (key,value) pairs""" - return list([(a,self[a]) for a in self.keys()]) - - def unlock(self): - """Allow overwriting of all definitions in this collection""" - for a in self.keys(): - self[a].overwrite=True - - def lock(self): - """Disallow changes in definitions""" - for a in self.keys(): - self[a].overwrite=False - - def rename(self,oldname,newname,blockname_as_well=True): - """Change a _definition.id from oldname to newname, and if `blockname_as_well` is True, - change the underlying blockname too.""" - if blockname_as_well: - super(CifDic,self).rename(self.block_id_table[oldname.lower()],newname) - self.block_id_table[newname.lower()]=newname - if oldname.lower() in self.block_id_table: #not removed - del self.block_id_table[oldname.lower()] - else: - self.block_id_table[newname.lower()]=self.block_id_table[oldname.lower()] - del self.block_id_table[oldname.lower()] - return - - def get_root_category(self): - """Get the single 'Head' category of this dictionary""" - root_cats = [r for r in self.keys() if self[r].get('_definition.class','Datum')=='Head'] - if len(root_cats)>1 or len(root_cats)==0: - raise CifError("Cannot determine a unique Head category, got" % repr(root_cats)) - return root_cats[0] - - def ddlm_immediate_children(self,catname): - """Return a list of datanames for the immediate children of catname. These are - semantic children (i.e. based on _name.category_id), not structural children as - in the case of StarFile.get_immediate_children""" - - straight_children = [a for a in self.keys() if self[a].get('_name.category_id','').lower() == catname.lower()] - return list(straight_children) - - def ddlm_all_children(self,catname): - """Return a list of all children, including the `catname`""" - all_children = self.ddlm_immediate_children(catname) - cat_children = [a for a in all_children if self[a].get('_definition.scope','Item') == 'Category'] - for c in cat_children: - all_children.remove(c) - all_children += self.ddlm_all_children(c) - return all_children + [catname] - - def is_semantic_child(self,parent,maybe_child): - """Return true if `maybe_child` is a child of `parent`""" - all_children = self.ddlm_all_children(parent) - return maybe_child in all_children - - def ddlm_danglers(self): - """Return a list of definitions that do not have a category defined - for them, or are children of an unattached category""" - top_block = self.get_root_category() - connected = set(self.ddlm_all_children(top_block)) - all_keys = set(self.keys()) - unconnected = all_keys - connected - return list(unconnected) - - def get_ddlm_parent(self,itemname): - """Get the parent category of itemname""" - parent = self[itemname].get('_name.category_id','') - if parent == '': # use the top block by default - raise CifError("%s has no parent" % itemname) - return parent - - def expand_category_opt(self,name_list): - """Return a list of all non-category items in a category or return the name - if the name is not a category""" - new_list = [] - for name in name_list: - if self.get(name,{}).get('_definition.scope','Item') == 'Category': - new_list += self.expand_category_opt([a for a in self.keys() if \ - self[a].get('_name.category_id','').lower() == name.lower()]) - else: - new_list.append(name) - return new_list - - def get_categories(self): - """Return a list of category names""" - return list([c for c in self.keys() if self[c].get("_definition.scope")=='Category']) - - def names_in_cat(self,cat,names_only=False): - names = [a for a in self.keys() if self[a].get('_name.category_id','').lower()==cat.lower()] - if not names_only: - return list([a for a in names if self[a].get('_definition.scope','Item')=='Item']) - else: - return list([self[a]["_name.object_id"] for a in names]) - - - - def create_alias_table(self): - """Populate an alias table that we can look up when searching for a dataname""" - all_aliases = [a for a in self.keys() if '_alias.definition_id' in self[a]] - self.alias_table = dict([[a,self[a]['_alias.definition_id']] for a in all_aliases]) - - def create_cat_obj_table(self): - """Populate a table indexed by (cat,obj) and returning the correct dataname""" - base_table = dict([((self[a].get('_name.category_id','').lower(),self[a].get('_name.object_id','').lower()),[self[a].get('_definition.id','')]) \ - for a in self.keys() if self[a].get('_definition.scope','Item')=='Item']) - loopable = self.get_loopable_cats() - loopers = [self.ddlm_immediate_children(a) for a in loopable] - print('Loopable cats:' + repr(loopable)) - loop_children = [[b for b in a if b.lower() in loopable ] for a in loopers] - expand_list = dict([(a,b) for a,b in zip(loopable,loop_children) if len(b)>0]) - print("Expansion list:" + repr(expand_list)) - extra_table = {} #for debugging we keep it separate from base_table until the end - def expand_base_table(parent_cat,child_cats): - extra_names = [] - # first deal with all the child categories - for child_cat in child_cats: - nn = [] - if child_cat in expand_list: # a nested category: grab its names - nn = expand_base_table(child_cat,expand_list[child_cat]) - # store child names - extra_names += nn - # add all child names to the table - child_names = [(self[n]['_name.object_id'].lower(),self[n]['_definition.id']) \ - for n in self.names_in_cat(child_cat) if self[n].get('_type.purpose','') != 'Key'] - child_names += extra_names - extra_table.update(dict([((parent_cat,obj),[name]) for obj,name in child_names if (parent_cat,name) not in extra_table])) - # and the repeated ones get appended instead - repeats = [a for a in child_names if a in extra_table] - for obj,name in repeats: - extra_table[(parent_cat,obj)] += [name] - # and finally, add our own names to the return list - child_names += [(self[n]['_name.object_id'].lower(),self[n]['_definition.id']) \ - for n in self.names_in_cat(parent_cat) if self[n].get('_type.purpose','')!='Key'] - return child_names - [expand_base_table(parent,child) for parent,child in expand_list.items()] - print('Expansion cat/obj values: ' + repr(extra_table)) - # append repeated ones - non_repeats = dict([a for a in extra_table.items() if a[0] not in base_table]) - repeats = [a for a in extra_table.keys() if a in base_table] - base_table.update(non_repeats) - for k in repeats: - base_table[k] += extra_table[k] - self.cat_obj_lookup_table = base_table - self.loop_expand_list = expand_list - - def get_loopable_cats(self): - """A short utility function which returns a list of looped categories. This - is preferred to a fixed attribute as that fixed attribute would need to be - updated after any edits""" - return [a.lower() for a in self.keys() if self[a].get('_definition.class','')=='Loop'] - - def create_cat_key_table(self): - """Create a utility table with a list of keys applicable to each category. A key is - a compound key, that is, it is a list""" - self.cat_key_table = dict([(c,[listify(self[c].get("_category_key.name", - [self[c].get("_category.key_id")]))]) for c in self.get_loopable_cats()]) - def collect_keys(parent_cat,child_cats): - kk = [] - for child_cat in child_cats: - if child_cat in self.loop_expand_list: - kk += collect_keys(child_cat) - # add these keys to our list - kk += [listify(self[child_cat].get('_category_key.name',[self[child_cat].get('_category.key_id')]))] - self.cat_key_table[parent_cat] = self.cat_key_table[parent_cat] + kk - return kk - for k,v in self.loop_expand_list.items(): - collect_keys(k,v) - print('Keys for categories' + repr(self.cat_key_table)) - - def add_type_info(self): - if "_item_type_list.construct" in self.master_block: - types = self.master_block["_item_type_list.code"] - prim_types = self.master_block["_item_type_list.primitive_code"] - constructs = list([a + "$" for a in self.master_block["_item_type_list.construct"]]) - # add in \r wherever we see \n, and change \{ to \\{ - def regex_fiddle(mm_regex): - brack_match = r"((.*\[.+)(\\{)(.*\].*))" - ret_match = r"((.*\[.+)(\\n)(.*\].*))" - fixed_regexp = mm_regex[:] #copy - # fix the brackets - bm = re.match(brack_match,mm_regex) - if bm != None: - fixed_regexp = bm.expand(r"\2\\\\{\4") - # fix missing \r - rm = re.match(ret_match,fixed_regexp) - if rm != None: - fixed_regexp = rm.expand(r"\2\3\\r\4") - #print("Regexp %s becomes %s" % (mm_regex,fixed_regexp)) - return fixed_regexp - constructs = map(regex_fiddle,constructs) - for typecode,construct in zip(types,constructs): - self.typedic[typecode] = re.compile(construct,re.MULTILINE|re.DOTALL) - # now make a primitive <-> type construct mapping - for typecode,primtype in zip(types,prim_types): - self.primdic[typecode] = primtype - - def add_category_info(self,full=True): - if self.diclang == "DDLm": - catblocks = [c for c in self.keys() if self[c].get('_definition.scope')=='Category'] - looped_cats = [a for a in catblocks if self[a].get('_definition.class','Set') == 'Loop'] - self.parent_lookup = {} - for one_cat in looped_cats: - parent_cat = one_cat - parent_def = self[parent_cat] - next_up = parent_def['_name.category_id'].lower() - while next_up in self and self[next_up].get('_definition.class','Set') == 'Loop': - parent_def = self[next_up] - parent_cat = next_up - next_up = parent_def['_name.category_id'].lower() - self.parent_lookup[one_cat] = parent_cat - - if full: - self.key_equivs = {} - for one_cat in looped_cats: #follow them up - lower_keys = listify(self[one_cat]['_category_key.name']) - start_keys = lower_keys[:] - while len(lower_keys)>0: - this_cat = self[lower_keys[0]]['_name.category_id'] - parent = [a for a in looped_cats if self[this_cat]['_name.category_id'].lower()==a] - #print(Processing %s, keys %s, parent %s" % (this_cat,repr(lower_keys),repr(parent))) - if len(parent)>1: - raise CifError("Category %s has more than one parent: %s" % (one_cat,repr(parent))) - if len(parent)==0: break - parent = parent[0] - parent_keys = listify(self[parent]['_category_key.name']) - linked_keys = [self[l]["_name.linked_item_id"] for l in lower_keys] - # sanity check - if set(parent_keys) != set(linked_keys): - raise CifError("Parent keys and linked keys are different! %s/%s" % (parent_keys,linked_keys)) - # now add in our information - for parent,child in zip(linked_keys,start_keys): - self.key_equivs[child] = self.key_equivs.get(child,[])+[parent] - lower_keys = linked_keys #preserves order of start keys - - else: - self.parent_lookup = {} - self.key_equivs = {} - - def change_category_name(self,oldname,newname): - self.unlock() - """Change the category name from [[oldname]] to [[newname]]""" - if oldname not in self: - raise KeyError('Cannot rename non-existent category %s to %s' % (oldname,newname)) - if newname in self: - raise KeyError('Cannot rename %s to %s as %s already exists' % (oldname,newname,oldname)) - child_defs = self.ddlm_immediate_children(oldname) - self.rename(oldname,newname) #NB no name integrity checks - self[newname]['_name.object_id']=newname - self[newname]['_definition.id']=newname - for child_def in child_defs: - self[child_def]['_name.category_id'] = newname - if self[child_def].get('_definition.scope','Item')=='Item': - newid = self.create_catobj_name(newname,self[child_def]['_name.object_id']) - self[child_def]['_definition.id']=newid - self.rename(child_def,newid[1:]) #no underscore at the beginning - self.lock() - - def create_catobj_name(self,cat,obj): - """Combine category and object in approved fashion to create id""" - return ('_'+cat+'.'+obj) - - def change_category(self,itemname,catname): - """Move itemname into catname, return new handle""" - defid = self[itemname] - if defid['_name.category_id'].lower()==catname.lower(): - print('Already in category, no change') - return itemname - if catname not in self: #don't have it - print('No such category %s' % catname) - return itemname - self.unlock() - objid = defid['_name.object_id'] - defid['_name.category_id'] = catname - newid = itemname # stays the same for categories - if defid.get('_definition.scope','Item') == 'Item': - newid = self.create_catobj_name(catname,objid) - defid['_definition.id']= newid - self.rename(itemname,newid) - self.set_parent(catname,newid) - self.lock() - return newid - - def change_name(self,one_def,newobj): - """Change the object_id of one_def to newobj. This is not used for - categories, but can be used for dictionaries""" - if '_dictionary.title' not in self[one_def]: #a dictionary block - newid = self.create_catobj_name(self[one_def]['_name.category_id'],newobj) - self.unlock() - self.rename(one_def,newid) - self[newid]['_definition.id']=newid - self[newid]['_name.object_id']=newobj - else: - self.unlock() - newid = newobj - self.rename(one_def,newobj) - self[newid]['_dictionary.title'] = newid - self.lock() - return newid - - # Note that our semantic parent is given by catparent, but our syntactic parent is - # always just the root block - def add_category(self,catname,catparent=None,is_loop=True,allow_dangler=False): - """Add a new category to the dictionary with name [[catname]]. - If [[catparent]] is None, the category will be a child of - the topmost 'Head' category or else the top data block. If - [[is_loop]] is false, a Set category is created. If [[allow_dangler]] - is true, the parent category does not have to exist.""" - if catname in self: - raise CifError('Attempt to add existing category %s' % catname) - self.unlock() - syntactic_root = self.get_roots()[0][0] - if catparent is None: - semantic_root = [a for a in self.keys() if self[a].get('_definition.class',None)=='Head'] - if len(semantic_root)>0: - semantic_root = semantic_root[0] - else: - semantic_root = syntactic_root - else: - semantic_root = catparent - realname = super(CifDic,self).NewBlock(catname,parent=syntactic_root) - self.block_id_table[catname.lower()]=realname - self[catname]['_name.object_id'] = catname - if not allow_dangler or catparent is None: - self[catname]['_name.category_id'] = self[semantic_root]['_name.object_id'] - else: - self[catname]['_name.category_id'] = catparent - self[catname]['_definition.id'] = catname - self[catname]['_definition.scope'] = 'Category' - if is_loop: - self[catname]['_definition.class'] = 'Loop' - else: - self[catname]['_definition.class'] = 'Set' - self[catname]['_description.text'] = 'No definition provided' - self.lock() - return catname - - def add_definition(self,itemname,catparent,def_text='PLEASE DEFINE ME',allow_dangler=False): - """Add itemname to category [[catparent]]. If itemname contains periods, - all text before the final period is ignored. If [[allow_dangler]] is True, - no check for a parent category is made.""" - self.unlock() - if '.' in itemname: - objname = itemname.split('.')[-1] - else: - objname = itemname - objname = objname.strip('_') - if not allow_dangler and (catparent not in self or self[catparent]['_definition.scope']!='Category'): - raise CifError('No category %s in dictionary' % catparent) - fullname = '_'+catparent.lower()+'.'+objname - print('New name: %s' % fullname) - syntactic_root = self.get_roots()[0][0] - realname = super(CifDic,self).NewBlock(fullname, fix=False, parent=syntactic_root) #low-level change - # update our dictionary structures - self.block_id_table[fullname]=realname - self[fullname]['_definition.id']=fullname - self[fullname]['_name.object_id']=objname - self[fullname]['_name.category_id']=catparent - self[fullname]['_definition.class']='Datum' - self[fullname]['_description.text']=def_text - - def remove_definition(self,defname): - """Remove a definition from the dictionary.""" - if defname not in self: - return - if self[defname].get('_definition.scope')=='Category': - children = self.ddlm_immediate_children(defname) - [self.remove_definition(a) for a in children] - cat_id = self[defname]['_definition.id'].lower() - del self[defname] - - def get_cat_obj(self,name): - """Return (cat,obj) tuple. [[name]] must contain only a single period""" - cat,obj = name.split('.') - return (cat.strip('_'),obj) - - def get_name_by_cat_obj(self,category,object,give_default=False): - """Return the dataname corresponding to the given category and object""" - if category[0] == '_': #accidentally left in - true_cat = category[1:].lower() - else: - true_cat = category.lower() - try: - return self.cat_obj_lookup_table[(true_cat,object.lower())][0] - except KeyError: - if give_default: - return '_'+true_cat+'.'+object - raise KeyError('No such category,object in the dictionary: %s %s' % (true_cat,object)) - - - def WriteOut(self,**kwargs): - myblockorder = self.get_full_child_list() - self.set_grammar(self.grammar) - self.standard = 'Dic' - return super(CifDic,self).WriteOut(blockorder = myblockorder,**kwargs) - - def get_full_child_list(self): - """Return a list of definition blocks in order parent-child-child-child-parent-child...""" - top_block = self.get_roots()[0][0] - root_cat = [a for a in self.keys() if self[a].get('_definition.class','Datum')=='Head'] - if len(root_cat) == 1: - all_names = [top_block] + self.recurse_child_list(root_cat[0]) - unrooted = self.ddlm_danglers() - double_names = set(unrooted).intersection(set(all_names)) - if len(double_names)>0: - raise CifError('Names are children of internal and external categories:%s' % repr(double_names)) - remaining = unrooted[:] - for no_root in unrooted: - if self[no_root].get('_definition.scope','Item')=='Category': - all_names += [no_root] - remaining.remove(no_root) - these_children = [n for n in unrooted if self[n]['_name.category_id'].lower()==no_root.lower()] - all_names += these_children - [remaining.remove(n) for n in these_children] - # now sort by category - ext_cats = set([self[r].get('_name.category_id',self.cat_from_name(r)).lower() for r in remaining]) - for e in ext_cats: - cat_items = [r for r in remaining if self[r].get('_name.category_id',self.cat_from_name(r)).lower() == e] - [remaining.remove(n) for n in cat_items] - all_names += cat_items - if len(remaining)>0: - print('WARNING: following items do not seem to belong to a category??') - print(repr(remaining)) - all_names += remaining - print('Final block order: ' + repr(all_names)) - return all_names - raise ValueError('Dictionary contains no/multiple Head categories, please print as plain CIF instead') - - def cat_from_name(self,one_name): - """Guess the category from the name. This should be used only when this is not important semantic information, - for example, when printing out""" - (cat,obj) = one_name.split(".") - if cat[0] == "_": cat = cat[1:] - return cat - - def recurse_child_list(self,parentname): - """Recursively expand the logical child list of [[parentname]]""" - final_list = [parentname] - child_blocks = [a for a in self.child_table.keys() if self[a].get('_name.category_id','').lower() == parentname.lower()] - child_blocks.sort() #we love alphabetical order - child_items = [a for a in child_blocks if self[a].get('_definition.scope','Item') == 'Item'] - final_list += child_items - child_cats = [a for a in child_blocks if self[a].get('_definition.scope','Item') == 'Category'] - for child_cat in child_cats: - final_list += self.recurse_child_list(child_cat) - return final_list - - - - def get_key_pack(self,category,value,data): - keyname = self[category][self.unique_spec] - onepack = data.GetPackKey(keyname,value) - return onepack - - def get_number_with_esd(numstring): - import string - numb_re = '((-?(([0-9]*[.]([0-9]+))|([0-9]+)[.]?))([(][0-9]+[)])?([eEdD][+-]?[0-9]+)?)|(\?)|(\.)' - our_match = re.match(numb_re,numstring) - if our_match: - a,base_num,b,c,dad,dbd,esd,exp,q,dot = our_match.groups() - # print("Debug: {} -> {!r}".format(numstring, our_match.groups())) - else: - return None,None - if dot or q: return None,None #a dot or question mark - if exp: #has exponent - exp = exp.replace("d","e") # mop up old fashioned numbers - exp = exp.replace("D","e") - base_num = base_num + exp - # print("Debug: have %s for base_num from %s" % (base_num,numstring)) - base_num = float(base_num) - # work out esd, if present. - if esd: - esd = float(esd[1:-1]) # no brackets - if dad: # decimal point + digits - esd = esd * (10 ** (-1* len(dad))) - if exp: - esd = esd * (10 ** (float(exp[1:]))) - return base_num,esd - - def getmaxmin(self,rangeexp): - regexp = '(-?(([0-9]*[.]([0-9]+))|([0-9]+)[.]?)([eEdD][+-]?[0-9]+)?)*' - regexp = regexp + ":" + regexp - regexp = re.match(regexp,rangeexp) - try: - minimum = regexp.group(1) - maximum = regexp.group(7) - except AttributeError: - print("Can't match %s" % rangeexp) - if minimum == None: minimum = "." - else: minimum = float(minimum) - if maximum == None: maximum = "." - else: maximum = float(maximum) - return maximum,minimum - - def initialise_drel(self): - """Parse drel functions and prepare data structures in dictionary""" - self.ddlm_parse_valid() #extract validity information from data block - self.transform_drel() #parse the drel functions - self.add_drel_funcs() #put the drel functions into the namespace - - def transform_drel(self): - from .drel import drel_ast_yacc - from .drel import py_from_ast - import traceback - parser = drel_ast_yacc.parser - lexer = drel_ast_yacc.lexer - my_namespace = self.keys() - my_namespace = dict(zip(my_namespace,my_namespace)) - # we provide a table of loopable categories {cat_name:((key1,key2..),[item_name,...]),...}) - loopable_cats = self.get_loopable_cats() - loop_keys = [listify(self[a]["_category_key.name"]) for a in loopable_cats] - loop_keys = [[self[a]['_name.object_id'] for a in b] for b in loop_keys] - cat_names = [self.names_in_cat(a,names_only=True) for a in loopable_cats] - loop_info = dict(zip(loopable_cats,zip(loop_keys,cat_names))) - # parser.listable_items = [a for a in self.keys() if "*" in self[a].get("_type.dimension","")] - derivable_list = [a for a in self.keys() if "_method.expression" in self[a] \ - and self[a].get("_name.category_id","")!= "function"] - for derivable in derivable_list: - target_id = derivable - # reset the list of visible names for parser - special_ids = [dict(zip(self.keys(),self.keys()))] - print("Target id: %s" % derivable) - drel_exprs = self[derivable]["_method.expression"] - drel_purposes = self[derivable]["_method.purpose"] - all_methods = [] - if not isinstance(drel_exprs,list): - drel_exprs = [drel_exprs] - drel_purposes = [drel_purposes] - for drel_purpose,drel_expr in zip(drel_purposes,drel_exprs): - if drel_purpose != 'Evaluation': - continue - drel_expr = "\n".join(drel_expr.splitlines()) - # print("Transforming %s" % drel_expr) - # List categories are treated differently... - try: - meth_ast = parser.parse(drel_expr+"\n",lexer=lexer) - except: - print('Syntax error in method for %s; leaving as is' % derivable) - a,b = sys.exc_info()[:2] - print((repr(a),repr(b))) - print(traceback.print_tb(sys.exc_info()[-1],None,sys.stdout)) - # reset the lexer - lexer.begin('INITIAL') - continue - # Construct the python method - cat_meth = False - if self[derivable].get('_definition.scope','Item') == 'Category': - cat_meth = True - pyth_meth = py_from_ast.make_python_function(meth_ast,"pyfunc",target_id, - loopable=loop_info, - cif_dic = self,cat_meth=cat_meth) - all_methods.append(pyth_meth) - if len(all_methods)>0: - save_overwrite = self[derivable].overwrite - self[derivable].overwrite = True - self[derivable]["_method.py_expression"] = all_methods - self[derivable].overwrite = save_overwrite - #print("Final result:\n " + repr(self[derivable]["_method.py_expression"])) - - def add_drel_funcs(self): - from .drel import drel_ast_yacc - from .drel import py_from_ast - funclist = [a for a in self.keys() if self[a].get("_name.category_id","")=='function'] - funcnames = [(self[a]["_name.object_id"], - getattr(self[a].GetKeyedPacket("_method.purpose","Evaluation"),"_method.expression")) for a in funclist] - # create executable python code... - parser = drel_ast_yacc.parser - # we provide a table of loopable categories {cat_name:(key,[item_name,...]),...}) - loopable_cats = self.get_loopable_cats() - loop_keys = [listify(self[a]["_category_key.name"]) for a in loopable_cats] - loop_keys = [[self[a]['_name.object_id'] for a in b] for b in loop_keys] - cat_names = [self.names_in_cat(a,names_only=True) for a in loopable_cats] - loop_info = dict(zip(loopable_cats,zip(loop_keys,cat_names))) - for funcname,funcbody in funcnames: - newline_body = "\n".join(funcbody.splitlines()) - parser.target_id = funcname - res_ast = parser.parse(newline_body) - py_function = py_from_ast.make_python_function(res_ast,None,targetname=funcname,func_def=True,loopable=loop_info,cif_dic = self) - #print('dREL library function ->\n' + py_function) - global_table = globals() - exec(py_function, global_table) #add to namespace - #print('Globals after dREL functions added:' + repr(globals())) - self.ddlm_functions = globals() #for outside access - - @track_recursion - def derive_item(self,start_key,cifdata,store_value = False,allow_defaults=True): - key = start_key #starting value - result = None #success is a non-None value - default_result = False #we have not used a default value - # check for aliases - # check for an older form of a new value - found_it = [k for k in self.alias_table.get(key,[]) if k in cifdata] - if len(found_it)>0: - corrected_type = self.change_type(key,cifdata[found_it[0]]) - return corrected_type - # now do the reverse check - any alternative form - alias_name = [a for a in self.alias_table.items() if key in a[1]] - print('Aliases for %s: %s' % (key,repr(alias_name))) - if len(alias_name)==1: - key = alias_name[0][0] #actual definition name - if key in cifdata: return self.change_type(key,cifdata[key]) - found_it = [k for k in alias_name[0][1] if k in cifdata] - if len(found_it)>0: - return self.change_type(key,cifdata[found_it[0]]) - elif len(alias_name)>1: - raise CifError('Dictionary error: dataname alias appears in different definitions: ' + repr(alias_name)) - - the_category = self[key]["_name.category_id"] - cat_names = [a for a in self.keys() if self[a].get("_name.category_id",None)==the_category] - has_cat_names = [a for a in cat_names if cifdata.has_key_or_alias(a)] - # store any default value in case we have a problem - def_val = self[key].get("_enumeration.default","") - def_index_val = self[key].get("_enumeration.def_index_id","") - if len(has_cat_names)==0: # try category method - cat_result = {} - pulled_from_cats = [k for k in self.keys() if '_category_construct_local.components' in self[k]] - pulled_from_cats = [(k,[ - self[n]['_name.category_id'] for n in self[k]['_category_construct_local.components']] - ) for k in pulled_from_cats] - pulled_to_cats = [k[0] for k in pulled_from_cats if the_category in k[1]] - if '_category_construct_local.type' in self[the_category]: - print("**Now constructing category %s using DDLm attributes**" % the_category) - try: - cat_result = self.construct_category(the_category,cifdata,store_value=True) - except (CifRecursionError,StarFile.StarDerivationError): - print('** Failed to construct category %s (error)' % the_category) - # Trying a pull-back when the category is partially populated - # will not work, hence we test that cat_result has no keys - if len(pulled_to_cats)>0 and len(cat_result)==0: - print("**Now populating category %s from pulled-back category %s" % (the_category,repr(pulled_to_cats))) - try: - cat_result = self.push_from_pullback(the_category,pulled_to_cats,cifdata,store_value=True) - except (CifRecursionError,StarFile.StarDerivationError): - print('** Failed to construct category %s from pullback information (error)' % the_category) - if '_method.py_expression' in self[the_category] and key not in cat_result: - print("**Now applying category method for %s in search of %s**" % (the_category,key)) - cat_result = self.derive_item(the_category,cifdata,store_value=True) - print("**Tried pullbacks, obtained for %s " % the_category + repr(cat_result)) - # do we now have our value? - if key in cat_result: - return cat_result[key] - - # Recalculate in case it actually worked - has_cat_names = [a for a in cat_names if cifdata.has_key_or_alias(a)] - the_funcs = self[key].get('_method.py_expression',"") - if the_funcs: #attempt to calculate it - #global_table = globals() - #global_table.update(self.ddlm_functions) - for one_func in the_funcs: - print('Executing function for %s:' % key) - #print(one_func) - exec(one_func, globals()) #will access dREL functions, puts "pyfunc" in scope - # print('in following global environment: ' + repr(global_table)) - stored_setting = cifdata.provide_value - cifdata.provide_value = True - try: - result = pyfunc(cifdata) - except CifRecursionError as s: - print(s) - result = None - except StarFile.StarDerivationError as s: - print(s) - result = None - finally: - cifdata.provide_value = stored_setting - if result is not None: - break - #print("Function returned {!r}".format(result)) - - if result is None and allow_defaults: # try defaults - if def_val: - result = self.change_type(key,def_val) - default_result = True - elif def_index_val: #derive a default value - index_vals = self[key]["_enumeration_default.index"] - val_to_index = cifdata[def_index_val] #what we are keying on - if self[def_index_val]['_type.contents'] in ['Code','Name','Tag']: - lcase_comp = True - index_vals = [a.lower() for a in index_vals] - # Handle loops - if isinstance(val_to_index,list): - if lcase_comp: - val_to_index = [a.lower() for a in val_to_index] - keypos = [index_vals.index(a) for a in val_to_index] - result = [self[key]["_enumeration_default.value"][a] for a in keypos] - else: - if lcase_comp: - val_to_index = val_to_index.lower() - keypos = index_vals.index(val_to_index) #value error if no such value available - result = self[key]["_enumeration_default.value"][keypos] - default_result = True #flag that it must be extended - result = self.change_type(key,result) - print("Indexed on %s to get %s for %s" % (def_index_val,repr(result),repr(val_to_index))) - - # read it in - if result is None: #can't do anything else - print('Warning: no way of deriving item %s, allow_defaults is %s' % (key,repr(allow_defaults))) - raise StarFile.StarDerivationError(start_key) - is_looped = False - if self[the_category].get('_definition.class','Set')=='Loop': - is_looped = True - if len(has_cat_names)>0: #this category already exists - if result is None or default_result: #need to create a list of values - loop_len = len(cifdata[has_cat_names[0]]) - out_result = [result]*loop_len - result = out_result - else: #nothing exists in this category, we can't store this at all - print('Resetting result %s for %s to null list as category is empty' % (key,result)) - result = [] - - # now try to insert the new information into the right place - # find if items of this category already appear... - # Never cache empty values - if not (isinstance(result,list) and len(result)==0) and\ - store_value: - if self[key].get("_definition.scope","Item")=='Item': - if is_looped: - result = self.store_new_looped_value(key,cifdata,result,default_result) - else: - result = self.store_new_unlooped_value(key,cifdata,result) - else: - self.store_new_cat_values(cifdata,result,the_category) - return result - - def store_new_looped_value(self,key,cifdata,result,default_result): - """Store a looped value from the dREL system into a CifFile""" - # try to change any matrices etc. to lists - the_category = self[key]["_name.category_id"] - out_result = result - if result is not None and not default_result: - # find any numpy arrays - def conv_from_numpy(one_elem): - if not hasattr(one_elem,'dtype'): - if isinstance(one_elem,(list,tuple)): - return StarFile.StarList([conv_from_numpy(a) for a in one_elem]) - return one_elem - if one_elem.size > 1: #so is not a float - return StarFile.StarList([conv_from_numpy(a) for a in one_elem.tolist()]) - else: - try: - return one_elem.item(0) - except: - return one_elem - out_result = [conv_from_numpy(a) for a in result] - # so out_result now contains a value suitable for storage - cat_names = [a for a in self.keys() if self[a].get("_name.category_id",None)==the_category] - has_cat_names = [a for a in cat_names if a in cifdata] - print('Adding {}, found pre-existing names: '.format(key) + repr(has_cat_names)) - if len(has_cat_names)>0: #this category already exists - cifdata[key] = out_result #lengths must match or else!! - cifdata.AddLoopName(has_cat_names[0],key) - else: - cifdata[key] = out_result - cifdata.CreateLoop([key]) - print('Loop info:' + repr(cifdata.loops)) - return out_result - - def store_new_unlooped_value(self,key,cifdata,result): - """Store a single value from the dREL system""" - if result is not None and hasattr(result,'dtype'): - if result.size > 1: - out_result = StarFile.StarList(result.tolist()) - cifdata[key] = out_result - else: - cifdata[key] = result.item(0) - else: - cifdata[key] = result - return result - - def construct_category(self,category,cifdata,store_value=True): - """Construct a category using DDLm attributes""" - con_type = self[category].get('_category_construct_local.type',None) - if con_type == None: - return {} - if con_type == 'Pullback' or con_type == 'Filter': - morphisms = self[category]['_category_construct_local.components'] - morph_values = [cifdata[a] for a in morphisms] # the mapped values for each cat - cats = [self[a]['_name.category_id'] for a in morphisms] - cat_keys = [self[a]['_category.key_id'] for a in cats] - cat_values = [list(cifdata[a]) for a in cat_keys] #the category key values for each cat - if con_type == 'Filter': - int_filter = self[category].get('_category_construct_local.integer_filter',None) - text_filter = self[category].get('_category_construct_local.text_filter',None) - if int_filter is not None: - morph_values.append([int(a) for a in int_filter]) - if text_filter is not None: - morph_values.append(text_filter) - cat_values.append(range(len(morph_values[-1]))) - # create the mathematical product filtered by equality of dataname values - pullback_ids = [(x,y) for x in cat_values[0] for y in cat_values[1] \ - if morph_values[0][cat_values[0].index(x)]==morph_values[1][cat_values[1].index(y)]] - # now prepare for return - if len(pullback_ids)==0: - return {} - newids = self[category]['_category_construct_local.new_ids'] - fullnewids = [self.cat_obj_lookup_table[(category,n)][0] for n in newids] - if con_type == 'Pullback': - final_results = {fullnewids[0]:[x[0] for x in pullback_ids],fullnewids[1]:[x[1] for x in pullback_ids]} - final_results.update(self.duplicate_datanames(cifdata,cats[0],category,key_vals = final_results[fullnewids[0]],skip_names=newids)) - final_results.update(self.duplicate_datanames(cifdata,cats[1],category,key_vals = final_results[fullnewids[1]],skip_names=newids)) - elif con_type == 'Filter': #simple filter - final_results = {fullnewids[0]:[x[0] for x in pullback_ids]} - final_results.update(self.duplicate_datanames(cifdata,cats[0],category,key_vals = final_results[fullnewids[0]],skip_names=newids)) - if store_value: - self.store_new_cat_values(cifdata,final_results,category) - return final_results - - def push_from_pullback(self,target_category,source_categories,cifdata,store_value=True): - """Each of the categories in source_categories are pullbacks that include - the target_category""" - target_key = self[target_category]['_category.key_id'] - result = {target_key:[]} - first_time = True - # for each source category, determine which element goes to the target - for sc in source_categories: - components = self[sc]['_category_construct_local.components'] - comp_cats = [self[c]['_name.category_id'] for c in components] - new_ids = self[sc]['_category_construct_local.new_ids'] - source_ids = [self.cat_obj_lookup_table[(sc,n)][0] for n in new_ids] - if len(components) == 2: # not a filter - element_pos = comp_cats.index(target_category) - old_id = source_ids[element_pos] - print('Using %s to populate %s' % (old_id,target_key)) - result[target_key].extend(cifdata[old_id]) - # project through all identical names - extra_result = self.duplicate_datanames(cifdata,sc,target_category,skip_names=new_ids+[target_key]) - # we only include keys that are common to all categories - if first_time: - result.update(extra_result) - else: - for k in extra_result.keys(): - if k in result: - print('Updating %s: was %s' % (k,repr(result[k]))) - result[k].extend(extra_result[k]) - else: - extra_result = self.duplicate_datanames(cifdata,sc,target_category,skip_names=new_ids) - if len(extra_result)>0 or source_ids[0] in cifdata: #something is present - result[target_key].extend(cifdata[source_ids[0]]) - for k in extra_result.keys(): - if k in result: - print('Reverse filter: Updating %s: was %s' % (k,repr(result[k]))) - result[k].extend(extra_result[k]) - else: - result[k]=extra_result[k] - # Bonus derivation if there is a singleton filter - if self[sc]['_category_construct_local.type'] == 'Filter': - int_filter = self[sc].get('_category_construct_local.integer_filter',None) - text_filter = self[sc].get('_category_construct_local.text_filter',None) - if int_filter is not None: - filter_values = int_filter - else: - filter_values = text_filter - if len(filter_values)==1: #a singleton - extra_dataname = self[sc]['_category_construct_local.components'][0] - if int_filter is not None: - new_value = [int(filter_values[0])] * len(cifdata[source_ids[0]]) - else: - new_value = filter_values * len(cifdata[source_ids[0]]) - if extra_dataname not in result: - result[extra_dataname] = new_value - else: - result[extra_dataname].extend(new_value) - else: - raise ValueError('Unexpected category construct type' + self[sc]['_category_construct_local.type']) - first_time = False - # check for sanity - all dataname lengths must be identical - datalen = len(set([len(a) for a in result.values()])) - if datalen != 1: - raise AssertionError('Failed to construct equal-length category items,'+ repr(result)) - if store_value: - print('Now storing ' + repr(result)) - self.store_new_cat_values(cifdata,result,target_category) - return result - - def duplicate_datanames(self,cifdata,from_category,to_category,key_vals=None,skip_names=[]): - """Copy across datanames for which the from_category key equals [[key_vals]]""" - result = {} - s_names_in_cat = set(self.names_in_cat(from_category,names_only=True)) - t_names_in_cat = set(self.names_in_cat(to_category,names_only=True)) - can_project = s_names_in_cat & t_names_in_cat - can_project -= set(skip_names) #already dealt with - source_key = self[from_category]['_category.key_id'] - print('Source dataname set: ' + repr(s_names_in_cat)) - print('Target dataname set: ' + repr(t_names_in_cat)) - print('Projecting through following datanames from %s to %s' % (from_category,to_category) + repr(can_project)) - for project_name in can_project: - full_from_name = self.cat_obj_lookup_table[(from_category.lower(),project_name.lower())][0] - full_to_name = self.cat_obj_lookup_table[(to_category.lower(),project_name.lower())][0] - if key_vals is None: - try: - result[full_to_name] = cifdata[full_from_name] - except StarFile.StarDerivationError: - pass - else: - all_key_vals = cifdata[source_key] - filter_pos = [all_key_vals.index(a) for a in key_vals] - try: - all_data_vals = cifdata[full_from_name] - except StarFile.StarDerivationError: - pass - result[full_to_name] = [all_data_vals[i] for i in filter_pos] - return result - - def store_new_cat_values(self,cifdata,result,the_category): - """Store the values in [[result]] into [[cifdata]]""" - the_key = [a for a in result.keys() if self[a].get('_type.purpose','')=='Key'] - double_names = [a for a in result.keys() if a in cifdata] - if len(double_names)>0: - already_present = [a for a in self.names_in_cat(the_category) if a in cifdata] - if set(already_present) != set(result.keys()): - print("Category %s not updated, mismatched datanames: %s" % (the_category, repr(set(already_present)^set(result.keys())))) - return - #check key values - old_keys = set(cifdata[the_key]) - common_keys = old_keys & set(result[the_key]) - if len(common_keys)>0: - print("Category %s not updated, key values in common:" % (common_keys)) - return - #extend result values with old values - for one_name,one_value in result.items(): - result[one_name].extend(cifdata[one_name]) - for one_name, one_value in result.items(): - try: - self.store_new_looped_value(one_name,cifdata,one_value,False) - except StarFile.StarError: - print('%s: Not replacing %s with calculated %s' % (one_name,repr(cifdata[one_name]),repr(one_value))) - #put the key as the first item - print('Fixing item order for {}'.format(repr(the_key))) - for one_key in the_key: #should only be one - cifdata.ChangeItemOrder(one_key,0) - - - def generate_default_packet(self,catname,catkey,keyvalue): - """Return a StarPacket with items from ``catname`` and a key value - of ``keyvalue``""" - newpack = StarPacket() - for na in self.names_in_cat(catname): - def_val = self[na].get("_enumeration.default","") - if def_val: - final_val = self.change_type(na,def_val) - newpack.extend(final_val) - setattr(newpack,na,final_val) - if len(newpack)>0: - newpack.extend(keyvalue) - setattr(newpack,catkey,keyvalue) - return newpack - - - def switch_numpy(self,to_val): - pass - - def change_type(self,itemname,inval): - import numpy - if inval == "?": return inval - change_function = convert_type(self[itemname]) - if isinstance(inval,list) and not isinstance(inval,StarFile.StarList): #from a loop - newval = list([change_function(a) for a in inval]) - else: - newval = change_function(inval) - return newval - - def install_validation_functions(self): - """Install the DDL-appropriate validation checks""" - if self.diclang != 'DDLm': - self.item_validation_funs = [ - self.validate_item_type, - self.validate_item_esd, - self.validate_item_enum, # functions which check conformance - self.validate_enum_range, - self.validate_looping] - self.loop_validation_funs = [ - self.validate_loop_membership, - self.validate_loop_key, - self.validate_loop_references] # functions checking loop values - self.global_validation_funs = [ - self.validate_exclusion, - self.validate_parent, - self.validate_child, - self.validate_dependents, - self.validate_uniqueness] # where we need to look at other values - self.block_validation_funs = [ # where only a full block will do - self.validate_mandatory_category] - self.global_remove_validation_funs = [ - self.validate_remove_parent_child] # removal is quicker with special checks - elif self.diclang == 'DDLm': - self.item_validation_funs = [ - self.validate_item_enum, - self.validate_item_esd_ddlm, - ] - self.loop_validation_funs = [ - self.validate_looping_ddlm, - self.validate_loop_key_ddlm, - self.validate_loop_membership - ] - self.global_validation_funs = [] - self.block_validation_funs = [ - self.check_mandatory_items, - self.check_prohibited_items - ] - self.global_remove_validation_funs = [] - self.optimize = False # default value - self.done_parents = [] - self.done_children = [] - self.done_keys = [] - - def validate_item_type(self,item_name,item_value): - def mymatch(m,a): - res = m.match(a) - if res != None: return res.group() - else: return "" - target_type = self[item_name].get(self.type_spec) - if target_type == None: # e.g. a category definition - return {"result":True} # not restricted in any way - matchexpr = self.typedic[target_type] - item_values = listify(item_value) - #for item in item_values: - #print("Type match " + item_name + " " + item + ":",) - #skip dots and question marks - check_all = [a for a in item_values if a !="." and a != "?"] - check_all = [a for a in check_all if mymatch(matchexpr,a) != a] - if len(check_all)>0: return {"result":False,"bad_values":check_all} - else: return {"result":True} - - def decide(self,result_list): - """Construct the return list""" - if len(result_list)==0: - return {"result":True} - else: - return {"result":False,"bad_values":result_list} - - def validate_item_container(self, item_name,item_value): - container_type = self[item_name]['_type.container'] - item_values = listify(item_value) - if container_type == 'Single': - okcheck = [a for a in item_values if not isinstance(a,(int,float,long,unicode))] - return decide(okcheck) - if container_type in ('Multiple','List'): - okcheck = [a for a in item_values if not isinstance(a,StarList)] - return decide(okcheck) - if container_type == 'Array': #A list with numerical values - okcheck = [a for a in item_values if not isinstance(a,StarList)] - first_check = decide(okcheck) - if not first_check['result']: return first_check - #num_check = [a for a in item_values if len([b for b in a if not isinstance - - def validate_item_esd(self,item_name,item_value): - if self[item_name].get(self.primitive_type) != 'numb': - return {"result":None} - can_esd = self[item_name].get(self.esd_spec,"none") == "esd" - if can_esd: return {"result":True} #must be OK! - item_values = listify(item_value) - check_all = list([a for a in item_values if get_number_with_esd(a)[1] != None]) - if len(check_all)>0: return {"result":False,"bad_values":check_all} - return {"result":True} - - def validate_item_esd_ddlm(self,item_name,item_value): - if self[item_name].get('self.primitive_type') not in \ - ['Count','Index','Integer','Real','Imag','Complex','Binary','Hexadecimal','Octal']: - return {"result":None} - can_esd = True - if self[item_name].get('_type.purpose') != 'Measurand': - can_esd = False - item_values = listify(item_value) - check_all = [get_number_with_esd(a)[1] for a in item_values] - check_all = [v for v in check_all if (can_esd and v == None) or \ - (not can_esd and v != None)] - if len(check_all)>0: return {"result":False,"bad_values":check_all} - return {"result":True} - - def validate_enum_range(self,item_name,item_value): - if "_item_range.minimum" not in self[item_name] and \ - "_item_range.maximum" not in self[item_name]: - return {"result":None} - minvals = self[item_name].get("_item_range.minimum",default = ["."]) - maxvals = self[item_name].get("_item_range.maximum",default = ["."]) - def makefloat(a): - if a == ".": return a - else: return float(a) - maxvals = map(makefloat, maxvals) - minvals = map(makefloat, minvals) - rangelist = list(zip(minvals,maxvals)) - item_values = listify(item_value) - def map_check(rangelist,item_value): - if item_value == "?" or item_value == ".": return True - iv,esd = get_number_with_esd(item_value) - if iv==None: return None #shouldn't happen as is numb type - for lower,upper in rangelist: - #check the minima - if lower == ".": lower = iv - 1 - if upper == ".": upper = iv + 1 - if iv > lower and iv < upper: return True - if upper == lower and iv == upper: return True - # debug - # print("Value %s fails range check %d < x < %d" % (item_value,lower,upper)) - return False - check_all = [a for a in item_values if map_check(rangelist,a) != True] - if len(check_all)>0: return {"result":False,"bad_values":check_all} - else: return {"result":True} - - def validate_item_enum(self,item_name,item_value): - try: - enum_list = self[item_name][self.enum_spec][:] - except KeyError: - return {"result":None} - enum_list.append(".") #default value - enum_list.append("?") #unknown - item_values = listify(item_value) - #print("Enum check: {!r} in {!r}".format(item_values, enum_list)) - check_all = [a for a in item_values if a not in enum_list] - if len(check_all)>0: return {"result":False,"bad_values":check_all} - else: return {"result":True} - - def validate_looping(self,item_name,item_value): - try: - must_loop = self[item_name][self.must_loop_spec] - except KeyError: - return {"result":None} - if must_loop == 'yes' and isinstance(item_value,(unicode,str)): # not looped - return {"result":False} #this could be triggered - if must_loop == 'no' and not isinstance(item_value,(unicode,str)): - return {"result":False} - return {"result":True} - - def validate_looping_ddlm(self,loop_names): - """Check that all names are loopable""" - truly_loopy = self.get_final_cats(loop_names) - if len(truly_loopy)0: - return {"result":False,"bad_items":bad_items} - else: return {"result":True} - - def get_final_cats(self,loop_names): - """Return a list of the uppermost parent categories for the loop_names. Names - that are not from loopable categories are ignored.""" - try: - categories = [self[a][self.cat_spec].lower() for a in loop_names] - except KeyError: #category is mandatory - raise ValidCifError( "%s missing from dictionary %s for item in loop containing %s" % (self.cat_spec,self.dicname,loop_names[0])) - truly_looped = [a for a in categories if a in self.parent_lookup.keys()] - return [self.parent_lookup[a] for a in truly_looped] - - def validate_loop_key(self,loop_names): - category = self[loop_names[0]][self.cat_spec] - # find any unique values which must be present - key_spec = self[category].get(self.key_spec,[]) - for names_to_check in key_spec: - if isinstance(names_to_check,unicode): #only one - names_to_check = [names_to_check] - for loop_key in names_to_check: - if loop_key not in loop_names: - #is this one of those dang implicit items? - if self[loop_key].get(self.must_exist_spec,None) == "implicit": - continue #it is virtually there... - alternates = self.get_alternates(loop_key) - if alternates == []: - return {"result":False,"bad_items":loop_key} - for alt_names in alternates: - alt = [a for a in alt_names if a in loop_names] - if len(alt) == 0: - return {"result":False,"bad_items":loop_key} # no alternates - return {"result":True} - - def validate_loop_key_ddlm(self,loop_names): - """Make sure at least one of the necessary keys are available""" - final_cats = self.get_final_cats(loop_names) - if len(final_cats)>0: - poss_keys = self.cat_key_table[final_cats[0]] - found_keys = [a for a in poss_keys if a in loop_names] - if len(found_keys)>0: - return {"result":True} - else: - return {"result":False,"bad_items":poss_keys} - else: - return {"result":True} - - def validate_loop_references(self,loop_names): - must_haves = [self[a].get(self.list_ref_spec,None) for a in loop_names] - must_haves = [a for a in must_haves if a != None] - # build a flat list. For efficiency we don't remove duplicates,as - # we expect no more than the order of 10 or 20 looped names. - def flat_func(a,b): - if isinstance(b,unicode): - a.append(b) #single name - else: - a.extend(b) #list of names - return a - flat_mh = [] - [flat_func(flat_mh,a) for a in must_haves] - group_mh = filter(lambda a:a[-1]=="_",flat_mh) - single_mh = filter(lambda a:a[-1]!="_",flat_mh) - res = [a for a in single_mh if a not in loop_names] - def check_gr(s_item, name_list): - nl = map(lambda a:a[:len(s_item)],name_list) - if s_item in nl: return True - return False - res_g = [a for a in group_mh if check_gr(a,loop_names)] - if len(res) == 0 and len(res_g) == 0: return {"result":True} - # construct alternate list - alternates = map(lambda a: (a,self.get_alternates(a)),res) - alternates = [a for a in alternates if a[1] != []] - # next line purely for error reporting - missing_alts = [a[0] for a in alternates if a[1] == []] - if len(alternates) != len(res): - return {"result":False,"bad_items":missing_alts} #short cut; at least one - #doesn't have an altern - #loop over alternates - for orig_name,alt_names in alternates: - alt = [a for a in alt_names if a in loop_names] - if len(alt) == 0: return {"result":False,"bad_items":orig_name}# no alternates - return {"result":True} #found alternates - - def get_alternates(self,main_name,exclusive_only=False): - alternates = self[main_name].get(self.related_func,None) - alt_names = [] - if alternates != None: - alt_names = self[main_name].get(self.related_item,None) - if isinstance(alt_names,unicode): - alt_names = [alt_names] - alternates = [alternates] - together = zip(alt_names,alternates) - if exclusive_only: - alt_names = [a for a in together if a[1]=="alternate_exclusive" \ - or a[1]=="replace"] - else: - alt_names = [a for a in together if a[1]=="alternate" or a[1]=="replace"] - alt_names = list([a[0] for a in alt_names]) - # now do the alias thing - alias_names = listify(self[main_name].get("_item_aliases.alias_name",[])) - alt_names.extend(alias_names) - # print("Alternates for {}: {!r}".format(main_name, alt_names)) - return alt_names - - - def validate_exclusion(self,item_name,item_value,whole_block,provisional_items={},globals={}): - alternates = [a.lower() for a in self.get_alternates(item_name,exclusive_only=True)] - item_name_list = [a.lower() for a in whole_block.keys()] - item_name_list.extend([a.lower() for a in provisional_items.keys()]) - bad = [a for a in alternates if a in item_name_list] - if len(bad)>0: - print("Bad: %s, alternates %s" % (repr(bad),repr(alternates))) - return {"result":False,"bad_items":bad} - else: return {"result":True} - - # validate that parent exists and contains matching values - def validate_parent(self,item_name,item_value,whole_block,provisional_items={},globals={}): - parent_item = self[item_name].get(self.parent_spec) - if not parent_item: return {"result":None} #no parent specified - if isinstance(parent_item,list): - parent_item = parent_item[0] - if self.optimize: - if parent_item in self.done_parents: - return {"result":None} - else: - self.done_parents.append(parent_item) - print("Done parents %s" % repr(self.done_parents)) - # initialise parent/child values - if isinstance(item_value,unicode): - child_values = [item_value] - else: child_values = item_value[:] #copy for safety - # track down the parent - # print("Looking for {} parent item {} in {!r}".format(item_name, parent_item, whole_block)) - # if globals contains the parent values, we are doing a DDL2 dictionary, and so - # we have collected all parent values into the global block - so no need to search - # for them elsewhere. - # print("Looking for {!r}".format(parent_item)) - parent_values = globals.get(parent_item) - if not parent_values: - parent_values = provisional_items.get(parent_item,whole_block.get(parent_item)) - if not parent_values: - # go for alternates - namespace = whole_block.keys() - namespace.extend(provisional_items.keys()) - namespace.extend(globals.keys()) - alt_names = filter_present(self.get_alternates(parent_item),namespace) - if len(alt_names) == 0: - if len([a for a in child_values if a != "." and a != "?"])>0: - return {"result":False,"parent":parent_item}#no parent available -> error - else: - return {"result":None} #maybe True is more appropriate?? - parent_item = alt_names[0] #should never be more than one?? - parent_values = provisional_items.get(parent_item,whole_block.get(parent_item)) - if not parent_values: # check global block - parent_values = globals.get(parent_item) - if isinstance(parent_values,unicode): - parent_values = [parent_values] - #print("Checking parent %s against %s, values %r/%r" % (parent_item, - # item_name, parent_values, child_values)) - missing = self.check_parent_child(parent_values,child_values) - if len(missing) > 0: - return {"result":False,"bad_values":missing,"parent":parent_item} - return {"result":True} - - def validate_child(self,item_name,item_value,whole_block,provisional_items={},globals={}): - try: - child_items = self[item_name][self.child_spec][:] #copy - except KeyError: - return {"result":None} #not relevant - # special case for dictionaries -> we check parents of children only - if item_name in globals: #dictionary so skip - return {"result":None} - if isinstance(child_items,unicode): # only one child - child_items = [child_items] - if isinstance(item_value,unicode): # single value - parent_values = [item_value] - else: parent_values = item_value[:] - # expand child list with list of alternates - for child_item in child_items[:]: - child_items.extend(self.get_alternates(child_item)) - # now loop over the children - for child_item in child_items: - if self.optimize: - if child_item in self.done_children: - return {"result":None} - else: - self.done_children.append(child_item) - print("Done children %s" % repr(self.done_children)) - if child_item in provisional_items: - child_values = provisional_items[child_item][:] - elif child_item in whole_block: - child_values = whole_block[child_item][:] - else: continue - if isinstance(child_values,unicode): - child_values = [child_values] - # print("Checking child %s against %s, values %r/%r" % (child_item, - # item_name, child_values, parent_values)) - missing = self.check_parent_child(parent_values,child_values) - if len(missing)>0: - return {"result":False,"bad_values":missing,"child":child_item} - return {"result":True} #could mean that no child items present - - #a generic checker: all child vals should appear in parent_vals - def check_parent_child(self,parent_vals,child_vals): - # shield ourselves from dots and question marks - pv = parent_vals[:] - pv.extend([".","?"]) - res = [a for a in child_vals if a not in pv] - #print("Missing: %s" % res) - return res - - def validate_remove_parent_child(self,item_name,whole_block): - try: - child_items = self[item_name][self.child_spec] - except KeyError: - return {"result":None} - if isinstance(child_items,unicode): # only one child - child_items = [child_items] - for child_item in child_items: - if child_item in whole_block: - return {"result":False,"child":child_item} - return {"result":True} - - def validate_dependents(self,item_name,item_value,whole_block,prov={},globals={}): - try: - dep_items = self[item_name][self.dep_spec][:] - except KeyError: - return {"result":None} #not relevant - if isinstance(dep_items,unicode): - dep_items = [dep_items] - actual_names = whole_block.keys() - actual_names.extend(prov.keys()) - actual_names.extend(globals.keys()) - missing = [a for a in dep_items if a not in actual_names] - if len(missing) > 0: - alternates = map(lambda a:[self.get_alternates(a),a],missing) - # compact way to get a list of alternative items which are - # present - have_check = [(filter_present(b[0],actual_names), - b[1]) for b in alternates] - have_check = list([a for a in have_check if len(a[0])==0]) - if len(have_check) > 0: - have_check = [a[1] for a in have_check] - return {"result":False,"bad_items":have_check} - return {"result":True} - - def validate_uniqueness(self,item_name,item_value,whole_block,provisional_items={}, - globals={}): - category = self[item_name].get(self.cat_spec) - if category == None: - print("No category found for %s" % item_name) - return {"result":None} - # print("Category {!r} for item {}".format(category, item_name)) - # we make a copy in the following as we will be removing stuff later! - unique_i = self[category].get("_category_key.name",[])[:] - if isinstance(unique_i,unicode): - unique_i = [unique_i] - if item_name not in unique_i: #no need to verify - return {"result":None} - if isinstance(item_value,unicode): #not looped - return {"result":None} - # print("Checking %s -> %s -> %s ->Unique: %r" % (item_name,category,catentry, unique_i)) - # check that we can't optimize by not doing this check - if self.optimize: - if unique_i in self.done_keys: - return {"result":None} - else: - self.done_keys.append(unique_i) - val_list = [] - # get the matching data from any other data items - unique_i.remove(item_name) - other_data = [] - if len(unique_i) > 0: # i.e. do have others to think about - for other_name in unique_i: - # we look for the value first in the provisional dict, then the main block - # the logic being that anything in the provisional dict overrides the - # main block - if other_name in provisional_items: - other_data.append(provisional_items[other_name]) - elif other_name in whole_block: - other_data.append(whole_block[other_name]) - elif self[other_name].get(self.must_exist_spec)=="implicit": - other_data.append([item_name]*len(item_value)) #placeholder - else: - return {"result":False,"bad_items":other_name}#missing data name - # ok, so we go through all of our values - # this works by comparing lists of strings to one other, and - # so could be fooled if you think that '1.' and '1' are - # identical - for i in range(len(item_value)): - #print("Value no. %d" % i, end=" ") - this_entry = item_value[i] - for j in range(len(other_data)): - this_entry = " ".join([this_entry,other_data[j][i]]) - #print("Looking for {!r} in {!r}: ".format(this_entry, val_list)) - if this_entry in val_list: - return {"result":False,"bad_values":this_entry} - val_list.append(this_entry) - return {"result":True} - - - def validate_mandatory_category(self,whole_block): - mand_cats = [self[a]['_category.id'] for a in self.keys() if self[a].get("_category.mandatory_code","no")=="yes"] - if len(mand_cats) == 0: - return {"result":True} - # print("Mandatory categories - {!r}".format(mand_cats) - # find which categories each of our datanames belongs to - all_cats = [self[a].get(self.cat_spec) for a in whole_block.keys()] - missing = set(mand_cats) - set(all_cats) - if len(missing) > 0: - return {"result":False,"bad_items":repr(missing)} - return {"result":True} - - def check_mandatory_items(self,whole_block,default_scope='Item'): - """Return an error if any mandatory items are missing""" - if len(self.scopes_mandatory)== 0: return {"result":True} - if default_scope == 'Datablock': - return {"result":True} #is a data file - scope = whole_block.get('_definition.scope',default_scope) - if '_dictionary.title' in whole_block: - scope = 'Dictionary' - missing = list([a for a in self.scopes_mandatory[scope] if a not in whole_block]) - if len(missing)==0: - return {"result":True} - else: - return {"result":False,"bad_items":missing} - - def check_prohibited_items(self,whole_block,default_scope='Item'): - """Return an error if any prohibited items are present""" - if len(self.scopes_naughty)== 0: return {"result":True} - if default_scope == 'Datablock': - return {"result":True} #is a data file - scope = whole_block.get('_definition.scope',default_scope) - if '_dictionary.title' in whole_block: - scope = 'Dictionary' - present = list([a for a in self.scopes_naughty[scope] if a in whole_block]) - if len(present)==0: - return {"result":True} - else: - return {"result":False,"bad_items":present} - - - def run_item_validation(self,item_name,item_value): - return {item_name:list([(f.__name__,f(item_name,item_value)) for f in self.item_validation_funs])} - - def run_loop_validation(self,loop_names): - return {loop_names[0]:list([(f.__name__,f(loop_names)) for f in self.loop_validation_funs])} - - def run_global_validation(self,item_name,item_value,data_block,provisional_items={},globals={}): - results = list([(f.__name__,f(item_name,item_value,data_block,provisional_items,globals)) for f in self.global_validation_funs]) - return {item_name:results} - - def run_block_validation(self,whole_block,block_scope='Item'): - results = list([(f.__name__,f(whole_block)) for f in self.block_validation_funs]) - # fix up the return values - return {"whole_block":results} - - def optimize_on(self): - self.optimize = True - self.done_keys = [] - self.done_children = [] - self.done_parents = [] - - def optimize_off(self): - self.optimize = False - self.done_keys = [] - self.done_children = [] - self.done_parents = [] - - - -class ValidCifBlock(CifBlock): - """A `CifBlock` that is valid with respect to a given CIF dictionary. Methods - of `CifBlock` are overridden where necessary to disallow addition of invalid items to the - `CifBlock`. - - ## Initialisation - - * `dic` is a `CifDic` object to be used for validation. - - """ - def __init__(self,dic = None, diclist=[], mergemode = "replace",*args,**kwords): - CifBlock.__init__(self,*args,**kwords) - if dic and diclist: - print("Warning: diclist argument ignored when initialising ValidCifBlock") - if isinstance(dic,CifDic): - self.fulldic = dic - else: - raise TypeError( "ValidCifBlock passed non-CifDic type in dic argument") - if len(diclist)==0 and not dic: - raise ValidCifError( "At least one dictionary must be specified") - if diclist and not dic: - self.fulldic = merge_dic(diclist,mergemode) - if not self.run_data_checks()[0]: - raise ValidCifError( self.report()) - - def run_data_checks(self,verbose=False): - self.v_result = {} - self.fulldic.optimize_on() - for dataname in self.keys(): - update_value(self.v_result,self.fulldic.run_item_validation(dataname,self[dataname])) - update_value(self.v_result,self.fulldic.run_global_validation(dataname,self[dataname],self)) - for loop_names in self.loops.values(): - update_value(self.v_result,self.fulldic.run_loop_validation(loop_names)) - # now run block-level checks - update_value(self.v_result,self.fulldic.run_block_validation(self)) - # return false and list of baddies if anything didn't match - self.fulldic.optimize_off() - all_keys = list(self.v_result.keys()) #dictionary will change - for test_key in all_keys: - #print("%s: %r" % (test_key, self.v_result[test_key])) - self.v_result[test_key] = [a for a in self.v_result[test_key] if a[1]["result"]==False] - if len(self.v_result[test_key]) == 0: - del self.v_result[test_key] - isvalid = len(self.v_result)==0 - #if not isvalid: - # print("Baddies: {!r}".format(self.v_result)) - return isvalid,self.v_result - - def single_item_check(self,item_name,item_value): - #self.match_single_item(item_name) - if item_name not in self.fulldic: - result = {item_name:[]} - else: - result = self.fulldic.run_item_validation(item_name,item_value) - baddies = list([a for a in result[item_name] if a[1]["result"]==False]) - # if even one false one is found, this should trigger - isvalid = (len(baddies) == 0) - # if not isvalid: print("Failures for {}: {!r}".format(item_name, baddies)) - return isvalid,baddies - - def loop_item_check(self,loop_names): - in_dic_names = list([a for a in loop_names if a in self.fulldic]) - if len(in_dic_names)==0: - result = {loop_names[0]:[]} - else: - result = self.fulldic.run_loop_validation(in_dic_names) - baddies = list([a for a in result[in_dic_names[0]] if a[1]["result"]==False]) - # if even one false one is found, this should trigger - isvalid = (len(baddies) == 0) - # if not isvalid: print("Failures for {}: {!r}".format(loop_names, baddies)) - return isvalid,baddies - - def global_item_check(self,item_name,item_value,provisional_items={}): - if item_name not in self.fulldic: - result = {item_name:[]} - else: - result = self.fulldic.run_global_validation(item_name, - item_value,self,provisional_items = provisional_items) - baddies = list([a for a in result[item_name] if a[1]["result"] is False]) - # if even one false one is found, this should trigger - isvalid = (len(baddies) == 0) - # if not isvalid: print("Failures for {}: {!r}".format(item_name, baddies)) - return isvalid,baddies - - def remove_global_item_check(self,item_name): - if item_name not in self.fulldic: - result = {item_name:[]} - else: - result = self.fulldic.run_remove_global_validation(item_name,self,False) - baddies = list([a for a in result[item_name] if a[1]["result"]==False]) - # if even one false one is found, this should trigger - isvalid = (len(baddies) == 0) - # if not isvalid: print("Failures for {}: {!r}".format(item_name, baddies)) - return isvalid,baddies - - def AddToLoop(self,dataname,loopdata): - # single item checks - paired_data = loopdata.items() - for name,value in paired_data: - valid,problems = self.single_item_check(name,value) - self.report_if_invalid(valid,problems) - # loop item checks; merge with current loop - found = 0 - for aloop in self.block["loops"]: - if dataname in aloop: - loopnames = aloop.keys() - for new_name in loopdata.keys(): - if new_name not in loopnames: loopnames.append(new_name) - valid,problems = self.looped_item_check(loopnames) - self.report_if_invalid(valid,problems) - prov_dict = loopdata.copy() - for name,value in paired_data: - del prov_dict[name] # remove temporarily - valid,problems = self.global_item_check(name,value,prov_dict) - prov_dict[name] = value # add back in - self.report_if_invalid(valid,problems) - CifBlock.AddToLoop(self,dataname,loopdata) - - def AddCifItem(self,data): - if isinstance(data[0],(unicode,str)): # single item - valid,problems = self.single_item_check(data[0],data[1]) - self.report_if_invalid(valid,problems,data[0]) - valid,problems = self.global_item_check(data[0],data[1]) - self.report_if_invalid(valid,problems,data[0]) - elif isinstance(data[0],tuple) or isinstance(data[0],list): - paired_data = list(zip(data[0],data[1])) - for name,value in paired_data: - valid,problems = self.single_item_check(name,value) - self.report_if_invalid(valid,problems,name) - valid,problems = self.loop_item_check(data[0]) - self.report_if_invalid(valid,problems,data[0]) - prov_dict = {} # for storing temporary items - for name,value in paired_data: prov_dict[name]=value - for name,value in paired_data: - del prov_dict[name] # remove temporarily - valid,problems = self.global_item_check(name,value,prov_dict) - prov_dict[name] = value # add back in - self.report_if_invalid(valid,problems,name) - else: - raise ValueError("Programming error: AddCifItem passed non-tuple,non-string item") - super(ValidCifBlock,self).AddCifItem(data) - - def AddItem(self,key,value,**kwargs): - """Set value of dataname `key` to `value` after checking for conformance with CIF dictionary""" - valid,problems = self.single_item_check(key,value) - self.report_if_invalid(valid,problems,key) - valid,problems = self.global_item_check(key,value) - self.report_if_invalid(valid,problems,key) - super(ValidCifBlock,self).AddItem(key,value,**kwargs) - - # utility function - def report_if_invalid(self,valid,bad_list,data_name): - if not valid: - bad_tests = [a[0] for a in bad_list] - error_string = ",".join(bad_tests) - error_string = repr(data_name) + " fails following validity checks: " + error_string - raise ValidCifError( error_string) - - def __delitem__(self,key): - # we don't need to run single item checks; we do need to run loop and - # global checks. - if key in self: - try: - loop_items = self.GetLoop(key) - except TypeError: - loop_items = [] - if loop_items: #need to check loop conformance - loop_names = [a[0] for a in loop_items if a[0] != key] - valid,problems = self.loop_item_check(loop_names) - self.report_if_invalid(valid,problems) - valid,problems = self.remove_global_item_check(key) - self.report_if_invalid(valid,problems) - self.RemoveCifItem(key) - - - def report(self): - outstr = StringIO() - outstr.write( "Validation results\n") - outstr.write( "------------------\n") - print("%d invalid items found\n" % len(self.v_result)) - for item_name,val_func_list in self.v_result.items(): - outstr.write("%s fails following tests:\n" % item_name) - for val_func in val_func_list: - outstr.write("\t%s\n") - return outstr.getvalue() - - -class ValidCifFile(CifFile): - """A CIF file for which all datablocks are valid. Argument `dic` to - initialisation specifies a `CifDic` object to use for validation.""" - def __init__(self,dic=None,diclist=[],mergemode="replace",*args,**kwargs): - if not diclist and not dic and not hasattr(self,'bigdic'): - raise ValidCifError( "At least one dictionary is required to create a ValidCifFile object") - if not dic and diclist: #merge here for speed - self.bigdic = merge_dic(diclist,mergemode) - elif dic and not diclist: - self.bigdic = dic - CifFile.__init__(self,*args,**kwargs) - for blockname in self.keys(): - self.dictionary[blockname]=ValidCifBlock(data=self.dictionary[blockname],dic=self.bigdic) - - def NewBlock(self,blockname,blockcontents,**kwargs): - CifFile.NewBlock(self,blockname,blockcontents,**kwargs) - # dictionary[blockname] is now a CifBlock object. We - # turn it into a ValidCifBlock object - self.dictionary[blockname] = ValidCifBlock(dic=self.bigdic, - data=self.dictionary[blockname]) - - -class ValidationResult: - """Represents validation result. It is initialised with """ - def __init__(self,results): - """results is return value of validate function""" - self.valid_result, self.no_matches = results - - def report(self,use_html): - """Return string with human-readable description of validation result""" - return validate_report((self.valid_result, self.no_matches),use_html) - - def is_valid(self,block_name=None): - """Return True for valid CIF file, otherwise False""" - if block_name is not None: - block_names = [block_name] - else: - block_names = self.valid_result.iterkeys() - for block_name in block_names: - if not self.valid_result[block_name] == (True,{}): - valid = False - break - else: - valid = True - return valid - - def has_no_match_items(self,block_name=None): - """Return true if some items are not found in dictionary""" - if block_name is not None: - block_names = [block_name] - else: - block_names = self.no_matches.iter_keys() - for block_name in block_names: - if self.no_matches[block_name]: - has_no_match_items = True - break - else: - has_no_match_items = False - return has_no_match_items - - - -def Validate(ciffile,dic = "", diclist=[],mergemode="replace",isdic=False): - """Validate the `ciffile` conforms to the definitions in `CifDic` object `dic`, or if `dic` is missing, - to the results of merging the `CifDic` objects in `diclist` according to `mergemode`. Flag - `isdic` indicates that `ciffile` is a CIF dictionary meaning that save frames should be - accessed for validation and that mandatory_category should be interpreted differently for DDL2.""" - if not isinstance(ciffile,CifFile): - check_file = CifFile(ciffile) - else: - check_file = ciffile - if not dic: - fulldic = merge_dic(diclist,mergemode) - else: - fulldic = dic - no_matches = {} - valid_result = {} - if isdic: #assume one block only - check_file.scoping = 'instance' #only data blocks visible - top_level = check_file.keys()[0] - check_file.scoping = 'dictionary' #all blocks visible - # collect a list of parents for speed - if fulldic.diclang == 'DDL2': - poss_parents = fulldic.get_all("_item_linked.parent_name") - for parent in poss_parents: - curr_parent = listify(check_file.get(parent,[])) - new_vals = check_file.get_all(parent) - new_vals.extend(curr_parent) - if len(new_vals)>0: - check_file[parent] = new_vals - print("Added %s (len %d)" % (parent,len(check_file[parent]))) - # now run the validations - for block in check_file.keys(): - if isdic and block == top_level: - block_scope = 'Dictionary' - elif isdic: - block_scope = 'Item' - else: - block_scope = 'Datablock' - no_matches[block] = [a for a in check_file[block].keys() if a not in fulldic] - # remove non-matching items - print("Not matched: " + repr(no_matches[block])) - for nogood in no_matches[block]: - del check_file[block][nogood] - print("Validating block %s, scope %s" % (block,block_scope)) - valid_result[block] = run_data_checks(check_file[block],fulldic,block_scope=block_scope) - return valid_result,no_matches - -def validate_report(val_result,use_html=False): - valid_result,no_matches = val_result - outstr = StringIO() - if use_html: - outstr.write("

Validation results

") - else: - outstr.write( "Validation results\n") - outstr.write( "------------------\n") - if len(valid_result) > 10: - suppress_valid = True #don't clutter with valid messages - if use_html: - outstr.write("

For brevity, valid blocks are not reported in the output.

") - else: - suppress_valid = False - for block in valid_result.keys(): - block_result = valid_result[block] - if block_result[0]: - out_line = "Block '%s' is VALID" % block - else: - out_line = "Block '%s' is INVALID" % block - if use_html: - if (block_result[0] and (not suppress_valid or len(no_matches[block])>0)) or not block_result[0]: - outstr.write( "

%s

" % out_line) - else: - outstr.write( "\n %s\n" % out_line) - if len(no_matches[block])!= 0: - if use_html: - outstr.write( "

The following items were not found in the dictionary") - outstr.write(" (note that this does not invalidate the data block):

") - outstr.write("

\n") - [outstr.write("" % it) for it in no_matches[block]] - outstr.write("
%s
\n") - else: - outstr.write( "\n The following items were not found in the dictionary:\n") - outstr.write("Note that this does not invalidate the data block\n") - [outstr.write("%s\n" % it) for it in no_matches[block]] - # now organise our results by type of error, not data item... - error_type_dic = {} - for error_item, error_list in block_result[1].items(): - for func_name,bad_result in error_list: - bad_result.update({"item_name":error_item}) - try: - error_type_dic[func_name].append(bad_result) - except KeyError: - error_type_dic[func_name] = [bad_result] - # make a table of test name, test message - info_table = {\ - 'validate_item_type':\ - "The following data items had badly formed values", - 'validate_item_esd':\ - "The following data items should not have esds appended", - 'validate_enum_range':\ - "The following data items have values outside permitted range", - 'validate_item_enum':\ - "The following data items have values outside permitted set", - 'validate_looping':\ - "The following data items violate looping constraints", - 'validate_loop_membership':\ - "The following looped data names are of different categories to the first looped data name", - 'validate_loop_key':\ - "A required dataname for this category is missing from the loop\n containing the dataname", - 'validate_loop_key_ddlm':\ - "A loop key is missing for the category containing the dataname", - 'validate_loop_references':\ - "A dataname required by the item is missing from the loop", - 'validate_parent':\ - "A parent dataname is missing or contains different values", - 'validate_child':\ - "A child dataname contains different values to the parent", - 'validate_uniqueness':\ - "One or more data items do not take unique values", - 'validate_dependents':\ - "A dataname required by the item is missing from the data block", - 'validate_exclusion': \ - "Both dataname and exclusive alternates or aliases are present in data block", - 'validate_mandatory_category':\ - "A required category is missing from this block", - 'check_mandatory_items':\ - "A required data attribute is missing from this block", - 'check_prohibited_items':\ - "A prohibited data attribute is present in this block"} - - for test_name,test_results in error_type_dic.items(): - if use_html: - outstr.write(html_error_report(test_name,info_table[test_name],test_results)) - else: - outstr.write(error_report(test_name,info_table[test_name],test_results)) - outstr.write("\n\n") - return outstr.getvalue() - -# A function to lay out a single error report. We are passed -# the name of the error (one of our validation functions), the -# explanation to print out, and a dictionary with the error -# information. We print no more than 50 characters of the item - -def error_report(error_name,error_explanation,error_dics): - retstring = "\n\n " + error_explanation + ":\n\n" - headstring = "%-32s" % "Item name" - bodystring = "" - if "bad_values" in error_dics[0]: - headstring += "%-20s" % "Bad value(s)" - if "bad_items" in error_dics[0]: - headstring += "%-20s" % "Bad dataname(s)" - if "child" in error_dics[0]: - headstring += "%-20s" % "Child" - if "parent" in error_dics[0]: - headstring += "%-20s" % "Parent" - headstring +="\n" - for error in error_dics: - bodystring += "\n%-32s" % error["item_name"] - if "bad_values" in error: - out_vals = [repr(a)[:50] for a in error["bad_values"]] - bodystring += "%-20s" % out_vals - if "bad_items" in error: - bodystring += "%-20s" % repr(error["bad_items"]) - if "child" in error: - bodystring += "%-20s" % repr(error["child"]) - if "parent" in error: - bodystring += "%-20s" % repr(error["parent"]) - return retstring + headstring + bodystring - -# This lays out an HTML error report - -def html_error_report(error_name,error_explanation,error_dics,annotate=[]): - retstring = "

" + error_explanation + ":

" - retstring = retstring + "" - headstring = "" - bodystring = "" - if "bad_values" in error_dics[0]: - headstring += "" - if "bad_items" in error_dics[0]: - headstring += "" - if "child" in error_dics[0]: - headstring += "" - if "parent" in error_dics[0]: - headstring += "" - headstring +="\n" - for error in error_dics: - bodystring += "" % error["item_name"] - if "bad_values" in error: - bodystring += "" % error["bad_values"] - if "bad_items" in error: - bodystring += "" % error["bad_items"] - if "child" in error: - bodystring += "" % error["child"] - if "parent" in error: - bodystring += "" % error["parent"] - bodystring += "\n" - return retstring + headstring + bodystring + "
Item nameBad value(s)Bad dataname(s)ChildParent
%s%s%s%s%s
\n" - -def run_data_checks(check_block,fulldic,block_scope='Item'): - v_result = {} - for key in check_block.keys(): - update_value(v_result, fulldic.run_item_validation(key,check_block[key])) - update_value(v_result, fulldic.run_global_validation(key,check_block[key],check_block)) - for loopnames in check_block.loops.values(): - update_value(v_result, fulldic.run_loop_validation(loopnames)) - update_value(v_result,fulldic.run_block_validation(check_block,block_scope=block_scope)) - # return false and list of baddies if anything didn't match - all_keys = list(v_result.keys()) - for test_key in all_keys: - v_result[test_key] = [a for a in v_result[test_key] if a[1]["result"]==False] - if len(v_result[test_key]) == 0: - del v_result[test_key] - # if even one false one is found, this should trigger - # print("Baddies: {!r}".format(v_result)) - isvalid = len(v_result)==0 - return isvalid,v_result - - -def get_number_with_esd(numstring): - import string - numb_re = '((-?(([0-9]*[.]([0-9]+))|([0-9]+)[.]?))([(][0-9]+[)])?([eEdD][+-]?[0-9]+)?)|(\?)|(\.)' - our_match = re.match(numb_re,numstring) - if our_match: - a,base_num,b,c,dad,dbd,esd,exp,q,dot = our_match.groups() - # print("Debug: {} -> {!r}".format(numstring, our_match.groups())) - else: - return None,None - if dot or q: return None,None #a dot or question mark - if exp: #has exponent - exp = exp.replace("d","e") # mop up old fashioned numbers - exp = exp.replace("D","e") - base_num = base_num + exp - # print("Debug: have %s for base_num from %s" % (base_num,numstring)) - base_num = float(base_num) - # work out esd, if present. - if esd: - esd = float(esd[1:-1]) # no brackets - if dad: # decimal point + digits - esd = esd * (10 ** (-1* len(dad))) - if exp: - esd = esd * (10 ** (float(exp[1:]))) - return base_num,esd - -def float_with_esd(inval): - if isinstance(inval,unicode): - j = inval.find("(") - if j>=0: return float(inval[:j]) - return float(inval) - - - -def convert_type(definition): - """Convert value to have the type given by definition""" - #extract the actual required type information - container = definition['_type.container'] - dimension = definition.get('_type.dimension',StarFile.StarList([])) - structure = interpret_structure(definition['_type.contents']) - if container == 'Single': #a single value to convert - return convert_single_value(structure) - elif container == 'List': #lots of the same value - return convert_list_values(structure,dimension) - elif container == 'Multiple': #no idea - return None - elif container in ('Array','Matrix'): #numpy array - return convert_matrix_values(structure) - return lambda a:a #unable to convert - -def convert_single_value(type_spec): - """Convert a single item according to type_spec""" - if type_spec == 'Real': - return float_with_esd - if type_spec in ('Count','Integer','Index','Binary','Hexadecimal','Octal'): - return int - if type_spec == 'Complex': - return complex - if type_spec == 'Imag': - return lambda a:complex(0,a) - if type_spec in ('Code','Name','Tag'): #case-insensitive -> lowercase - return lambda a:a.lower() - return lambda a:a #can't do anything numeric - -#def convert_list_values(structure,dimension): -# """Convert the values according to the element -# structure given in [[structure]]""" -# if isinstance(structure,(unicode,str)): #simple repetition -# func_def = "element_convert = convert_single_value('%s')" % structure -# else: -# func_def = "def element_convert(element):\n" -# func_def += " final_val = []\n" -# for pos_no in range(len(structure)): -# func_def += " final_val.append(" -# type_spec = structure[pos_no] -# if type_spec == 'Real': -# cf = "float_with_esd(" -# elif type_spec in ('Count','Integer','Index','Binary','Hexadecimal','Octal'): -# cf = 'int(' -# elif type_spec == 'Complex': -# cf = 'complex(' -# elif type_spec == 'Imag': -# cf = 'complex(0,' -# elif type_spec in ('Code','Name','Tag'): -# cf = '(' -# else: cf = '' -# func_def += cf -# func_def += "element[%d]" % pos_no -# if "(" in cf: func_def +=")" -# if type_spec in ('Code','Name','Tag'): -# func_def +=".lower()" -# func_def +=")\n" # close append -# func_def += " return final_val\n" -# print(func_def) -# exec(func_def, globals()) #(re)defines element_convert in global namespace -# if len(dimension)> 0 and int(dimension[0]) != 1: -# return lambda a: list(map(element_convert,a)) -# else: return element_convert -# -#def convert_matrix_values(valtype): -# """Convert a dREL String or Float valued List structure to a numpy matrix structure""" -# # first convert to numpy array, then let numpy do the work -# try: import numpy -# except: -# return lambda a:a #cannot do it -# func_def = "def matrix_convert(a):\n" -# func_def += " import numpy\n" -# func_def += " p = numpy.array(a)\n" -# if valtype == 'Real': -# func_def+= " return p.astype('float')\n" -# elif valtype == 'Integer': -# func_def +=" return p.astype('int')\n" -# elif valtype == 'Complex': -# func_def +=" return p.astype('complex')\n" -# else: -# raise ValueError('Unknown matrix value type') -# exec(func_def,globals()) #matrix convert is defined -# return matrix_convert - -def interpret_structure(struc_spec): - """Interpret a DDLm structure specification""" - from . import TypeContentsParser as t - p = t.TypeParser(t.TypeParserScanner(struc_spec)) - return getattr(p,"input")() - - -# A utility function to append to item values rather than replace them -def update_value(base_dict,new_items): - for new_key in new_items.keys(): - if new_key in base_dict: - base_dict[new_key].extend(new_items[new_key]) - else: - base_dict[new_key] = new_items[new_key] - -#Transpose the list of lists passed to us -def transpose(base_list): - new_lofl = [] - full_length = len(base_list) - opt_range = range(full_length) - for i in range(len(base_list[0])): - new_packet = [] - for j in opt_range: - new_packet.append(base_list[j][i]) - new_lofl.append(new_packet) - return new_lofl - -# listify strings - used surprisingly often -def listify(item): - if isinstance(item,(unicode,str)): return [item] - else: return item - -# given a list of search items, return a list of items -# actually contained in the given data block -def filter_present(namelist,datablocknames): - return [a for a in namelist if a in datablocknames] - -# Make an item immutable, used if we want a list to be a key -def make_immutable(values): - """Turn list of StarList values into a list of immutable items""" - if not isinstance(values[0],StarList): - return values - else: - return [tuple(a) for a in values] - -# merge ddl dictionaries. We should be passed filenames or CifFile -# objects -def merge_dic(diclist,mergemode="replace",ddlspec=None): - dic_as_cif_list = [] - for dic in diclist: - if not isinstance(dic,CifFile) and \ - not isinstance(dic,(unicode,str)): - raise TypeError("Require list of CifFile names/objects for dictionary merging") - if not isinstance(dic,CifFile): dic_as_cif_list.append(CifFile(dic)) - else: dic_as_cif_list.append(dic) - # we now merge left to right - basedic = dic_as_cif_list[0] - if "on_this_dictionary" in basedic: #DDL1 style only - for dic in dic_as_cif_list[1:]: - basedic.merge(dic,mode=mergemode,match_att=["_name"]) - elif len(basedic.keys()) == 1: #One block: DDL2/m style - old_block = basedic[basedic.keys()[0]] - for dic in dic_as_cif_list[1:]: - new_block = dic[dic.keys()[0]] - basedic.merge(dic,mode=mergemode, - single_block=[basedic.keys()[0],dic.keys()[0]], - match_att=["_item.name"],match_function=find_parent) - return CifDic(basedic) - -def find_parent(ddl2_def): - if "_item.name" not in ddl2_def: - return None - if isinstance(ddl2_def["_item.name"],unicode): - return ddl2_def["_item.name"] - if "_item_linked.child_name" not in ddl2_def: - raise CifError("Asked to find parent in block with no child_names") - if "_item_linked.parent_name" not in ddl2_def: - raise CifError("Asked to find parent in block with no parent_names") - result = list([a for a in ddl2_def["_item.name"] if a not in ddl2_def["_item_linked.child_name"]]) - if len(result)>1 or len(result)==0: - raise CifError("Unable to find single unique parent data item") - return result[0] - - -def ReadCif(filename,grammar='auto',scantype='standard',scoping='instance',standard='CIF'): - """ Read in a CIF file, returning a `CifFile` object. - - * `filename` may be a URL, a file - path on the local system, or any object with a `read` method. - - * `grammar` chooses the CIF grammar variant. `1.0` is the original 1992 grammar and `1.1` - is identical except for the exclusion of square brackets as the first characters in - undelimited datanames. `2.0` will read files in the CIF2.0 standard, and `STAR2` will - read files according to the STAR2 publication. If grammar is `None`, autodetection - will be attempted in the order `2.0`, `1.1` and `1.0`. This will always succeed for - properly-formed CIF2.0 files. Note that only Unicode characters in the basic multilingual - plane are recognised (this will be fixed when PyCIFRW is ported to Python 3). - - * `scantype` can be `standard` or `flex`. `standard` provides pure Python parsing at the - cost of a factor of 10 or so in speed. `flex` will tokenise the input CIF file using - fast C routines, but is not available for CIF2/STAR2 files. Note that running PyCIFRW in - Jython uses native Java regular expressions - to provide a speedup regardless of this argument (and does not yet support CIF2). - - * `scoping` is only relevant where nested save frames are expected (STAR2 only). - `instance` scoping makes nested save frames - invisible outside their hierarchy, allowing duplicate save frame names in separate - hierarchies. `dictionary` scoping makes all save frames within a data block visible to each - other, thereby restricting all save frames to have unique names. - Currently the only recognised value for `standard` is `CIF`, which when set enforces a - maximum length of 75 characters for datanames and has no other effect. """ - - finalcif = CifFile(scoping=scoping,standard=standard) - return StarFile.ReadStar(filename,prepared=finalcif,grammar=grammar,scantype=scantype) - #return StarFile.StarFile(filename,maxlength,scantype=scantype,grammar=grammar,**kwargs) - -class CifLoopBlock(StarFile.LoopBlock): - def __init__(self,data=(),**kwargs): - super(CifLoopBlock,self).__init__(data,**kwargs) - -#No documentation flags - diff --git a/GSASII/CifFile/StarFile.py b/GSASII/CifFile/StarFile.py deleted file mode 100755 index 3efaa4c2d..000000000 --- a/GSASII/CifFile/StarFile.py +++ /dev/null @@ -1,2488 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -__copyright = """ -PYCIFRW License Agreement (Python License, Version 2) ------------------------------------------------------ - -1. This LICENSE AGREEMENT is between the Australian Nuclear Science -and Technology Organisation ("ANSTO"), and the Individual or -Organization ("Licensee") accessing and otherwise using this software -("PyCIFRW") in source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, -ANSTO hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use PyCIFRW alone -or in any derivative version, provided, however, that this License -Agreement and ANSTO's notice of copyright, i.e., "Copyright (c) -2001-2014 ANSTO; All Rights Reserved" are retained in PyCIFRW alone or -in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates PyCIFRW or any part thereof, and wants to make the -derivative work available to others as provided herein, then Licensee -hereby agrees to include in any such work a brief summary of the -changes made to PyCIFRW. - -4. ANSTO is making PyCIFRW available to Licensee on an "AS IS" -basis. ANSTO MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, ANSTO MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYCIFRW WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. ANSTO SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYCIFRW -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A -RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYCIFRW, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between ANSTO -and Licensee. This License Agreement does not grant permission to use -ANSTO trademarks or trade name in a trademark sense to endorse or -promote products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using PyCIFRW, Licensee agrees -to be bound by the terms and conditions of this License Agreement. - -""" - - -# Python 2,3 compatibility -try: - from urllib import urlopen # for arbitrary opening - from urlparse import urlparse, urlunparse -except: - from urllib.request import urlopen - from urllib.parse import urlparse,urlunparse -import re,os -import copy -import textwrap - -try: - from StringIO import StringIO #not cStringIO as we cannot subclass -except ImportError: - from io import StringIO - -if isinstance(u"abc",str): #Python 3 - unicode = str - -try: - import numpy - have_numpy = True -except ImportError: - have_numpy = False - -class StarList(list): - def __getitem__(self,args): - if isinstance(args,(int,slice)): - return super(StarList,self).__getitem__(args) - elif isinstance(args,tuple) and len(args)>1: #extended comma notation - return super(StarList,self).__getitem__(args[0]).__getitem__(args[1:]) - else: - return super(StarList,self).__getitem__(args[0]) - - def __str__(self): - return "SL("+super(StarList,self).__str__() + ")" - -class StarDict(dict): - pass - - -class LoopBlock(object): - def __init__(self,parent_block,dataname): - self.loop_no = parent_block.FindLoop(dataname) - if self.loop_no < 0: - raise KeyError('%s is not in a loop structure' % dataname) - self.parent_block = parent_block - - def keys(self): - return self.parent_block.loops[self.loop_no] - - def values(self): - return [self.parent_block[a] for a in self.keys()] - - #Avoid iterator even though that is Python3-esque - def items(self): - return list(zip(self.keys(),self.values())) - - def __getitem__(self,dataname): - if isinstance(dataname,int): #a packet request - return self.GetPacket(dataname) - if dataname in self.keys(): - return self.parent_block[dataname] - else: - raise KeyError('%s not in loop block' % dataname) - - def __setitem__(self,dataname,value): - self.parent_block[dataname] = value - self.parent_block.AddLoopName(self.keys()[0],dataname) - - def __contains__(self,key): - return key in self.parent_block.loops[self.loop_no] - - def has_key(self,key): - return key in self - - def __iter__(self): - packet_list = zip(*self.values()) - names = self.keys() - for p in packet_list: - r = StarPacket(p) - for n in range(len(names)): - setattr(r,names[n].lower(),r[n]) - yield r - - # for compatibility - def __getattr__(self,attname): - return getattr(self.parent_block,attname) - - def load_iter(self,coords=[]): - count = 0 #to create packet index - while not self.popout: - # ok, we have a new packet: append a list to our subloops - for aloop in self.loops: - aloop.new_enclosing_packet() - for iname in self.item_order: - if isinstance(iname,LoopBlock): #into a nested loop - for subitems in iname.load_iter(coords=coords+[count]): - # print 'Yielding %s' % `subitems` - yield subitems - # print 'End of internal loop' - else: - if self.dimension == 0: - # print 'Yielding %s' % `self[iname]` - yield self,self[iname] - else: - backval = self.block[iname] - for i in range(len(coords)): - # print 'backval, coords: %s, %s' % (`backval`,`coords`) - backval = backval[coords[i]] - yield self,backval - count = count + 1 # count packets - self.popout = False # reinitialise - # print 'Finished iterating' - yield self,'###Blank###' #this value should never be used - - # an experimental fast iterator for level-1 loops (ie CIF) - def fast_load_iter(self): - targets = map(lambda a:self.block[a],self.item_order) - while targets: - for target in targets: - yield self,target - - # Add another list of the required shape to take into account a new outer packet - def new_enclosing_packet(self): - if self.dimension > 1: #otherwise have a top-level list - for iname in self.keys(): #includes lower levels - target_list = self[iname] - for i in range(3,self.dimension): #dim 2 upwards are lists of lists of... - target_list = target_list[-1] - target_list.append([]) - # print '%s now %s' % (iname,`self[iname]`) - - def recursive_iter(self,dict_so_far={},coord=[]): - # print "Recursive iter: coord %s, keys %s, dim %d" % (`coord`,`self.block.keys()`,self.dimension) - my_length = 0 - top_items = self.block.items() - top_values = self.block.values() #same order as items - drill_values = self.block.values() - for dimup in range(0,self.dimension): #look higher in the tree - if len(drill_values)>0: #this block has values - drill_values=drill_values[0] #drill in - else: - raise StarError("Malformed loop packet %s" % repr( top_items[0] )) - my_length = len(drill_values[0]) #length of 'string' entry - if self.dimension == 0: #top level - for aloop in self.loops: - for apacket in aloop.recursive_iter(): - # print "Recursive yielding %s" % repr( dict(top_items + apacket.items()) ) - prep_yield = StarPacket(top_values+apacket.values()) #straight list - for name,value in top_items + apacket.items(): - setattr(prep_yield,name,value) - yield prep_yield - else: #in some loop - for i in range(my_length): - kvpairs = map(lambda a:(a,self.coord_to_group(a,coord)[i]),self.block.keys()) - kvvals = map(lambda a:a[1],kvpairs) #just values - # print "Recursive kvpairs at %d: %s" % (i,repr( kvpairs )) - if self.loops: - for aloop in self.loops: - for apacket in aloop.recursive_iter(coord=coord+[i]): - # print "Recursive yielding %s" % repr( dict(kvpairs + apacket.items()) ) - prep_yield = StarPacket(kvvals+apacket.values()) - for name,value in kvpairs + apacket.items(): - setattr(prep_yield,name,value) - yield prep_yield - else: # we're at the bottom of the tree - # print "Recursive yielding %s" % repr( dict(kvpairs) ) - prep_yield = StarPacket(kvvals) - for name,value in kvpairs: - setattr(prep_yield,name,value) - yield prep_yield - - # small function to use the coordinates. - def coord_to_group(self,dataname,coords): - if not isinstance(dataname,unicode): - return dataname # flag inner loop processing - newm = self[dataname] # newm must be a list or tuple - for c in coords: - # print "Coord_to_group: %s ->" % (repr( newm )), - newm = newm[c] - # print repr( newm ) - return newm - - def flat_iterator(self): - my_length = 0 - top_keys = self.block.keys() - if len(top_keys)>0: - my_length = len(self.block[top_keys[0]]) - for pack_no in range(my_length): - yield(self.collapse(pack_no)) - - - def RemoveItem(self,itemname): - """Remove `itemname` from the block.""" - # first check any loops - loop_no = self.FindLoop(itemname) - testkey = itemname.lower() - if testkey in self: - del self.block[testkey] - del self.true_case[testkey] - # now remove from loop - if loop_no >= 0: - self.loops[loop_no].remove(testkey) - if len(self.loops[loop_no])==0: - del self.loops[loop_no] - self.item_order.remove(loop_no) - else: #will appear in order list - self.item_order.remove(testkey) - - def RemoveLoopItem(self,itemname): - """*Deprecated*. Use `RemoveItem` instead""" - self.RemoveItem(itemname) - - def GetLoop(self,keyname): - """Return a `StarFile.LoopBlock` object constructed from the loop containing `keyname`. - `keyname` is only significant as a way to specify the loop.""" - return LoopBlock(self,keyname) - - def GetPacket(self,index): - thispack = StarPacket([]) - for myitem in self.parent_block.loops[self.loop_no]: - thispack.append(self[myitem][index]) - setattr(thispack,myitem,thispack[-1]) - return thispack - - def AddPacket(self,packet): - for myitem in self.parent_block.loops[self.loop_no]: - old_values = self.parent_block[myitem] - old_values.append(packet.__getattribute__(myitem)) - self.parent_block[myitem] = old_values - - def GetItemOrder(self): - """Return a list of datanames in this `LoopBlock` in the order that they will be - printed""" - return self.parent_block.loops[self.loop_no][:] - - - def GetItemOrder(self): - """Return a list of datanames in this `LoopBlock` in the order that they will be - printed""" - return self.parent_block.loops[self.loop_no][:] - - def ChangeItemOrder(self,itemname,newpos): - """Change the position at which `itemname` appears when printing out to `newpos`.""" - self.parent_block.loops[self.loop_no].remove(itemname.lower()) - self.parent_block.loops[self.loop_no].insert(newpos,itemname.lower()) - - def GetItemPosition(self,itemname): - """A utility function to get the numerical order in the printout - of `itemname`. An item has coordinate `(loop_no,pos)` with - the top level having a `loop_no` of -1. If an integer is passed to - the routine then it will return the position of the loop - referenced by that number.""" - import string - if isinstance(itemname,int): - # return loop position - return (-1, self.item_order.index(itemname)) - if not itemname in self: - raise ValueError('No such dataname %s' % itemname) - testname = itemname.lower() - if testname in self.item_order: - return (-1,self.item_order.index(testname)) - loop_no = self.FindLoop(testname) - loop_pos = self.loops[loop_no].index(testname) - return loop_no,loop_pos - - def GetLoopNames(self,keyname): - if keyname in self: - return self.keys() - for aloop in self.loops: - try: - return aloop.GetLoopNames(keyname) - except KeyError: - pass - raise KeyError('Item does not exist') - - def GetLoopNames(self,keyname): - """Return all datanames appearing together with `keyname`""" - loop_no = self.FindLoop(keyname) - if loop_no >= 0: - return self.loops[loop_no] - else: - raise KeyError('%s is not in any loop' % keyname) - - def AddToLoop(self,dataname,loopdata): - thisloop = self.GetLoop(dataname) - for itemname,itemvalue in loopdata.items(): - thisloop[itemname] = itemvalue - - def AddToLoop(self,dataname,loopdata): - """*Deprecated*. Use `AddItem` followed by calls to `AddLoopName`. - - Add multiple columns to the loop containing `dataname`. `loopdata` is a - collection of (key,value) pairs, where `key` is the new dataname and `value` - is a list of values for that dataname""" - # check lengths - thisloop = self.FindLoop(dataname) - loop_len = len(self[dataname]) - bad_vals = [a for a in loopdata.items() if len(a[1])!=loop_len] - if len(bad_vals)>0: - raise StarLengthError("Number of values for looped datanames %s not equal to %d" \ - % (repr( bad_vals ),loop_len)) - self.update(loopdata) - self.loops[thisloop]+=loopdata.keys() - - -class StarBlock(object): - def __init__(self,data = (), maxoutlength=2048, wraplength=80, overwrite=True, - characterset='ascii',maxnamelength=-1): - self.block = {} #the actual data storage (lower case keys) - self.loops = {} #each loop is indexed by a number and contains a list of datanames - self.item_order = [] #lower case, loops referenced by integer - self.formatting_hints = {} - self.true_case = {} #transform lower case to supplied case - self.provide_value = False #prefer string version always - self.dictionary = None #DDLm dictionary - self.popout = False #used during load iteration - self.curitem = -1 #used during iteration - self.cache_vals = True #store all calculated values - self.maxoutlength = maxoutlength - self.setmaxnamelength(maxnamelength) #to enforce CIF limit of 75 characters - self.set_characterset(characterset) #to check input names - self.wraplength = wraplength - self.overwrite = overwrite - self.string_delimiters = ["'",'"',"\n;"] #universal CIF set - self.list_delimiter = " " #CIF2 default - self.wrapper = textwrap.TextWrapper() - if isinstance(data,(tuple,list)): - for item in data: - self.AddLoopItem(item) - elif isinstance(data,StarBlock): - self.block = data.block.copy() - self.item_order = data.item_order[:] - self.true_case = data.true_case.copy() - # loops as well - self.loops = data.loops.copy() - - def setmaxnamelength(self,maxlength): - """Set the maximum allowable dataname length (-1 for no check)""" - self.maxnamelength = maxlength - if maxlength > 0: - bad_names = [a for a in self.keys() if len(a)>self.maxnamelength] - if len(bad_names)>0: - raise StarError('Datanames too long: ' + repr( bad_names )) - - def set_characterset(self,characterset): - """Set the characterset for checking datanames: may be `ascii` or `unicode`""" - import sys - self.characterset = characterset - if characterset == 'ascii': - self.char_check = re.compile("[][ \n\r\t!%&\(\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\|~\"#$';_-]+",re.M) - elif characterset == 'unicode': - if sys.maxunicode < 1114111: - self.char_check = re.compile(u"[][ \n\r\t!%&\(\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\|~\"#$';_\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD-]+",re.M) - else: - self.char_check = re.compile(u"[][ \n\r\t!%&\(\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\|~\"#$';_\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD\U00010000-\U0010FFFD-]+",re.M) - - def __str__(self): - return self.printsection() - - def __setitem__(self,key,value): - if key == "saves": - raise StarError("""Setting the saves key is deprecated. Add the save block to - an enclosing block collection (e.g. CIF or STAR file) with this block as child""") - self.AddItem(key,value) - - def __getitem__(self,key): - if key == "saves": - raise StarError("""The saves key is deprecated. Access the save block from - the enclosing block collection (e.g. CIF or STAR file object)""") - try: - rawitem,is_value = self.GetFullItemValue(key) - except KeyError: - if self.dictionary: - # send the dictionary the required key and a pointer to us - try: - new_value = self.dictionary.derive_item(key,self,store_value=self.cache_vals,allow_defaults=False) - except StarDerivationFailure: #try now with defaults included - try: - new_value = self.dictionary.derive_item(key,self,store_value=self.cache_vals,allow_defaults=True) - except StarDerivationFailure as s: - print("In StarBlock.__getitem__, " + repr(s)) - raise KeyError('No such item: %s' % key) - print('Set %s to derived value %s' % (key, repr(new_value))) - return new_value - else: - raise KeyError('No such item: %s' % key) - # we now have an item, we can try to convert it to a number if that is appropriate - # note numpy values are never stored but are converted to lists - if not self.dictionary or not key in self.dictionary: return rawitem - print('%s: is_value %s provide_value %s value %s' % (key,repr( is_value ),repr( self.provide_value ),repr( rawitem ))) - if is_value: - if self.provide_value: return rawitem - else: - print('Turning %s into string' % repr( rawitem )) - return self.convert_to_string(key) - else: # a string - if self.provide_value and ((not isinstance(rawitem,list) and rawitem != '?' and rawitem != ".") or \ - (isinstance(rawitem,list) and '?' not in rawitem and '.' not in rawitem)): - return self.dictionary.change_type(key,rawitem) - elif self.provide_value: # catch the question marks - do_calculate = False - if isinstance(rawitem,(list,tuple)): - known = [a for a in rawitem if a != '?'] - if len(known) == 0: #all questions - do_calculate = True - elif rawitem == '?': - do_calculate = True - if do_calculate: - # remove old value - del self[key] - try: - new_value = self.dictionary.derive_item(key,self,store_value=True,allow_defaults=False) - except StarDerivationFailure as s: - try: - new_value = self.dictionary.derive_item(key,self,store_value=True,allow_defaults=True) - except StarDerivationFailure as s: - - print("Could not turn %s into a value:" + repr(s)) - return rawitem - else: - print('Set %s to derived value %s' % (key, repr( new_value ))) - return new_value - return rawitem #can't do anything - - def __delitem__(self,key): - self.RemoveItem(key) - - def __len__(self): - blen = len(self.block) - return blen - - def __nonzero__(self): - if self.__len__() > 0: return 1 - return 0 - - # keys returns all internal keys - def keys(self): - return list(self.block.keys()) #always lower case - - def values(self): - return [self[a] for a in self.keys()] - - def items(self): - return list(zip(self.keys(),self.values())) - - def __contains__(self,key): - if isinstance(key,(unicode,str)) and key.lower() in self.keys(): - return True - return False - - def has_key(self,key): - return key in self - - def has_key_or_alias(self,key): - """Check if a dataname or alias is available in the block""" - initial_test = key in self - if initial_test: return True - elif self.dictionary: - aliases = [k for k in self.dictionary.alias_table.get(key,[]) if self.has_key(k)] - if len(aliases)>0: - return True - return False - - def get(self,key,default=None): - if key in self: - retval = self.__getitem__(key) - else: - retval = default - return retval - - def clear(self): - self.block = {} - self.loops = {} - self.item_order = [] - self.true_case = {} - - # doesn't appear to work - def copy(self): - newcopy = StarBlock() - newcopy.block = self.block.copy() - newcopy.loops = [] - newcopy.item_order = self.item_order[:] - newcopy.true_case = self.true_case.copy() - newcopy.loops = self.loops.copy() - # return self.copy.im_class(newcopy) #catch inheritance - return newcopy - - def update(self,adict): - for key in adict.keys(): - self.AddItem(key,adict[key]) - - def GetItemPosition(self,itemname): - """A utility function to get the numerical order in the printout - of `itemname`. An item has coordinate `(loop_no,pos)` with - the top level having a `loop_no` of -1. If an integer is passed to - the routine then it will return the position of the loop - referenced by that number.""" - import string - if isinstance(itemname,int): - # return loop position - return (-1, self.item_order.index(itemname)) - if not itemname in self: - raise ValueError('No such dataname %s' % itemname) - testname = itemname.lower() - if testname in self.item_order: - return (-1,self.item_order.index(testname)) - loop_no = self.FindLoop(testname) - loop_pos = self.loops[loop_no].index(testname) - return loop_no,loop_pos - - def ChangeItemOrder(self,itemname,newpos): - """Move the printout order of `itemname` to `newpos`. If `itemname` is - in a loop, `newpos` refers to the order within the loop.""" - if isinstance(itemname,(unicode,str)): - true_name = itemname.lower() - else: - true_name = itemname - loopno = self.FindLoop(true_name) - if loopno < 0: #top level - self.item_order.remove(true_name) - self.item_order.insert(newpos,true_name) - else: - self.loops[loopno].remove(true_name) - self.loops[loopno].insert(newpos,true_name) - - def GetItemOrder(self): - """Return a list of datanames in the order in which they will be printed. Loops are - referred to by numerical index""" - return self.item_order[:] - - def AddItem(self,key,value,precheck=False): - """Add dataname `key` to block with value `value`. `value` may be - a single value, a list or a tuple. If `precheck` is False (the default), - all values will be checked and converted to unicode strings as necessary. If - `precheck` is True, this checking is bypassed. No checking is necessary - when values are read from a CIF file as they are already in correct form.""" - if not isinstance(key,(unicode,str)): - raise TypeError('Star datanames are strings only (got %s)' % repr( key )) - key = unicode(key) #everything is unicode internally - if not precheck: - self.check_data_name(key,self.maxnamelength) # make sure no nasty characters - # check for overwriting - if key in self: - if not self.overwrite: - raise StarError( 'Attempt to insert duplicate item name %s' % key) - if not precheck: #need to sanitise - regval,empty_val = self.regularise_data(value) - pure_string = check_stringiness(regval) - self.check_item_value(regval) - else: - regval,empty_val = value,None - pure_string = True - # update ancillary information first - lower_key = key.lower() - if not lower_key in self and self.FindLoop(lower_key)<0: #need to add to order - self.item_order.append(lower_key) - # always remove from our case table in case the case is different - try: - del self.true_case[lower_key] - except KeyError: - pass - self.true_case[lower_key] = key - if pure_string: - self.block.update({lower_key:[regval,empty_val]}) - else: - self.block.update({lower_key:[empty_val,regval]}) - - def AddLoopItem(self,incomingdata,precheck=False,maxlength=-1): - """*Deprecated*. Use `AddItem` followed by `CreateLoop` if - necessary.""" - # print "Received data %s" % `incomingdata` - # we accept tuples, strings, lists and dicts!! - # Direct insertion: we have a string-valued key, with an array - # of values -> single-item into our loop - if isinstance(incomingdata[0],(tuple,list)): - # a whole loop - keyvallist = zip(incomingdata[0],incomingdata[1]) - for key,value in keyvallist: - self.AddItem(key,value) - self.CreateLoop(incomingdata[0]) - elif not isinstance(incomingdata[0],(unicode,str)): - raise TypeError('Star datanames are strings only (got %s)' % repr( incomingdata[0] )) - else: - self.AddItem(incomingdata[0],incomingdata[1]) - - def check_data_name(self,dataname,maxlength=-1): - if maxlength > 0: - self.check_name_length(dataname,maxlength) - if dataname[0]!='_': - raise StarError( 'Dataname ' + dataname + ' does not begin with _') - if self.characterset=='ascii': - if len ([a for a in dataname if ord(a) < 33 or ord(a) > 126]) > 0: - raise StarError( 'Dataname ' + dataname + ' contains forbidden characters') - else: - # print 'Checking %s for unicode characterset conformance' % dataname - if len ([a for a in dataname if ord(a) < 33]) > 0: - raise StarError( 'Dataname ' + dataname + ' contains forbidden characters (below code point 33)') - if len ([a for a in dataname if ord(a) > 126 and ord(a) < 160]) > 0: - raise StarError( 'Dataname ' + dataname + ' contains forbidden characters (between code point 127-159)') - if len ([a for a in dataname if ord(a) > 0xD7FF and ord(a) < 0xE000]) > 0: - raise StarError( 'Dataname ' + dataname + ' contains unsupported characters (between U+D800 and U+E000)') - if len ([a for a in dataname if ord(a) > 0xFDCF and ord(a) < 0xFDF0]) > 0: - raise StarError( 'Dataname ' + dataname + ' contains unsupported characters (between U+FDD0 and U+FDEF)') - if len ([a for a in dataname if ord(a) == 0xFFFE or ord(a) == 0xFFFF]) > 0: - raise StarError( 'Dataname ' + dataname + ' contains unsupported characters (U+FFFE and/or U+FFFF)') - if len ([a for a in dataname if ord(a) > 0x10000 and (ord(a) & 0xE == 0xE)]) > 0: - print('%s fails' % dataname) - for a in dataname: print('%x' % ord(a),end="") - print() - raise StarError( u'Dataname ' + dataname + u' contains unsupported characters (U+xFFFE and/or U+xFFFF)') - - def check_name_length(self,dataname,maxlength): - if len(dataname)>maxlength: - raise StarError( 'Dataname %s exceeds maximum length %d' % (dataname,maxlength)) - return - - def check_item_value(self,item): - test_item = item - if not isinstance(item,(list,dict,tuple)): - test_item = [item] #single item list - def check_one (it): - if isinstance(it,unicode): - if it=='': return - me = self.char_check.match(it) - if not me: - print("Fail value check: %s" % it) - raise StarError('Bad character in %s' % it) - else: - if me.span() != (0,len(it)): - print("Fail value check, match only %d-%d in string %s" % (me.span()[0],me.span()[1],repr( it ))) - raise StarError('Data item "' + repr( it ) + u'"... contains forbidden characters') - [check_one(a) for a in test_item] - - def regularise_data(self,dataitem): - """Place dataitem into a list if necessary""" - from numbers import Number - if isinstance(dataitem,str): - return unicode(dataitem),None - if isinstance(dataitem,(Number,unicode,StarList,StarDict)): - return dataitem,None #assume StarList/StarDict contain unicode if necessary - if isinstance(dataitem,(tuple,list)): - v,s = zip(*list([self.regularise_data(a) for a in dataitem])) - return list(v),list(s) - #return dataitem,[None]*len(dataitem) - # so try to make into a list - try: - regval = list(dataitem) - except TypeError as value: - raise StarError( str(dataitem) + ' is wrong type for data value\n' ) - v,s = zip(*list([self.regularise_data(a) for a in regval])) - return list(v),list(s) - - def RemoveItem(self,itemname): - """Remove `itemname` from the block.""" - # first check any loops - loop_no = self.FindLoop(itemname) - testkey = itemname.lower() - if testkey in self: - del self.block[testkey] - del self.true_case[testkey] - # now remove from loop - if loop_no >= 0: - self.loops[loop_no].remove(testkey) - if len(self.loops[loop_no])==0: - del self.loops[loop_no] - self.item_order.remove(loop_no) - else: #will appear in order list - self.item_order.remove(testkey) - - def RemoveLoopItem(self,itemname): - """*Deprecated*. Use `RemoveItem` instead""" - self.RemoveItem(itemname) - - def GetItemValue(self,itemname): - """Return value of `itemname`. If `itemname` is looped, a list - of all values will be returned.""" - return self.GetFullItemValue(itemname)[0] - - def GetFullItemValue(self,itemname): - """Return the value associated with `itemname`, and a boolean flagging whether - (True) or not (False) it is in a form suitable for calculation. False is - always returned for strings and `StarList` objects.""" - try: - s,v = self.block[itemname.lower()] - except KeyError: - raise KeyError('Itemname %s not in datablock' % itemname) - # prefer string value unless all are None - # are we a looped value? - if not isinstance(s,(tuple,list)) or isinstance(s,StarList): - if not_none(s): - return s,False #a string value - else: - return v,not isinstance(v,StarList) #a StarList is not calculation-ready - elif not_none(s): - return s,False #a list of string values - else: - if len(v)>0: - return v,not isinstance(v[0],StarList) - return v,True - - def CreateLoop(self,datanames,order=-1,length_check=True): - """Create a loop in the datablock. `datanames` is a list of datanames that - together form a loop. If length_check is True, they should have been initialised in the block - to have the same number of elements (possibly 0). If `order` is given, - the loop will appear at this position in the block when printing - out. A loop counts as a single position.""" - - if length_check: - # check lengths: these datanames should exist - listed_values = [a for a in datanames if isinstance(self[a],list) and not isinstance(self[a],StarList)] - if len(listed_values) == len(datanames): - len_set = set([len(self[a]) for a in datanames]) - if len(len_set)>1: - raise ValueError('Request to loop datanames %s with different lengths: %s' % (repr( datanames ),repr( len_set ))) - elif len(listed_values) != 0: - raise ValueError('Request to loop datanames where some are single values and some are not') - # store as lower case - lc_datanames = [d.lower() for d in datanames] - # remove these datanames from all other loops - [self.loops[a].remove(b) for a in self.loops for b in lc_datanames if b in self.loops[a]] - # remove empty loops - empty_loops = [a for a in self.loops.keys() if len(self.loops[a])==0] - for a in empty_loops: - self.item_order.remove(a) - del self.loops[a] - if len(self.loops)>0: - loopno = max(self.loops.keys()) + 1 - else: - loopno = 1 - self.loops[loopno] = list(lc_datanames) - if order >= 0: - self.item_order.insert(order,loopno) - else: - self.item_order.append(loopno) - # remove these datanames from item ordering - self.item_order = [a for a in self.item_order if a not in lc_datanames] - - def AddLoopName(self,oldname, newname): - """Add `newname` to the loop containing `oldname`. If it is already in the new loop, no - error is raised. If `newname` is in a different loop, it is removed from that loop. - The number of values associated with `newname` must match the number of values associated - with all other columns of the new loop or a `ValueError` will be raised.""" - lower_newname = newname.lower() - loop_no = self.FindLoop(oldname) - if loop_no < 0: - raise KeyError('%s not in loop' % oldname) - if lower_newname in self.loops[loop_no]: - return - # check length - old_provides = self.provide_value - self.provide_value = False - loop_len = len(self[oldname]) - self.provide_value = old_provides - if len(self[newname]) != loop_len: - raise ValueError('Mismatch of loop column lengths for %s: should be %d' % (newname,loop_len)) - # remove from any other loops - [self.loops[a].remove(lower_newname) for a in self.loops if lower_newname in self.loops[a]] - # and add to this loop - self.loops[loop_no].append(lower_newname) - # remove from item_order if present - try: - self.item_order.remove(lower_newname) - except ValueError: - pass - - def FindLoop(self,keyname): - """Find the loop that contains `keyname` and return its numerical index or - -1 if not present. The numerical index can be used to refer to the loop in - other routines.""" - loop_no = [a for a in self.loops.keys() if keyname.lower() in self.loops[a]] - if len(loop_no)>0: - return loop_no[0] - else: - return -1 - - def GetLoop(self,keyname): - """Return a `StarFile.LoopBlock` object constructed from the loop containing `keyname`. - `keyname` is only significant as a way to specify the loop.""" - return LoopBlock(self,keyname) - - def GetLoopNames(self,keyname): - if keyname in self: - return self.keys() - for aloop in self.loops: - try: - return aloop.GetLoopNames(keyname) - except KeyError: - pass - raise KeyError('Item does not exist') - - def GetLoopNames(self,keyname): - """Return all datanames appearing together with `keyname`""" - loop_no = self.FindLoop(keyname) - if loop_no >= 0: - return self.loops[loop_no] - else: - raise KeyError('%s is not in any loop' % keyname) - - def AddLoopName(self,oldname, newname): - """Add `newname` to the loop containing `oldname`. If it is already in the new loop, no - error is raised. If `newname` is in a different loop, it is removed from that loop. - The number of values associated with `newname` must match the number of values associated - with all other columns of the new loop or a `ValueError` will be raised.""" - lower_newname = newname.lower() - loop_no = self.FindLoop(oldname) - if loop_no < 0: - raise KeyError('%s not in loop' % oldname) - if lower_newname in self.loops[loop_no]: - return - # check length - old_provides = self.provide_value - self.provide_value = False - loop_len = len(self[oldname]) - self.provide_value = old_provides - if len(self[newname]) != loop_len: - raise ValueError('Mismatch of loop column lengths for %s: should be %d' % (newname,loop_len)) - # remove from any other loops - [self.loops[a].remove(lower_newname) for a in self.loops if lower_newname in self.loops[a]] - # and add to this loop - self.loops[loop_no].append(lower_newname) - # remove from item_order if present - try: - self.item_order.remove(lower_newname) - except ValueError: - pass - - def AddToLoop(self,dataname,loopdata): - thisloop = self.GetLoop(dataname) - for itemname,itemvalue in loopdata.items(): - thisloop[itemname] = itemvalue - - def AddToLoop(self,dataname,loopdata): - """*Deprecated*. Use `AddItem` followed by calls to `AddLoopName`. - - Add multiple columns to the loop containing `dataname`. `loopdata` is a - collection of (key,value) pairs, where `key` is the new dataname and `value` - is a list of values for that dataname""" - # check lengths - thisloop = self.FindLoop(dataname) - loop_len = len(self[dataname]) - bad_vals = [a for a in loopdata.items() if len(a[1])!=loop_len] - if len(bad_vals)>0: - raise StarLengthError("Number of values for looped datanames %s not equal to %d" \ - % (repr( bad_vals ),loop_len)) - self.update(loopdata) - self.loops[thisloop]+=loopdata.keys() - - def RemoveKeyedPacket(self,keyname,keyvalue): - """Remove the packet for which dataname `keyname` takes - value `keyvalue`. Only the first such occurrence is - removed.""" - packet_coord = list(self[keyname]).index(keyvalue) - loopnames = self.GetLoopNames(keyname) - for dataname in loopnames: - self.block[dataname][0] = list(self.block[dataname][0]) - del self.block[dataname][0][packet_coord] - self.block[dataname][1] = list(self.block[dataname][1]) - del self.block[dataname][1][packet_coord] - - def GetKeyedPacket(self,keyname,keyvalue,no_case=False): - """Return the loop packet (a `StarPacket` object) where `keyname` has value - `keyvalue`. Ignore case in `keyvalue` if `no_case` is True. `ValueError` - is raised if no packet is found or more than one packet is found.""" - my_loop = self.GetLoop(keyname) - #print("Looking for %s in %s" % (keyvalue, my_loop.parent_block)) - #print('Packet check on:' + keyname) - #[print(repr(getattr(a,keyname))) for a in my_loop] - if no_case: - one_pack= [a for a in my_loop if getattr(a,keyname).lower()==keyvalue.lower()] - else: - one_pack= [a for a in my_loop if getattr(a,keyname)==keyvalue] - if len(one_pack)!=1: - raise ValueError("Bad packet key %s = %s: returned %d packets" % (keyname,keyvalue,len(one_pack))) -# print("Keyed packet: %s" % one_pack[0]) - return one_pack[0] - - def GetCompoundKeyedPacket(self,keydict): - """Return the loop packet (a `StarPacket` object) where the `{key:(value,caseless)}` pairs - in `keydict` take the appropriate values. Ignore case for a given `key` if `caseless` is - True. `ValueError` is raised if no packet is found or more than one packet is found.""" - #print "Looking for %s in %s" % (keyvalue, self.parent_block[keyname]) - keynames = list(keydict.keys()) - my_loop = self.GetLoop(keynames[0]) - for one_key in keynames: - keyval,no_case = keydict[one_key] - if no_case: - my_loop = list([a for a in my_loop if str(getattr(a,one_key)).lower()==str(keyval).lower()]) - else: - my_loop = list([a for a in my_loop if getattr(a,one_key)==keyval]) - if len(my_loop)!=1: - raise ValueError("Bad packet keys %s: returned %d packets" % (repr(keydict),len(my_loop))) - print("Compound keyed packet: %s" % my_loop[0]) - return my_loop[0] - - def GetKeyedSemanticPacket(self,keyvalue,cat_id): - """Return a complete packet for category `cat_id` where the - category key for the category equals `keyvalue`. This routine - will understand any joined loops, so if separate loops in the - datafile belong to the - same category hierarchy (e.g. `_atom_site` and `_atom_site_aniso`), - the returned `StarPacket` object will contain datanames from - both categories.""" - target_keys = self.dictionary.cat_key_table[cat_id] - target_keys = [k[0] for k in target_keys] #one only in each list - p = StarPacket() - # set case-sensitivity flag - lcase = False - if self.dictionary[target_keys[0]]['_type.contents'] in ['Code','Tag','Name']: - lcase = True - for cat_key in target_keys: - try: - extra_packet = self.GetKeyedPacket(cat_key,keyvalue,no_case=lcase) - except KeyError: #missing key - try: - test_key = self[cat_key] #generate key if possible - print('Test key is %s' % repr( test_key )) - if test_key is not None and\ - not (isinstance(test_key,list) and (None in test_key or len(test_key)==0)): - print('Getting packet for key %s' % repr( keyvalue )) - extra_packet = self.GetKeyedPacket(cat_key,keyvalue,no_case=lcase) - except: #cannot be generated - continue - except ValueError: #none/more than one, assume none - continue - #extra_packet = self.dictionary.generate_default_packet(cat_id,cat_key,keyvalue) - p.merge_packet(extra_packet) - # the following attributes used to calculate missing values - for keyname in target_keys: - if hasattr(p,keyname): - p.key = [keyname] - break - if not hasattr(p,"key"): - raise ValueError("No key found for %s, packet is %s" % (cat_id,str(p))) - p.cif_dictionary = self.dictionary - p.fulldata = self - return p - - def GetMultiKeyedSemanticPacket(self,keydict,cat_id): - """Return a complete packet for category `cat_id` where the keyvalues are - provided as a dictionary of key:(value,caseless) pairs - This routine - will understand any joined loops, so if separate loops in the - datafile belong to the - same category hierarchy (e.g. `_atom_site` and `_atom_site_aniso`), - the returned `StarPacket` object will contain datanames from - the requested category and any children.""" - #if len(keyvalues)==1: #simplification - # return self.GetKeyedSemanticPacket(keydict[1][0],cat_id) - target_keys = self.dictionary.cat_key_table[cat_id] - # update the dictionary passed to us with all equivalents, for - # simplicity. - parallel_keys = list(zip(*target_keys)) #transpose - print('Parallel keys:' + repr(parallel_keys)) - print('Keydict:' + repr(keydict)) - start_keys = list(keydict.keys()) - for one_name in start_keys: - key_set = [a for a in parallel_keys if one_name in a] - for one_key in key_set: - keydict[one_key] = keydict[one_name] - # target_keys is a list of lists, each of which is a compound key - p = StarPacket() - # a little function to return the dataname for a key - def find_key(key): - for one_key in self.dictionary.key_equivs.get(key,[])+[key]: - if self.has_key(one_key): - return one_key - return None - for one_set in target_keys: #loop down the categories - true_keys = [find_key(k) for k in one_set] - true_keys = [k for k in true_keys if k is not None] - if len(true_keys)==len(one_set): - truekeydict = dict([(t,keydict[k]) for t,k in zip(true_keys,one_set)]) - try: - extra_packet = self.GetCompoundKeyedPacket(truekeydict) - except KeyError: #one or more are missing - continue #should try harder? - except ValueError: - continue - else: - continue - print('Merging packet for keys ' + repr(one_set)) - p.merge_packet(extra_packet) - # the following attributes used to calculate missing values - p.key = true_keys - p.cif_dictionary = self.dictionary - p.fulldata = self - return p - - - def set_grammar(self,new_grammar): - self.string_delimiters = ["'",'"',"\n;",None] - if new_grammar in ['STAR2','2.0']: - self.string_delimiters += ['"""',"'''"] - if new_grammar == '2.0': - self.list_delimiter = " " - elif new_grammar == 'STAR2': - self.list_delimiter = ", " - elif new_grammar not in ['1.0','1.1']: - raise StarError('Request to set unknown grammar %s' % new_grammar) - - def SetOutputLength(self,wraplength=80,maxoutlength=2048): - """Set the maximum output line length (`maxoutlength`) and the line length to - wrap at (`wraplength`). The wrap length is a target only and may not always be - possible.""" - if wraplength > maxoutlength: - raise StarError("Wrap length (requested %d) must be <= Maximum line length (requested %d)" % (wraplength,maxoutlength)) - self.wraplength = wraplength - self.maxoutlength = maxoutlength - - def printsection(self,instring='',blockstart="",blockend="",indent=0,finish_at='',start_from=''): - import string - self.provide_value = False - # first make an ordering - self.create_ordering(finish_at,start_from) #create self.output_order - # now do it... - if not instring: - outstring = CIFStringIO(target_width=80) # the returned string - else: - outstring = instring - # print block delimiter - outstring.write(blockstart,canbreak=True) - while len(self.output_order)>0: - #print "Remaining to output " + `self.output_order` - itemname = self.output_order.pop(0) - if not isinstance(itemname,int): #no loop - item_spec = [i for i in self.formatting_hints if i['dataname'].lower()==itemname.lower()] - if len(item_spec)>0: - item_spec = item_spec[0] - col_pos = item_spec.get('column',-1) - name_pos = item_spec.get('name_pos',-1) - else: - col_pos = -1 - item_spec = {} - name_pos = -1 - if col_pos < 0: col_pos = 40 - outstring.set_tab(col_pos) - itemvalue = self[itemname] - outstring.write(self.true_case[itemname],mustbreak=True,do_tab=False,startcol=name_pos) - outstring.write(' ',canbreak=True,do_tab=False,delimiter=True) #space after itemname - self.format_value(itemvalue,outstring,hints=item_spec) - else:# we are asked to print a loop block - outstring.set_tab(10) #guess this is OK? - loop_spec = [i['name_pos'] for i in self.formatting_hints if i["dataname"]=='loop'] - if loop_spec: - loop_indent = max(loop_spec[0],0) - else: - loop_indent = indent - outstring.write('loop_\n',mustbreak=True,do_tab=False,startcol=loop_indent) - self.format_names(outstring,indent+2,loop_no=itemname) - self.format_packets(outstring,indent+2,loop_no=itemname) - else: - returnstring = outstring.getvalue() - outstring.close() - return returnstring - - def format_names(self,outstring,indent=0,loop_no=-1): - """Print datanames from `loop_no` one per line""" - temp_order = self.loops[loop_no][:] #copy - format_hints = dict([(i['dataname'],i) for i in self.formatting_hints if i['dataname'] in temp_order]) - while len(temp_order)>0: - itemname = temp_order.pop(0) - req_indent = format_hints.get(itemname,{}).get('name_pos',indent) - outstring.write(' ' * req_indent,do_tab=False) - outstring.write(self.true_case[itemname],do_tab=False) - outstring.write("\n",do_tab=False) - - def format_packets(self,outstring,indent=0,loop_no=-1): - import string - alldata = [self[a] for a in self.loops[loop_no]] - loopnames = self.loops[loop_no] - #print 'Alldata: %s' % `alldata` - packet_data = list(zip(*alldata)) - #print 'Packet data: %s' % `packet_data` - #create a dictionary for quick lookup of formatting requirements - format_hints = dict([(i['dataname'],i) for i in self.formatting_hints if i['dataname'] in loopnames]) - for position in range(len(packet_data)): - if position > 0: - outstring.write("\n") #new line each packet except first - for point in range(len(packet_data[position])): - datapoint = packet_data[position][point] - format_hint = format_hints.get(loopnames[point],{}) - packstring = self.format_packet_item(datapoint,indent,outstring,format_hint) - outstring.write(' ',canbreak=True,do_tab=False,delimiter=True) - - def format_packet_item(self,pack_item,indent,outstring,format_hint): - # print 'Formatting %s' % `pack_item` - # temporary check for any non-unicode items - if isinstance(pack_item,str) and not isinstance(pack_item,unicode): - raise StarError("Item {0!r} is not unicode".format(pack_item)) - if isinstance(pack_item,unicode): - delimiter = format_hint.get('delimiter',None) - startcol = format_hint.get('column',-1) - outstring.write(self._formatstring(pack_item,delimiter=delimiter),startcol=startcol) - else: - self.format_value(pack_item,outstring,hints = format_hint) - - def _formatstring(self,instring,delimiter=None,standard='CIF1',indent=0,hints={}): - import string - if hints.get("reformat",False) and "\n" in instring: - instring = "\n"+self.do_wrapping(instring,hints["reformat_indent"]) - allowed_delimiters = set(self.string_delimiters) - if len(instring)==0: allowed_delimiters.difference_update([None]) - if len(instring) > (self.maxoutlength-2) or '\n' in instring: - allowed_delimiters.intersection_update(["\n;","'''",'"""']) - if ' ' in instring or '\t' in instring or '\v' in instring or (len(instring)>0 and instring[0] in '_$#;([{') or ',' in instring: - allowed_delimiters.difference_update([None]) - if len(instring)>3 and (instring[:4].lower()=='data' or instring[:4].lower()=='save'): - allowed_delimiters.difference_update([None]) - if len(instring)>5 and instring[:6].lower()=='global': - allowed_delimiters.difference_update([None]) - if '"' in instring: allowed_delimiters.difference_update(['"',None]) - if "'" in instring: allowed_delimiters.difference_update(["'",None]) - out_delimiter = "\n;" #default (most conservative) - if delimiter in allowed_delimiters: - out_delimiter = delimiter - elif "'" in allowed_delimiters: out_delimiter = "'" - elif '"' in allowed_delimiters: out_delimiter = '"' - if out_delimiter in ['"',"'",'"""',"'''"]: return out_delimiter + instring + out_delimiter - elif out_delimiter is None: return instring - # we are left with semicolon strings - # use our protocols: - maxlinelength = max([len(a) for a in instring.split('\n')]) - if maxlinelength > self.maxoutlength: - protocol_string = apply_line_folding(instring) - else: - protocol_string = instring - # now check for embedded delimiters - if "\n;" in protocol_string: - prefix = "CIF:" - while prefix in protocol_string: prefix = prefix + ":" - protocol_string = apply_line_prefix(protocol_string,prefix+"> ") - return "\n;" + protocol_string + "\n;" - - def format_value(self,itemvalue,stringsink,compound=False,hints={}): - """Format a Star data value""" - global have_numpy - delimiter = hints.get('delimiter',None) - startcol = hints.get('column',-1) - if isinstance(itemvalue,str) and not isinstance(itemvalue,unicode): #not allowed - raise StarError("Non-unicode value {0} found in block".format(itemvalue)) - if isinstance(itemvalue,unicode): #need to sanitize - stringsink.write(self._formatstring(itemvalue,delimiter=delimiter,hints=hints),canbreak = True,startcol=startcol) - elif isinstance(itemvalue,(list)) or (hasattr(itemvalue,'dtype') and hasattr(itemvalue,'__iter__')): #numpy - stringsink.set_tab(0) - stringsink.write('[',canbreak=True,newindent=True,mustbreak=compound,startcol=startcol) - if len(itemvalue)>0: - self.format_value(itemvalue[0],stringsink) - for listval in itemvalue[1:]: - # print 'Formatting %s' % `listval` - stringsink.write(self.list_delimiter,do_tab=False) - self.format_value(listval,stringsink,compound=True) - stringsink.write(']',unindent=True) - elif isinstance(itemvalue,dict): - stringsink.set_tab(0) - stringsink.write('{',newindent=True,mustbreak=compound,startcol=startcol) #start a new line inside - items = list(itemvalue.items()) - if len(items)>0: - stringsink.write("'"+items[0][0]+"'"+':',canbreak=True) - self.format_value(items[0][1],stringsink) - for key,value in items[1:]: - stringsink.write(self.list_delimiter) - stringsink.write("'"+key+"'"+":",canbreak=True) - self.format_value(value,stringsink) #never break between key and value - stringsink.write('}',unindent=True) - elif isinstance(itemvalue,(float,int)) or \ - (have_numpy and isinstance(itemvalue,(numpy.number))): #TODO - handle uncertainties - stringsink.write(str(itemvalue),canbreak=True,startcol=startcol) #numbers - else: - raise ValueError('Value in unexpected format for output: %s' % repr( itemvalue )) - - def create_ordering(self,finish_at,start_from): - """Create a canonical ordering that includes loops using our formatting hints dictionary""" - requested_order = list([i['dataname'] for i in self.formatting_hints if i['dataname']!='loop']) - new_order = [] - for item in requested_order: - if isinstance(item,unicode) and item.lower() in self.item_order: - new_order.append(item.lower()) - elif item in self: #in a loop somewhere - target_loop = self.FindLoop(item) - if target_loop not in new_order: - new_order.append(target_loop) - # adjust loop name order - loopnames = self.loops[target_loop] - loop_order = [i for i in requested_order if i in loopnames] - unordered = [i for i in loopnames if i not in loop_order] - self.loops[target_loop] = loop_order + unordered - extras = list([i for i in self.item_order if i not in new_order]) - self.output_order = new_order + extras - # now handle partial output - if start_from != '': - if start_from in requested_order: - sfi = requested_order.index(start_from) - loop_order = [self.FindLoop(k) for k in requested_order[sfi:] if self.FindLoop(k)>0] - candidates = list([k for k in self.output_order if k in requested_order[sfi:]]) - cand_pos = len(new_order) - if len(candidates)>0: - cand_pos = self.output_order.index(candidates[0]) - if len(loop_order)>0: - cand_pos = min(cand_pos,self.output_order.index(loop_order[0])) - if cand_pos < len(self.output_order): - print('Output starts from %s, requested %s' % (self.output_order[cand_pos],start_from)) - self.output_order = self.output_order[cand_pos:] - else: - print('Start is beyond end of output list') - self.output_order = [] - elif start_from in extras: - self.output_order = self.output_order[self.output_order.index(start_from):] - else: - self.output_order = [] - if finish_at != '': - if finish_at in requested_order: - fai = requested_order.index(finish_at) - loop_order = list([self.FindLoop(k) for k in requested_order[fai:] if self.FindLoop(k)>0]) - candidates = list([k for k in self.output_order if k in requested_order[fai:]]) - cand_pos = len(new_order) - if len(candidates)>0: - cand_pos = self.output_order.index(candidates[0]) - if len(loop_order)>0: - cand_pos = min(cand_pos,self.output_order.index(loop_order[0])) - if cand_pos < len(self.output_order): - print('Output finishes before %s, requested before %s' % (self.output_order[cand_pos],finish_at)) - self.output_order = self.output_order[:cand_pos] - else: - print('All of block output') - elif finish_at in extras: - self.output_order = self.output_order[:self.output_order.index(finish_at)] - #print('Final order: ' + repr(self.output_order)) - - def convert_to_string(self,dataname): - """Convert values held in dataname value fork to string version""" - v,is_value = self.GetFullItemValue(dataname) - if not is_value: - return v - if check_stringiness(v): return v #already strings - # TODO...something else - return v - - def do_wrapping(self,instring,indent=3): - """Wrap the provided string""" - if " " in instring: #already formatted - return instring - self.wrapper.initial_indent = ' '*indent - self.wrapper.subsequent_indent = ' '*indent - # remove leading and trailing space - instring = instring.strip() - # split into paragraphs - paras = instring.split("\n\n") - wrapped_paras = [self.wrapper.fill(p) for p in paras] - return "\n".join(wrapped_paras) - - - def merge(self,new_block,mode="strict",match_att=[],match_function=None, - rel_keys = []): - if mode == 'strict': - for key in new_block.keys(): - if key in self and key not in match_att: - raise StarError( "Identical keys %s in strict merge mode" % key) - elif key not in match_att: #a new dataname - self[key] = new_block[key] - # we get here if there are no keys in common, so we can now copy - # the loops and not worry about overlaps - for one_loop in new_block.loops.values(): - self.CreateLoop(one_loop) - # we have lost case information - self.true_case.update(new_block.true_case) - elif mode == 'replace': - newkeys = list(new_block.keys()) - for ma in match_att: - try: - newkeys.remove(ma) #don't touch the special ones - except ValueError: - pass - for key in new_block.keys(): - if isinstance(key,unicode): - self[key] = new_block[key] - # creating the loop will remove items from other loops - for one_loop in new_block.loops.values(): - self.CreateLoop(one_loop) - # we have lost case information - self.true_case.update(new_block.true_case) - elif mode == 'overlay': - print('Overlay mode, current overwrite is %s' % self.overwrite) - raise StarError('Overlay block merge mode not implemented') - save_overwrite = self.overwrite - self.overwrite = True - for attribute in new_block.keys(): - if attribute in match_att: continue #ignore this one - new_value = new_block[attribute] - #non-looped items - if new_block.FindLoop(attribute)<0: #not looped - self[attribute] = new_value - my_loops = self.loops.values() - perfect_overlaps = [a for a in new_block.loops if a in my_loops] - for po in perfect_overlaps: - loop_keys = [a for a in po if a in rel_keys] #do we have a key? - try: - newkeypos = map(lambda a:newkeys.index(a),loop_keys) - newkeypos = newkeypos[0] #one key per loop for now - loop_keys = loop_keys[0] - except (ValueError,IndexError): - newkeypos = [] - overlap_data = map(lambda a:listify(self[a]),overlaps) #old packet data - new_data = map(lambda a:new_block[a],overlaps) #new packet data - packet_data = transpose(overlap_data) - new_p_data = transpose(new_data) - # remove any packets for which the keys match between old and new; we - # make the arbitrary choice that the old data stays - if newkeypos: - # get matching values in new list - print("Old, new data:\n%s\n%s" % (repr(overlap_data[newkeypos]),repr(new_data[newkeypos]))) - key_matches = filter(lambda a:a in overlap_data[newkeypos],new_data[newkeypos]) - # filter out any new data with these key values - new_p_data = filter(lambda a:a[newkeypos] not in key_matches,new_p_data) - if new_p_data: - new_data = transpose(new_p_data) - else: new_data = [] - # wipe out the old data and enter the new stuff - byebyeloop = self.GetLoop(overlaps[0]) - # print "Removing '%s' with overlaps '%s'" % (`byebyeloop`,`overlaps`) - # Note that if, in the original dictionary, overlaps are not - # looped, GetLoop will return the block itself. So we check - # for this case... - if byebyeloop != self: - self.remove_loop(byebyeloop) - self.AddLoopItem((overlaps,overlap_data)) #adding old packets - for pd in new_p_data: #adding new packets - if pd not in packet_data: - for i in range(len(overlaps)): - #don't do this at home; we are appending - #to something in place - self[overlaps[i]].append(pd[i]) - self.overwrite = save_overwrite - - def assign_dictionary(self,dic): - if not dic.diclang=="DDLm": - print("Warning: ignoring dictionary %s" % dic.dic_as_cif.my_uri) - return - self.dictionary = dic - - def unassign_dictionary(self): - """Remove dictionary-dependent behaviour""" - self.dictionary = None - - - -class StarPacket(list): - def merge_packet(self,incoming): - """Merge contents of incoming packet with this packet""" - new_attrs = [a for a in dir(incoming) if a[0] == '_' and a[1] != "_"] - self.extend(incoming) - for na in new_attrs: - setattr(self,na,getattr(incoming,na)) - - def __getattr__(self,att_name): - """Derive a missing attribute""" - if att_name.lower() in self.__dict__: - return getattr(self,att_name.lower()) - if att_name in ('cif_dictionary','fulldata','key'): - raise AttributeError('Programming error: can only assign value of %s' % att_name) - d = self.cif_dictionary - c = self.fulldata - k = self.key - assert isinstance(k,list) - d.derive_item(att_name,c,store_value=True) - # - # now pick out the new value - # self.key is a list of the key values - keydict = dict([(v,(getattr(self,v),True)) for v in k]) - full_pack = c.GetCompoundKeyedPacket(keydict) - return getattr(full_pack,att_name) - -class BlockCollection(object): - """A container for StarBlock objects. The constructor takes - one non-keyword argument `datasource` to set the initial data. If - `datasource` is a Python dictionary, the values must be `StarBlock` - objects and the keys will be blocknames in the new object. Keyword - arguments: - - standard: - `CIF` or `Dic`. `CIF` enforces 75-character blocknames, and will - print block contents before that block's save frame. - - blocktype: - The type of blocks held in this container. Normally `StarBlock` - or `CifBlock`. - - characterset: - `ascii` or `unicode`. Blocknames and datanames appearing within - blocks are restricted to the appropriate characterset. Note that - only characters in the basic multilingual plane are accepted. This - restriction will be lifted when PyCIFRW is ported to Python3. - - scoping: - `instance` or `dictionary`: `instance` implies that save frames are - hidden from save frames lower in the hierarchy or in sibling - hierarchies. `dictionary` makes all save frames visible everywhere - within a data block. This setting is only relevant for STAR2 dictionaries and - STAR2 data files, as save frames are currently not used in plain CIF data - files. - -""" - def __init__(self,datasource=None,standard='CIF',blocktype = StarBlock, - characterset='ascii',scoping='instance',**kwargs): - import collections - self.dictionary = {} - self.standard = standard - self.lower_keys = set() # short_cuts - self.renamed = {} - self.PC = collections.namedtuple('PC',['block_id','parent']) - self.child_table = {} - self.visible_keys = [] # for efficiency - self.block_input_order = [] # to output in same order - self.scoping = scoping #will trigger setting of child table - self.blocktype = blocktype - self.master_template = {} #for outputting - self.set_grammar('2.0') - self.set_characterset(characterset) - if isinstance(datasource,BlockCollection): - self.merge_fast(datasource) - self.scoping = scoping #reset visibility - elif isinstance(datasource,dict): - for key,value in datasource.items(): - self[key]= value - self.header_comment = '' - - def set_grammar(self,new_grammar): - """Set the syntax and grammar for output to `new_grammar`""" - if new_grammar not in ['1.1','1.0','2.0','STAR2']: - raise StarError('Unrecognised output grammar %s' % new_grammar) - self.grammar = new_grammar - - def set_characterset(self,characterset): - """Set the allowed characters for datanames and datablocks: may be `ascii` or `unicode`. If datanames - have already been added to any datablocks, they are not checked.""" - self.characterset = characterset - for one_block in self.lower_keys: - self[one_block].set_characterset(characterset) - - def unlock(self): - """Allow overwriting of all blocks in this collection""" - for a in self.lower_keys: - self[a].overwrite=True - - def lock(self): - """Disallow overwriting for all blocks in this collection""" - for a in self.lower_keys: - self[a].overwrite = False - - def __str__(self): - return self.WriteOut() - - def __setitem__(self,key,value): - self.NewBlock(key,value,parent=None) - - def __getitem__(self,key): - if isinstance(key,(unicode,str)): - lowerkey = key.lower() - if lowerkey in self.lower_keys: - return self.dictionary[lowerkey] - #print 'Visible keys:' + `self.visible_keys` - #print 'All keys' + `self.lower_keys` - #print 'Child table' + `self.child_table` - raise KeyError('No such item %s' % key) - - # we have to get an ordered list of the current keys, - # as we'll have to delete one of them anyway. - # Deletion will delete any key regardless of visibility - - def __delitem__(self,key): - dummy = self[key] #raise error if not present - lowerkey = key.lower() - # get rid of all children recursively as well - children = [a[0] for a in self.child_table.items() if a[1].parent == lowerkey] - for child in children: - del self[child] #recursive call - del self.dictionary[lowerkey] - del self.child_table[lowerkey] - try: - self.visible_keys.remove(lowerkey) - except KeyError: - pass - self.lower_keys.remove(lowerkey) - self.block_input_order.remove(lowerkey) - - def __len__(self): - return len(self.visible_keys) - - def __contains__(self,item): - """Support the 'in' operator""" - if not isinstance(item,(unicode,str)): return False - if item.lower() in self.visible_keys: - return True - return False - - # We iterate over all visible - def __iter__(self): - for one_block in self.keys(): - yield self[one_block] - - # TODO: handle different case - def keys(self): - return self.visible_keys - - # Note that has_key does not exist in 3.5 - def has_key(self,key): - return key in self - - def get(self,key,default=None): - if key in self: # take account of case - return self.__getitem__(key) - else: - return default - - def clear(self): - self.dictionary.clear() - self.lower_keys = set() - self.child_table = {} - self.visible_keys = [] - self.block_input_order = [] - - def copy(self): - newcopy = self.dictionary.copy() #all blocks - for k,v in self.dictionary.items(): - newcopy[k] = v.copy() - newcopy = BlockCollection(newcopy) - newcopy.child_table = self.child_table.copy() - newcopy.lower_keys = self.lower_keys.copy() - newcopy.block_input_order = self.block_input_order.copy() - newcopy.characterset = self.characterset - newcopy.SetTemplate(self.master_template.copy()) - newcopy.scoping = self.scoping #this sets visible keys - return newcopy - - def update(self,adict): - for key in adict.keys(): - self[key] = adict[key] - - def items(self): - return [(a,self[a]) for a in self.keys()] - - def first_block(self): - """Return the 'first' block. This is not necessarily the first block in the file.""" - if self.keys(): - return self[self.keys()[0]] - - def NewBlock(self,blockname,blockcontents=None,fix=True,parent=None): - """Add a new block named `blockname` with contents `blockcontents`. If `fix` - is True, `blockname` will have spaces and tabs replaced by underscores. `parent` - allows a parent block to be set so that block hierarchies can be created. Depending on - the output standard, these blocks will be printed out as nested save frames or - ignored.""" - if blockcontents is None: - blockcontents = StarBlock() - if self.standard == "CIF": - blockcontents.setmaxnamelength(75) - if len(blockname)>75: - raise StarError('Blockname %s is longer than 75 characters' % blockname) - if fix: - newblockname = re.sub('[ \t]','_',blockname) - else: newblockname = blockname - new_lowerbn = newblockname.lower() - if new_lowerbn in self.lower_keys: #already there - if self.standard is not None: - toplevelnames = [a[0] for a in self.child_table.items() if a[1].parent==None] - if parent is None and new_lowerbn not in toplevelnames: #can give a new key to this one - while new_lowerbn in self.lower_keys: new_lowerbn = new_lowerbn + '+' - elif parent is not None and new_lowerbn in toplevelnames: #can fix a different one - replace_name = new_lowerbn - while replace_name in self.lower_keys: replace_name = replace_name + '+' - self._rekey(new_lowerbn,replace_name) - # now continue on to add in the new block - if parent.lower() == new_lowerbn: #the new block's requested parent just got renamed!! - parent = replace_name - else: - raise StarError( "Attempt to replace existing block " + blockname) - else: - del self[new_lowerbn] - self.dictionary.update({new_lowerbn:blockcontents}) - self.lower_keys.add(new_lowerbn) - self.block_input_order.append(new_lowerbn) - if parent is None: - self.child_table[new_lowerbn]=self.PC(newblockname,None) - self.visible_keys.append(new_lowerbn) - else: - if parent.lower() in self.lower_keys: - if self.scoping == 'instance': - self.child_table[new_lowerbn]=self.PC(newblockname,parent.lower()) - else: - self.child_table[new_lowerbn]=self.PC(newblockname,parent.lower()) - self.visible_keys.append(new_lowerbn) - else: - print('Warning:Parent block %s does not exist for child %s' % (parent,newblockname)) - self[new_lowerbn].set_grammar(self.grammar) - self[new_lowerbn].set_characterset(self.characterset) - self[new_lowerbn].formatting_hints = self.master_template - return new_lowerbn #in case calling routine wants to know - - def _rekey(self,oldname,newname,block_id=''): - """The block with key [[oldname]] gets [[newname]] as a new key, but the printed name - does not change unless [[block_id]] is given. Prefer [[rename]] for a safe version.""" - move_block = self[oldname] #old block - is_visible = oldname in self.visible_keys - move_block_info = self.child_table[oldname] #old info - move_block_children = [a for a in self.child_table.items() if a[1].parent==oldname] - # now rewrite the necessary bits - self.child_table.update(dict([(a[0],self.PC(a[1].block_id,newname)) for a in move_block_children])) - oldpos = self.block_input_order.index(oldname) - del self[oldname] #do this after updating child table so we don't delete children - self.dictionary.update({newname:move_block}) - self.lower_keys.add(newname) - #print 'Block input order was: ' + `self.block_input_order` - self.block_input_order[oldpos:oldpos]=[newname] - if block_id == '': - self.child_table.update({newname:move_block_info}) - else: - self.child_table.update({newname:self.PC(block_id,move_block_info.parent)}) - if is_visible: self.visible_keys += [newname] - - def rename(self,oldname,newname): - """Rename datablock from [[oldname]] to [[newname]]. Both key and printed name are changed. No - conformance checks are conducted.""" - realoldname = oldname.lower() - realnewname = newname.lower() - if realnewname in self.lower_keys: - raise StarError('Cannot change blockname %s to %s as %s already present' % (oldname,newname,newname)) - if realoldname not in self.lower_keys: - raise KeyError('Cannot find old block %s' % realoldname) - self._rekey(realoldname,realnewname,block_id=newname) - - def makebc(self,namelist,scoping='dictionary'): - """Make a block collection from a list of block names""" - newbc = BlockCollection() - block_lower = [n.lower() for n in namelist] - proto_child_table = [a for a in self.child_table.items() if a[0] in block_lower] - newbc.child_table = dict(proto_child_table) - new_top_level = [(a[0],self.PC(a[1].block_id,None)) for a in newbc.child_table.items() if a[1].parent not in block_lower] - newbc.child_table.update(dict(new_top_level)) - newbc.lower_keys = set([a[0] for a in proto_child_table]) - newbc.dictionary = dict((a[0],self.dictionary[a[0]]) for a in proto_child_table) - newbc.scoping = scoping - newbc.block_input_order = block_lower - return newbc - - - def merge_fast(self,new_bc,parent=None): - """Do a fast merge. WARNING: this may change one or more of its frame headers in order to - remove duplicate frames. Please keep a handle to the block object instead of the text of - the header.""" - if self.standard is None: - mode = 'replace' - else: - mode = 'strict' - overlap_flag = not self.lower_keys.isdisjoint(new_bc.lower_keys) - if parent is not None: - parent_name = [a[0] for a in self.dictionary.items() if a[1] == parent] - if len(parent_name)==0 or len(parent_name)>1: - raise StarError("Unable to find unique parent block name: have %s" % str(parent_name)) - parent_name = parent_name[0] - else: - parent_name = None #an error will be thrown if we treat as a string - if overlap_flag and mode != 'replace': - double_keys = self.lower_keys.intersection(new_bc.lower_keys) - for dup_key in double_keys: - our_parent = self.child_table[dup_key].parent - their_parent = new_bc.child_table[dup_key].parent - if (our_parent is None and their_parent is not None and parent is None) or\ - parent is not None: #rename our block - start_key = dup_key - while start_key in self.lower_keys: start_key = start_key+'+' - self._rekey(dup_key,start_key) - if parent_name.lower() == dup_key: #we just renamed the prospective parent! - parent_name = start_key - elif our_parent is not None and their_parent is None and parent is None: - start_key = dup_key - while start_key in new_bc.lower_keys: start_key = start_key+'+' - new_bc._rekey(dup_key,start_key) - else: - raise StarError("In strict merge mode:duplicate keys %s" % dup_key) - self.dictionary.update(new_bc.dictionary) - self.lower_keys.update(new_bc.lower_keys) - self.visible_keys += (list(new_bc.lower_keys)) - self.block_input_order += new_bc.block_input_order - #print('Block input order now:' + repr(self.block_input_order)) - self.child_table.update(new_bc.child_table) - if parent_name is not None: #redo the child_table entries - reparent_list = [(a[0],a[1].block_id) for a in new_bc.child_table.items() if a[1].parent==None] - reparent_dict = [(a[0],self.PC(a[1],parent_name.lower())) for a in reparent_list] - self.child_table.update(dict(reparent_dict)) - - def merge(self,new_bc,mode=None,parent=None,single_block=[], - idblock="",match_att=[],match_function=None): - if mode is None: - if self.standard is None: - mode = 'replace' - else: - mode = 'strict' - if single_block: - self[single_block[0]].merge(new_bc[single_block[1]],mode, - match_att=match_att, - match_function=match_function) - return None - base_keys = [a[1].block_id for a in self.child_table.items()] - block_to_item = base_keys #default - new_keys = [a[1].block_id for a in new_bc.child_table.items()] #get list of incoming blocks - if match_att: - #make a blockname -> item name map - if match_function: - block_to_item = [match_function(self[a]) for a in self.keys()] - else: - block_to_item = [self[a].get(match_att[0],None) for a in self.keys()] - #print `block_to_item` - for key in new_keys: #run over incoming blocknames - if key == idblock: continue #skip dictionary id - basekey = key #default value - if len(match_att)>0: - attval = new_bc[key].get(match_att[0],0) #0 if ignoring matching - else: - attval = 0 - for ii in range(len(block_to_item)): #do this way to get looped names - thisatt = block_to_item[ii] #keyname in old block - #print "Looking for %s in %s" % (attval,thisatt) - if attval == thisatt or \ - (isinstance(thisatt,list) and attval in thisatt): - basekey = base_keys.pop(ii) - block_to_item.remove(thisatt) - break - if not basekey in self or mode=="replace": - new_parent = new_bc.get_parent(key) - if parent is not None and new_parent is None: - new_parent = parent - self.NewBlock(basekey,new_bc[key],parent=new_parent) #add the block - else: - if mode=="strict": - raise StarError( "In strict merge mode: block %s in old and block %s in new files" % (basekey,key)) - elif mode=="overlay": - # print "Merging block %s with %s" % (basekey,key) - self[basekey].merge(new_bc[key],mode,match_att=match_att) - else: - raise StarError( "Merge called with unknown mode %s" % mode) - - def checknamelengths(self,target_block,maxlength=-1): - if maxlength < 0: - return - else: - toolong = [a for a in target_block.keys() if len(a)>maxlength] - outstring = "" - if toolong: - outstring = "\n".join(toolong) - raise StarError( 'Following data names too long:' + outstring) - - def get_all(self,item_name): - raw_values = [self[a].get(item_name) for a in self.keys()] - raw_values = [a for a in raw_values if a != None] - ret_vals = [] - for rv in raw_values: - if isinstance(rv,list): - for rvv in rv: - if rvv not in ret_vals: ret_vals.append(rvv) - else: - if rv not in ret_vals: ret_vals.append(rv) - return ret_vals - - def __setattr__(self,attr_name,newval): - if attr_name == 'scoping': - if newval not in ('dictionary','instance'): - raise StarError("Star file may only have 'dictionary' or 'instance' scoping, not %s" % newval) - if newval == 'dictionary': - self.visible_keys = [a for a in self.lower_keys] - else: - #only top-level datablocks visible - self.visible_keys = [a[0] for a in self.child_table.items() if a[1].parent==None] - object.__setattr__(self,attr_name,newval) - - def get_parent(self,blockname): - """Return the name of the block enclosing [[blockname]] in canonical form (lower case)""" - possibles = (a for a in self.child_table.items() if a[0] == blockname.lower()) - try: - first = next(possibles) #get first one - except: - raise StarError('no parent for %s' % blockname) - try: - second = next(possibles) - except StopIteration: - return first[1].parent - raise StarError('More than one parent for %s' % blockname) - - def get_roots(self): - """Get the top-level blocks""" - return [a for a in self.child_table.items() if a[1].parent==None] - - def get_children(self,blockname,include_parent=False,scoping='dictionary'): - """Get all children of [[blockname]] as a block collection. If [[include_parent]] is - True, the parent block will also be included in the block collection as the root.""" - newbc = BlockCollection() - block_lower = blockname.lower() - proto_child_table = [a for a in self.child_table.items() if self.is_child_of_parent(block_lower,a[1].block_id)] - newbc.child_table = dict(proto_child_table) - if not include_parent: - newbc.child_table.update(dict([(a[0],self.PC(a[1].block_id,None)) for a in proto_child_table if a[1].parent == block_lower])) - newbc.lower_keys = set([a[0] for a in proto_child_table]) - newbc.dictionary = dict((a[0],self.dictionary[a[0]]) for a in proto_child_table) - if include_parent: - newbc.child_table.update({block_lower:self.PC(self.child_table[block_lower].block_id,None)}) - newbc.lower_keys.add(block_lower) - newbc.dictionary.update({block_lower:self.dictionary[block_lower]}) - newbc.scoping = scoping - return newbc - - def get_immediate_children(self,parentname): - """Get the next level of children of the given block as a list, without nested levels""" - child_handles = [a for a in self.child_table.items() if a[1].parent == parentname.lower()] - return child_handles - - # This takes time - def get_child_list(self,parentname): - """Get a list of all child categories in alphabetical order""" - child_handles = [a[0] for a in self.child_table.items() if self.is_child_of_parent(parentname.lower(),a[0])] - child_handles.sort() - return child_handles - - def is_child_of_parent(self,parentname,blockname): - """Return `True` if `blockname` is a child of `parentname`""" - checkname = parentname.lower() - more_children = [a[0] for a in self.child_table.items() if a[1].parent == checkname] - if blockname.lower() in more_children: - return True - else: - for one_child in more_children: - if self.is_child_of_parent(one_child,blockname): return True - return False - - def set_parent(self,parentname,childname): - """Set the parent block""" - # first check that both blocks exist - if parentname.lower() not in self.lower_keys: - raise KeyError('Parent block %s does not exist' % parentname) - if childname.lower() not in self.lower_keys: - raise KeyError('Child block %s does not exist' % childname) - old_entry = self.child_table[childname.lower()] - self.child_table[childname.lower()]=self.PC(old_entry.block_id, - parentname.lower()) - self.scoping = self.scoping #reset visibility - - def SetTemplate(self,template_file): - """Use `template_file` as a template for all block output""" - self.master_template = process_template(template_file) - for b in self.dictionary.values(): - b.formatting_hints = self.master_template - - def WriteOut(self,comment='',wraplength=80,maxoutlength=0,blockorder=None,saves_after=None): - """Return the contents of this file as a string, wrapping if possible at `wraplength` - characters and restricting maximum line length to `maxoutlength`. Delimiters and - save frame nesting are controlled by `self.grammar`. If `blockorder` is - provided, blocks are output in this order unless nested save frames have been - requested (STAR2). The default block order is the order in which blocks were input. - `saves_after` inserts all save frames after the given dataname, - which allows less important items to appear later. Useful in conjunction with a - template for dictionary files.""" - if maxoutlength != 0: - self.SetOutputLength(maxoutlength) - if not comment: - comment = self.header_comment - outstring = StringIO() - if self.grammar == "2.0" and comment[0:10] != r"#\#CIF_2.0": - outstring.write(r"#\#CIF_2.0" + "\n") - outstring.write(comment) - # prepare all blocks - for b in self.dictionary.values(): - b.set_grammar(self.grammar) - b.formatting_hints = self.master_template - b.SetOutputLength(wraplength,self.maxoutlength) - # loop over top-level - # monitor output - all_names = list(self.child_table.keys()) #i.e. lower case - if blockorder is None: - blockorder = self.block_input_order - top_block_names = [(a,self.child_table[a].block_id) for a in blockorder if self.child_table[a].parent is None] - for blockref,blockname in top_block_names: - #print('Writing %s, ' % blockname + repr(self[blockref])) - outstring.write('\n' + 'data_' +blockname+'\n') - all_names.remove(blockref) - if self.standard == 'Dic': #put contents before save frames - outstring.write(self[blockref].printsection(finish_at='_dictionary_valid.application')) - if self.grammar == 'STAR2': #nested save frames - child_refs = self.get_immediate_children(blockref) - for child_ref,child_info in child_refs: - child_name = child_info.block_id - outstring.write('\n\n' + 'save_' + child_name + '\n') - self.block_to_string_nested(child_ref,child_name,outstring,4) - outstring.write('\n' + 'save_'+ '\n') - elif self.grammar in ('1.0','1.1','2.0'): #non-nested save frames - child_refs = [a for a in blockorder if self.is_child_of_parent(blockref,a)] - for child_ref in child_refs: - child_name = self.child_table[child_ref].block_id - outstring.write('\n\n' + 'save_' + child_name + '\n') - outstring.write(str(self[child_ref])) - outstring.write('\n\n' + 'save_' + '\n') - all_names.remove(child_ref.lower()) - else: - raise StarError('Grammar %s is not recognised for output' % self.grammar) - if self.standard != 'Dic': #put contents after save frames - outstring.write(str(self[blockref])) - else: - outstring.write(self[blockref].printsection(start_from='_dictionary_valid.application')) - returnstring = outstring.getvalue() - outstring.close() - if len(all_names)>0: - print('WARNING: following blocks not output: %s' % repr(all_names)) - else: - #print('All blocks output.') - pass - return returnstring - - def block_to_string_nested(self,block_ref,block_id,outstring,indentlevel=0): - """Output a complete datablock indexed by [[block_ref]] and named [[block_id]], including children, - and syntactically nesting save frames""" - child_refs = self.get_immediate_children(block_ref) - self[block_ref].set_grammar(self.grammar) - if self.standard == 'Dic': - outstring.write(str(self[block_ref])) - for child_ref,child_info in child_refs: - child_name = child_info.block_id - outstring.write('\n' + 'save_' + child_name + '\n') - self.block_to_string_nested(child_ref,child_name,outstring,indentlevel) - outstring.write('\n' + ' '*indentlevel + 'save_' + '\n') - if self.standard != 'Dic': - outstring.write(str(self[block_ref])) - - -class StarFile(BlockCollection): - def __init__(self,datasource=None,maxinlength=-1,maxoutlength=0, - scoping='instance',grammar='1.1',scantype='standard', - **kwargs): - super(StarFile,self).__init__(datasource=datasource,**kwargs) - self.my_uri = getattr(datasource,'my_uri','') - if maxoutlength == 0: - self.maxoutlength = 2048 - else: - self.maxoutlength = maxoutlength - self.scoping = scoping - if isinstance(datasource,(unicode,str)) or hasattr(datasource,"read"): - ReadStar(datasource,prepared=self,grammar=grammar,scantype=scantype, - maxlength = maxinlength) - self.header_comment = \ -"""#\\#STAR -########################################################################## -# STAR Format file -# Produced by PySTARRW module -# -# This is a STAR file. STAR is a superset of the CIF file type. For -# more information, please refer to International Tables for Crystallography, -# Volume G, Chapter 2.1 -# -########################################################################## -""" - def set_uri(self,my_uri): self.my_uri = my_uri - - -import math -class CIFStringIO(StringIO): - def __init__(self,target_width=80,**kwargs): - StringIO.__init__(self,**kwargs) - self.currentpos = 0 - self.target_width = target_width - self.tabwidth = -1 - self.indentlist = [0] - self.last_char = "" - - def write(self,outstring,canbreak=False,mustbreak=False,do_tab=True,newindent=False,unindent=False, - delimiter=False,startcol=-1): - """Write a string with correct linebreak, tabs and indents""" - # do we need to break? - if delimiter: - if len(outstring)>1: - raise ValueError('Delimiter %s is longer than one character' % repr( outstring )) - output_delimiter = True - if mustbreak: #insert a new line and indent - temp_string = '\n' + ' ' * self.indentlist[-1] - StringIO.write(self,temp_string) - self.currentpos = self.indentlist[-1] - self.last_char = temp_string[-1] - if self.currentpos+len(outstring)>self.target_width: #try to break - if not delimiter and outstring[0]!='\n': #ie ; - if canbreak: - temp_string = '\n' + ' ' * self.indentlist[-1] - StringIO.write(self,temp_string) - self.currentpos = self.indentlist[-1] - self.last_char = temp_string[-1] - else: #assume a break will be forced on next value - output_delimiter = False #the line break becomes the delimiter - #try to match requested column - if startcol > 0: - if self.currentpos < startcol: - StringIO.write(self,(startcol - self.currentpos)* ' ') - self.currentpos = startcol - self.last_char = ' ' - else: - print('Could not format %s at column %d as already at %d' % (outstring,startcol,self.currentpos)) - startcol = -1 #so that tabbing works as a backup - #handle tabs - if self.tabwidth >0 and do_tab and startcol < 0: - next_stop = ((self.currentpos//self.tabwidth)+1)*self.tabwidth - #print 'Currentpos %d: Next tab stop at %d' % (self.currentpos,next_stop) - if self.currentpos < next_stop: - StringIO.write(self,(next_stop-self.currentpos)*' ') - self.currentpos = next_stop - self.last_char = ' ' - #calculate indentation after tabs and col setting applied - if newindent: #indent by current amount - if self.indentlist[-1] == 0: #first time - self.indentlist.append(self.currentpos) - # print 'Indentlist: ' + `self.indentlist` - else: - self.indentlist.append(self.indentlist[-1]+2) - elif unindent: - if len(self.indentlist)>1: - self.indentlist.pop() - else: - print('Warning: cannot unindent any further') - #check that we still need a delimiter - if self.last_char in [' ','\n','\t']: - output_delimiter = False - #now output the string - every invocation comes through here - if (delimiter and output_delimiter) or not delimiter: - StringIO.write(self,outstring) - last_line_break = outstring.rfind('\n') - if last_line_break >=0: - self.currentpos = len(outstring)-last_line_break - else: - self.currentpos = self.currentpos + len(outstring) - #remember the last character - if len(outstring)>0: - self.last_char = outstring[-1] - - def set_tab(self,tabwidth): - """Set the tab stop position""" - self.tabwidth = tabwidth - -class StarError(Exception): - def __init__(self,value): - self.value = value - def __str__(self): - return '\nStar Format error: '+ self.value - -class StarLengthError(Exception): - def __init__(self,value): - self.value = value - def __str__(self): - return '\nStar length error: ' + self.value - -class StarDerivationError(Exception): - def __init__(self,fail_name): - self.fail_name = fail_name - def __str__(self): - return "Derivation of %s failed, None returned" % self.fail_name - -# -# This is subclassed from AttributeError in order to allow hasattr -# to work. -# -class StarDerivationFailure(AttributeError): - def __init__(self,fail_name): - self.fail_name = fail_name - def __str__(self): - return "Derivation of %s failed" % self.fail_name - -def ReadStar(filename,prepared = None, maxlength=-1, - scantype='standard',grammar='STAR2',CBF=False): - - """ Read in a STAR file, returning the contents in the `prepared` object. - - * `filename` may be a URL, a file - path on the local system, or any object with a `read` method. - - * `prepared` provides a `StarFile` or `CifFile` object that the contents of `filename` - will be added to. - - * `maxlength` is the maximum allowable line length in the input file. This has been set at - 2048 characters for CIF but is unlimited (-1) for STAR files. - - * `grammar` chooses the STAR grammar variant. `1.0` is the original 1992 CIF/STAR grammar and `1.1` - is identical except for the exclusion of square brackets as the first characters in - undelimited datanames. `2.0` will read files in the CIF2.0 standard, and `STAR2` will - read files according to the STAR2 publication. If grammar is `None` or `auto`, autodetection - will be attempted in the order `2.0`, `1.1` and `1.0`. This will always succeed for conformant CIF2.0 files. - Note that (nested) save frames are read in all grammar variations and then flagged afterwards if - they do not match the requested grammar. - - * `scantype` can be `standard` or `flex`. `standard` provides pure Python parsing at the - cost of a factor of 10 or so in speed. `flex` will tokenise the input CIF file using - fast C routines. Note that running PyCIFRW in Jython uses native Java regular expressions - to provide a speedup regardless of this argument. - - * `CBF` flags that the input file is in Crystallographic Binary File format. The binary block is - excised from the input data stream before parsing and is not available in the returned object. - """ - - import string - import codecs - # save desired scoping - save_scoping = prepared.scoping - from . import YappsStarParser_1_1 as Y11 - from . import YappsStarParser_1_0 as Y10 - from . import YappsStarParser_2_0 as Y20 - from . import YappsStarParser_STAR2 as YST - if prepared is None: - prepared = StarFile() - if grammar == "auto" or grammar is None: - try_list = [('2.0',Y20),('1.1',Y11),('1.0',Y10)] - elif grammar == '1.0': - try_list = [('1.0',Y10)] - elif grammar == '1.1': - try_list = [('1.1',Y11)] - elif grammar == '2.0': - try_list = [('2.0',Y20)] - elif grammar == 'STAR2': - try_list = [('STAR2',YST)] - else: - raise AttributeError('Unknown STAR/CIF grammar requested, %s' % repr( grammar )) - if isinstance(filename,(unicode,str)): - # create an absolute URL - relpath = urlparse(filename) - if relpath.scheme == "": - if not os.path.isabs(filename): - fullpath = os.path.join(os.getcwd(),filename) - else: - fullpath = filename - newrel = list(relpath) - newrel[0] = "file" - newrel[2] = fullpath - my_uri = urlunparse(newrel) - else: - my_uri = urlunparse(relpath) - # print("Full URL is: " + my_uri) - filestream = urlopen(my_uri) -# text = filestream.read().decode('utf8') - text = filestream.read().decode('latin1') - filestream.close() - else: - filestream = filename #already opened for us - text = filestream.read() - if not isinstance(text,unicode): -# text = text.decode('utf8') #CIF is always ascii/utf8 - text = text.decode('latin1') #CIF is always ascii/utf8 - my_uri = "" - if not text: # empty file, return empty block - return prepared.set_uri(my_uri) - # filter out non-ASCII characters in CBF files if required. We assume - # that the binary is enclosed in a fixed string that occurs - # nowhere else. - if CBF: - text_bits = text.split("-BINARY-FORMAT-SECTION-") - text = text_bits[0] - for section in range(2,len(text_bits),2): - text = text+" (binary omitted)"+text_bits[section] - # we recognise ctrl-Z as end of file - endoffile = text.find(chr(26)) - if endoffile >= 0: - text = text[:endoffile] - split = text.split('\n') - if maxlength > 0: - toolong = [a for a in split if len(a)>maxlength] - if toolong: - pos = split.index(toolong[0]) - raise StarError( 'Line %d contains more than %d characters' % (pos+1,maxlength)) - # honour the header string - if text[:10] != "#\#CIF_2.0" and ('2.0',Y20) in try_list: - try_list.remove(('2.0',Y20),) - if not try_list: - raise StarError('File %s missing CIF2.0 header' % (filename)) - for grammar_name,Y in try_list: - if scantype == 'standard' or grammar_name in ['2.0','STAR2']: - parser = Y.StarParser(Y.StarParserScanner(text)) - else: - parser = Y.StarParser(Y.yappsrt.Scanner(None,[],text,scantype='flex')) - # handle encoding switch - if grammar_name in ['2.0','STAR2']: - prepared.set_characterset('unicode') - else: - prepared.set_characterset('ascii') - proto_star = None - try: - proto_star = getattr(parser,"input")(prepared) - except Y.yappsrt.SyntaxError as e: - input = parser._scanner.input - Y.yappsrt.print_error(input, e, parser._scanner) - except Y.yappsrt.NoMoreTokens: - print('Could not complete parsing; stopped around here:',file=sys.stderr) - print(parser._scanner,file=sys.stderr) - except ValueError: - print('Unexpected error:') - import traceback - traceback.print_exc() - if proto_star is not None: - proto_star.set_grammar(grammar_name) #remember for output - break - if proto_star is None: - errorstring = 'Syntax error in input file: last value parsed was %s' % Y.lastval - errorstring = errorstring + '\nParser status: %s' % repr( parser._scanner ) - raise StarError( errorstring) - # set visibility correctly - proto_star.scoping = 'dictionary' - proto_star.set_uri(my_uri) - proto_star.scoping = save_scoping - return proto_star - -def get_dim(dataitem,current=0,packlen=0): - zerotypes = [int, float, str] - if type(dataitem) in zerotypes: - return current, packlen - if not dataitem.__class__ == ().__class__ and \ - not dataitem.__class__ == [].__class__: - return current, packlen - elif len(dataitem)>0: - # print "Get_dim: %d: %s" % (current,`dataitem`) - return get_dim(dataitem[0],current+1,len(dataitem)) - else: return current+1,0 - -def apply_line_folding(instring,minwraplength=60,maxwraplength=80): - """Insert line folding characters into instring between min/max wraplength""" - # first check that we need to do this - lines = instring.split('\n') - line_len = [len(l) for l in lines] - if max(line_len) < maxwraplength and re.match("\\[ \v\t\f]*\n",instring) is None: - return instring - outstring = "\\\n" #header - for l in lines: - if len(l) < maxwraplength: - outstring = outstring + l - if len(l) > 0 and l[-1]=='\\': #who'da thunk it? A line ending with a backslash - outstring = outstring + "\\\n" # - outstring = outstring + "\n" # put back the split character - else: - current_bit = l - while len(current_bit) > maxwraplength: - space_pos = re.search('[ \v\f\t]+',current_bit[minwraplength:]) - if space_pos is not None and space_pos.start()[^;\\\n][^\n\\\\]+)(?P\\\\{1,2}[ \t\v\f]*\n)",instring) - if prefix_match is not None: - prefix_text = prefix_match.group('prefix') - print('Found prefix %s' % prefix_text) - prefix_end = prefix_match.end('folding') - # keep any line folding instructions - if prefix_match.group('folding')[:2]=='\\\\': #two backslashes - outstring = instring[prefix_match.end('folding')-1:].replace("\n"+prefix_text,"\n") - return "\\" + outstring #keep line folding first line - else: - outstring = instring[prefix_match.end('folding')-1:].replace("\n"+prefix_text,"\n") - return outstring[1:] #drop first line ending, no longer necessary - else: - return instring - - -def listify(item): - if isinstance(item,unicode): return [item] - else: return item - -#Transpose the list of lists passed to us -def transpose(base_list): - new_lofl = [] - full_length = len(base_list) - opt_range = range(full_length) - for i in range(len(base_list[0])): - new_packet = [] - for j in opt_range: - new_packet.append(base_list[j][i]) - new_lofl.append(new_packet) - return new_lofl - -# This routine optimised to return as quickly as possible -# as it is called a lot. -def not_none(itemlist): - """Return true only if no values of None are present""" - if itemlist is None: - return False - if not isinstance(itemlist,(tuple,list)): - return True - for x in itemlist: - if not not_none(x): return False - return True - - -def check_stringiness(data): - """Check that the contents of data are all strings""" - if not hasattr(data,'dtype'): #so not Numpy - from numbers import Number - if isinstance(data,Number): return False - elif isinstance(data,(unicode,str)): return True - elif data is None:return False #should be data are None :) - else: - for one_item in data: - if not check_stringiness(one_item): return False - return True #all must be strings - else: #numerical python - import numpy - if data.ndim == 0: #a bare value - if data.dtype.kind in ['S','U']: return True - else: return False - else: - for one_item in numpy.nditer(data): - print('numpy data: ' + repr( one_item )) - if not check_stringiness(one_item): return False - return True - -def process_template(template_file): - """Process a template datafile to formatting instructions""" - template_as_cif = StarFile(template_file,grammar="2.0").first_block() - if isinstance(template_file,(unicode,str)): - template_string = open(template_file).read() - else: #a StringIO object - template_file.seek(0) #reset - template_string = template_file.read() - #template_as_lines = template_string.split("\n") - #template_as_lines = [l for l in template_as_lines if len(l)>0 and l[0]!='#'] - #template_as_lines = [l for l in template_as_lines if l.split()[0] != 'loop_'] - #template_full_lines = dict([(l.split()[0],l) for l in template_as_lines if len(l.split())>0]) - form_hints = [] #ordered array of hint dictionaries - find_indent = "^ +" - for item in template_as_cif.item_order: #order of input - if not isinstance(item,int): #not nested - hint_dict = {"dataname":item} - # find the line in the file - start_pos = re.search("(^[ \t]*(?P" + item + ")[ \t\n]+)(?P([\S]+)|(^;))",template_string,re.I|re.M) - if start_pos.group("spec") != None: - spec_pos = start_pos.start("spec")-start_pos.start(0) - spec_char = template_string[start_pos.start("spec"):start_pos.start("spec")+3] - if spec_char[0] in '\'";': - hint_dict.update({"delimiter":spec_char[0]}) - if spec_char == '"""' or spec_char == "'''": - hint_dict.update({"delimiter":spec_char}) - if spec_char[0] != ";": #so we need to work out the column number - hint_dict.update({"column":spec_pos}) - else: #need to put in the carriage return - hint_dict.update({"delimiter":"\n;"}) - # can we format the text? - text_val = template_as_cif[item] - hint_dict["reformat"] = "\n\t" in text_val or "\n " in text_val - if hint_dict["reformat"]: #find the indentation - p = re.search(find_indent,text_val,re.M) - if p.group() is not None: - hint_dict["reformat_indent"]=p.end() - p.start() - if start_pos.group('name') != None: - name_pos = start_pos.start('name') - start_pos.start(0) - hint_dict.update({"name_pos":name_pos}) - #print '%s: %s' % (item,`hint_dict`) - form_hints.append(hint_dict) - else: #loop block - testnames = template_as_cif.loops[item] - total_items = len(template_as_cif.loops[item]) - testname = testnames[0] - #find the loop spec line in the file - loop_regex = "(^[ \t]*(?Ploop_)[ \t\n\r]+(?P" + testname + ")([ \t\n\r]+_[\S]+){%d}[ \t]*$(?P(.(?!_loop|_[\S]+))*))" % (total_items - 1) - loop_line = re.search(loop_regex,template_string,re.I|re.M|re.S) - loop_so_far = loop_line.end() - packet_text = loop_line.group('packet') - loop_indent = loop_line.start('loop') - loop_line.start(0) - form_hints.append({"dataname":'loop','name_pos':loop_indent}) - packet_regex = "[ \t]*(?P(?P'''([^\n\r\f']*)''')|(?P'([^\n\r\f']*)'+)|(?P\"([^\n\r\"]*)\"+)|(?P[^\s]+))" - packet_pos = re.finditer(packet_regex,packet_text) - line_end_pos = re.finditer("^",packet_text,re.M) - next_end = next(line_end_pos).end() - last_end = next_end - for loopname in testnames: - #find the name in the file for name pos - name_regex = "(^[ \t]*(?P" + loopname + "))" - name_match = re.search(name_regex,template_string,re.I|re.M|re.S) - loop_name_indent = name_match.start('name')-name_match.start(0) - hint_dict = {"dataname":loopname,"name_pos":loop_name_indent} - #find the value - thismatch = next(packet_pos) - while thismatch.start('all') > next_end: - try: - last_end = next_end - next_end = next(line_end_pos).start() - print('next end %d' % next_end) - except StopIteration: - break - print('Start %d, last_end %d' % (thismatch.start('all'),last_end)) - col_pos = thismatch.start('all') - last_end + 1 - if thismatch.group('none') is None: - if thismatch.group('sqqq') is not None: - hint_dict.update({'delimiter':"'''"}) - else: - hint_dict.update({'delimiter':thismatch.groups()[0][0]}) - hint_dict.update({'column':col_pos}) - print('%s: %s' % (loopname,repr( hint_dict ))) - form_hints.append(hint_dict) - return form_hints - - -#No documentation flags - diff --git a/GSASII/CifFile/TypeContentsParser.py b/GSASII/CifFile/TypeContentsParser.py deleted file mode 100644 index 322e9d08e..000000000 --- a/GSASII/CifFile/TypeContentsParser.py +++ /dev/null @@ -1,82 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -# -# helper code: we define our match tokens -lastval = '' -def monitor(location,value): - global lastval - #print 'At %s: %s' % (location,repr(value)) - lastval = repr(value) - return value - - -# Begin -- grammar generated by Yapps -import sys, re -from . import yapps3_compiled_rt as yappsrt - -class TypeParserScanner(yappsrt.Scanner): - def __init__(self, *args,**kwargs): - patterns = [ - ('([ \t\n\r])', '([ \t\n\r])'), - ('container', '[A-Za-z]+\\('), - ('identifier', '[A-Za-z]+'), - ('c_c_b', '\\)'), - ('o_c_b', '\\('), - ('comma', '\\,'), - ('END', '$'), - ] - yappsrt.Scanner.__init__(self,patterns,['([ \t\n\r])'],*args,**kwargs) - -class TypeParser(yappsrt.Parser): - Context = yappsrt.Context - def input(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'input', []) - base_element = self.base_element(_context) - p = [base_element] - while self._peek('END', 'comma') == 'comma': - comma = self._scan('comma') - base_element = self.base_element(_context) - p.append(base_element) - if self._peek() not in ['END', 'comma']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['comma', 'END'])) - END = self._scan('END') - if len(p)==1: p = p[0] - return p - - def base_element(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'base_element', []) - _token = self._peek('container', 'identifier') - if _token == 'container': - container = self._scan('container') - element_list = self.element_list(_context) - c_c_b = self._scan('c_c_b') - return element_list - else: # == 'identifier' - identifier = self._scan('identifier') - return identifier - - def element_list(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'element_list', []) - base_element = self.base_element(_context) - p = [base_element] - while self._peek('comma', 'c_c_b') == 'comma': - comma = self._scan('comma') - base_element = self.base_element(_context) - p.append(base_element) - if self._peek() not in ['comma', 'c_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['comma', 'c_c_b'])) - return p - - -def parse(rule, text): - P = TypeParser(TypeParserScanner(text)) - return yappsrt.wrap_error_reporter(P, rule) - -# End -- grammar generated by Yapps - - - diff --git a/GSASII/CifFile/YappsStarParser_1_0.py b/GSASII/CifFile/YappsStarParser_1_0.py deleted file mode 100755 index 4dc887066..000000000 --- a/GSASII/CifFile/YappsStarParser_1_0.py +++ /dev/null @@ -1,288 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -from .StarFile import StarBlock,StarFile,StarList,StarDict -from io import StringIO -# An alternative specification for the Cif Parser, based on Yapps2 -# by Amit Patel (http://theory.stanford.edu/~amitp/Yapps) -# -# helper code: we define our match tokens -lastval = '' -def monitor(location,value): - global lastval - #print 'At %s: %s' % (location,repr(value)) - lastval = repr(value) - return value - -# Strip extras gets rid of leading and trailing whitespace, and -# semicolons. -def stripextras(value): - from .StarFile import remove_line_folding, remove_line_prefix - # we get rid of semicolons and leading/trailing terminators etc. - import re - jj = re.compile("[\n\r\f \t\v]*") - semis = re.compile("[\n\r\f \t\v]*[\n\r\f]\n*;") - cut = semis.match(value) - if cut: #we have a semicolon-delimited string - nv = value[cut.end():len(value)-2] - try: - if nv[-1]=='\r': nv = nv[:-1] - except IndexError: #empty data value - pass - # apply protocols - nv = remove_line_prefix(nv) - nv = remove_line_folding(nv) - return nv - else: - cut = jj.match(value) - if cut: - return stripstring(value[cut.end():]) - return value - -# helper function to get rid of inverted commas etc. - -def stripstring(value): - if value: - if value[0]== '\'' and value[-1]=='\'': - return value[1:-1] - if value[0]=='"' and value[-1]=='"': - return value[1:-1] - return value - -# helper function to get rid of triple quotes -def striptriple(value): - if value: - if value[:3] == '"""' and value[-3:] == '"""': - return value[3:-3] - if value[:3] == "'''" and value[-3:] == "'''": - return value[3:-3] - return value - -# helper function to populate a StarBlock given a list of names -# and values . -# -# Note that there may be an empty list at the very end of our itemlists, -# so we remove that if necessary. -# - -def makeloop(target_block,loopdata): - loop_seq,itemlists = loopdata - if itemlists[-1] == []: itemlists.pop(-1) - # print 'Making loop with %s' % repr(itemlists) - step_size = len(loop_seq) - for col_no in range(step_size): - target_block.AddItem(loop_seq[col_no], itemlists[col_no::step_size],precheck=True) - # print 'Makeloop constructed %s' % repr(loopstructure) - # now construct the loop - try: - target_block.CreateLoop(loop_seq) #will raise ValueError on problem - except ValueError: - error_string = 'Incorrect number of loop values for loop containing %s' % repr(loop_seq) - print(error_string, file=sys.stderr) - raise ValueError(error_string) - -# return an object with the appropriate amount of nesting -def make_empty(nestlevel): - gd = [] - for i in range(1,nestlevel): - gd = [gd] - return gd - -# this function updates a dictionary first checking for name collisions, -# which imply that the CIF is invalid. We need case insensitivity for -# names. - -# Unfortunately we cannot check loop item contents against non-loop contents -# in a non-messy way during parsing, as we may not have easy access to previous -# key value pairs in the context of our call (unlike our built-in access to all -# previous loops). -# For this reason, we don't waste time checking looped items against non-looped -# names during parsing of a data block. This would only match a subset of the -# final items. We do check against ordinary items, however. -# -# Note the following situations: -# (1) new_dict is empty -> we have just added a loop; do no checking -# (2) new_dict is not empty -> we have some new key-value pairs -# -def cif_update(old_dict,new_dict,loops): - old_keys = map(lambda a:a.lower(),old_dict.keys()) - if new_dict != {}: # otherwise we have a new loop - #print 'Comparing %s to %s' % (repr(old_keys),repr(new_dict.keys())) - for new_key in new_dict.keys(): - if new_key.lower() in old_keys: - raise CifError("Duplicate dataname or blockname %s in input file" % new_key) - old_dict[new_key] = new_dict[new_key] -# -# this takes two lines, so we couldn't fit it into a one line execution statement... -def order_update(order_array,new_name): - order_array.append(new_name) - return new_name - -# and finally...turn a sequence into a python dict (thanks to Stackoverflow) -def pairwise(iterable): - it = iter(iterable) - while 1: - yield next(it), next(it) - - -# Begin -- grammar generated by Yapps -import sys, re -from . import yapps3_compiled_rt as yappsrt - -class StarParserScanner(yappsrt.Scanner): - def __init__(self, *args,**kwargs): - patterns = [ - ('([ \t\n\r](?!;))|[ \t]', '([ \t\n\r](?!;))|[ \t]'), - ('(#.*[\n\r](?!;))|(#.*)', '(#.*[\n\r](?!;))|(#.*)'), - ('LBLOCK', '(L|l)(O|o)(O|o)(P|p)_'), - ('GLOBAL', '(G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_'), - ('STOP', '(S|s)(T|t)(O|o)(P|p)_'), - ('save_heading', '(S|s)(A|a)(V|v)(E|e)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+'), - ('save_end', '(S|s)(A|a)(V|v)(E|e)_'), - ('data_name', '_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+'), - ('data_heading', '(D|d)(A|a)(T|t)(A|a)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+'), - ('start_sc_line', '(\n|\r\n);([^\n\r])*(\r\n|\r|\n)+'), - ('sc_line_of_text', '[^;\r\n]([^\r\n])*(\r\n|\r|\n)+'), - ('end_sc_line', ';'), - ('data_value_1', '((?!(((S|s)(A|a)(V|v)(E|e)_[^\\s]*)|((G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_[^\\s]*)|((S|s)(T|t)(O|o)(P|p)_[^\\s]*)|((D|d)(A|a)(T|t)(A|a)_[^\\s]*)))[^\\s"#$\'_][^\\s]*)|\'((\'(?=\\S))|([^\n\r\x0c\']))*\'+|"(("(?=\\S))|([^\n\r"]))*"+'), - ('END', '$'), - ] - yappsrt.Scanner.__init__(self,patterns,['([ \t\n\r](?!;))|[ \t]', '(#.*[\n\r](?!;))|(#.*)'],*args,**kwargs) - -class StarParser(yappsrt.Parser): - Context = yappsrt.Context - def input(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'input', [prepared]) - _token = self._peek('END', 'data_heading') - if _token == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks = prepared;allblocks.merge_fast(dblock) - while self._peek('END', 'data_heading') == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks.merge_fast(dblock) - if self._peek() not in ['END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['END', 'data_heading'])) - END = self._scan('END') - else: # == 'END' - END = self._scan('END') - allblocks = prepared - return allblocks - - def dblock(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dblock', [prepared]) - data_heading = self._scan('data_heading') - heading = data_heading[5:];thisbc=StarFile(characterset='unicode',standard=prepared.standard);newname = thisbc.NewBlock(heading,StarBlock(overwrite=False));act_block=thisbc[newname] - while self._peek('save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(thisbc[heading], _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - thisbc.merge_fast(save_frame,parent=act_block) - if self._peek() not in ['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading'])) - thisbc[heading].setmaxnamelength(thisbc[heading].maxnamelength);return (monitor('dblock',thisbc)) - - def dataseq(self, starblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dataseq', [starblock]) - data = self.data(starblock, _context) - while self._peek('LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['LBLOCK', 'data_name']: - data = self.data(starblock, _context) - if self._peek() not in ['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - - def data(self, currentblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data', [currentblock]) - _token = self._peek('LBLOCK', 'data_name') - if _token == 'LBLOCK': - top_loop = self.top_loop(_context) - makeloop(currentblock,top_loop) - else: # == 'data_name' - datakvpair = self.datakvpair(_context) - currentblock.AddItem(datakvpair[0],datakvpair[1],precheck=True) - - def datakvpair(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'datakvpair', []) - data_name = self._scan('data_name') - data_value = self.data_value(_context) - return [data_name,data_value] - - def data_value(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data_value', []) - _token = self._peek('data_value_1', 'start_sc_line') - if _token == 'data_value_1': - data_value_1 = self._scan('data_value_1') - thisval = stripstring(data_value_1) - else: # == 'start_sc_line' - sc_lines_of_text = self.sc_lines_of_text(_context) - thisval = stripextras(sc_lines_of_text) - return monitor('data_value',thisval) - - def sc_lines_of_text(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'sc_lines_of_text', []) - start_sc_line = self._scan('start_sc_line') - lines = StringIO();lines.write(start_sc_line) - while self._peek('end_sc_line', 'sc_line_of_text') == 'sc_line_of_text': - sc_line_of_text = self._scan('sc_line_of_text') - lines.write(sc_line_of_text) - if self._peek() not in ['end_sc_line', 'sc_line_of_text']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['sc_line_of_text', 'end_sc_line'])) - end_sc_line = self._scan('end_sc_line') - lines.write(end_sc_line);return monitor('sc_line_of_text',lines.getvalue()) - - def top_loop(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'top_loop', []) - LBLOCK = self._scan('LBLOCK') - loopfield = self.loopfield(_context) - loopvalues = self.loopvalues(_context) - return loopfield,loopvalues - - def loopfield(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopfield', []) - toploop=[] - while self._peek('data_name', 'data_value_1', 'start_sc_line') == 'data_name': - data_name = self._scan('data_name') - toploop.append(data_name) - if self._peek() not in ['data_name', 'data_value_1', 'start_sc_line']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_name', 'data_value_1', 'start_sc_line'])) - return toploop - - def loopvalues(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopvalues', []) - data_value = self.data_value(_context) - dataloop=[data_value] - while self._peek('data_value_1', 'start_sc_line', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['data_value_1', 'start_sc_line']: - data_value = self.data_value(_context) - dataloop.append(monitor('loopval',data_value)) - if self._peek() not in ['data_value_1', 'start_sc_line', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'start_sc_line', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - return dataloop - - def save_frame(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'save_frame', []) - save_heading = self._scan('save_heading') - savehead = save_heading[5:];savebc = StarFile();newname=savebc.NewBlock(savehead,StarBlock(overwrite=False));act_block=savebc[newname] - while self._peek('save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(savebc[savehead], _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - savebc.merge_fast(save_frame,parent=act_block) - if self._peek() not in ['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading'])) - save_end = self._scan('save_end') - return monitor('save_frame',savebc) - - -def parse(rule, text): - P = StarParser(StarParserScanner(text)) - return yappsrt.wrap_error_reporter(P, rule) - -# End -- grammar generated by Yapps - - - diff --git a/GSASII/CifFile/YappsStarParser_1_1.py b/GSASII/CifFile/YappsStarParser_1_1.py deleted file mode 100755 index e19d508d0..000000000 --- a/GSASII/CifFile/YappsStarParser_1_1.py +++ /dev/null @@ -1,287 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -from .StarFile import StarBlock,StarFile,StarList,StarDict -from io import StringIO -# An alternative specification for the Cif Parser, based on Yapps2 -# by Amit Patel (http://theory.stanford.edu/~amitp/Yapps) -# -# helper code: we define our match tokens -lastval = '' -def monitor(location,value): - global lastval - #print 'At %s: %s' % (location,repr(value)) - lastval = repr(value) - return value - -# Strip extras gets rid of leading and trailing whitespace, and -# semicolons. -def stripextras(value): - from .StarFile import remove_line_folding, remove_line_prefix - # we get rid of semicolons and leading/trailing terminators etc. - import re - jj = re.compile("[\n\r\f \t\v]*") - semis = re.compile("[\n\r\f \t\v]*[\n\r\f]\n*;") - cut = semis.match(value) - if cut: #we have a semicolon-delimited string - nv = value[cut.end():len(value)-2] - try: - if nv[-1]=='\r': nv = nv[:-1] - except IndexError: #empty data value - pass - # apply protocols - nv = remove_line_prefix(nv) - nv = remove_line_folding(nv) - return nv - else: - cut = jj.match(value) - if cut: - return stripstring(value[cut.end():]) - return value - -# helper function to get rid of inverted commas etc. - -def stripstring(value): - if value: - if value[0]== '\'' and value[-1]=='\'': - return value[1:-1] - if value[0]=='"' and value[-1]=='"': - return value[1:-1] - return value - -# helper function to get rid of triple quotes -def striptriple(value): - if value: - if value[:3] == '"""' and value[-3:] == '"""': - return value[3:-3] - if value[:3] == "'''" and value[-3:] == "'''": - return value[3:-3] - return value - -# helper function to populate a StarBlock given a list of names -# and values . -# -# Note that there may be an empty list at the very end of our itemlists, -# so we remove that if necessary. -# - -def makeloop(target_block,loopdata): - loop_seq,itemlists = loopdata - if itemlists[-1] == []: itemlists.pop(-1) - # print 'Making loop with %s' % repr(itemlists) - step_size = len(loop_seq) - for col_no in range(step_size): - target_block.AddItem(loop_seq[col_no], itemlists[col_no::step_size],precheck=True) - # print 'Makeloop constructed %s' % repr(loopstructure) - # now construct the loop - try: - target_block.CreateLoop(loop_seq) #will raise ValueError on problem - except ValueError: - error_string = 'Incorrect number of loop values for loop containing %s' % repr(loop_seq) - print(error_string, file=sys.stderr) - raise ValueError(error_string) - -# return an object with the appropriate amount of nesting -def make_empty(nestlevel): - gd = [] - for i in range(1,nestlevel): - gd = [gd] - return gd - -# this function updates a dictionary first checking for name collisions, -# which imply that the CIF is invalid. We need case insensitivity for -# names. - -# Unfortunately we cannot check loop item contents against non-loop contents -# in a non-messy way during parsing, as we may not have easy access to previous -# key value pairs in the context of our call (unlike our built-in access to all -# previous loops). -# For this reason, we don't waste time checking looped items against non-looped -# names during parsing of a data block. This would only match a subset of the -# final items. We do check against ordinary items, however. -# -# Note the following situations: -# (1) new_dict is empty -> we have just added a loop; do no checking -# (2) new_dict is not empty -> we have some new key-value pairs -# -def cif_update(old_dict,new_dict,loops): - old_keys = map(lambda a:a.lower(),old_dict.keys()) - if new_dict != {}: # otherwise we have a new loop - #print 'Comparing %s to %s' % (repr(old_keys),repr(new_dict.keys())) - for new_key in new_dict.keys(): - if new_key.lower() in old_keys: - raise CifError("Duplicate dataname or blockname %s in input file" % new_key) - old_dict[new_key] = new_dict[new_key] -# -# this takes two lines, so we couldn't fit it into a one line execution statement... -def order_update(order_array,new_name): - order_array.append(new_name) - return new_name - -# and finally...turn a sequence into a python dict (thanks to Stackoverflow) -def pairwise(iterable): - it = iter(iterable) - while 1: - yield next(it), next(it) - - -# Begin -- grammar generated by Yapps -import sys, re -from . import yapps3_compiled_rt as yappsrt - -class StarParserScanner(yappsrt.Scanner): - def __init__(self, *args,**kwargs): - patterns = [ - ('([ \t\n\r](?!;))|[ \t]', '([ \t\n\r](?!;))|[ \t]'), - ('(#.*[\n\r](?!;))|(#.*)', '(#.*[\n\r](?!;))|(#.*)'), - ('LBLOCK', '(L|l)(O|o)(O|o)(P|p)_'), - ('GLOBAL', '(G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_'), - ('STOP', '(S|s)(T|t)(O|o)(P|p)_'), - ('save_heading', '(S|s)(A|a)(V|v)(E|e)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+'), - ('save_end', '(S|s)(A|a)(V|v)(E|e)_'), - ('data_name', '_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+'), - ('data_heading', '(D|d)(A|a)(T|t)(A|a)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_-]+'), - ('start_sc_line', '(\n|\r\n);([^\n\r])*(\r\n|\r|\n)+'), - ('sc_line_of_text', '[^;\r\n]([^\r\n])*(\r\n|\r|\n)+'), - ('end_sc_line', ';'), - ('data_value_1', '((?!(((S|s)(A|a)(V|v)(E|e)_[^\\s]*)|((G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_[^\\s]*)|((S|s)(T|t)(O|o)(P|p)_[^\\s]*)|((D|d)(A|a)(T|t)(A|a)_[^\\s]*)))[^\\s"#$\'_\\(\\{\\[\\]][^\\s]*)|\'((\'(?=\\S))|([^\n\r\x0c\']))*\'+|"(("(?=\\S))|([^\n\r"]))*"+'), - ('END', '$'), - ] - yappsrt.Scanner.__init__(self,patterns,['([ \t\n\r](?!;))|[ \t]', '(#.*[\n\r](?!;))|(#.*)'],*args,**kwargs) - -class StarParser(yappsrt.Parser): - Context = yappsrt.Context - def input(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'input', [prepared]) - _token = self._peek('END', 'data_heading') - if _token == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks = prepared;allblocks.merge_fast(dblock) - while self._peek('END', 'data_heading') == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks.merge_fast(dblock) - if self._peek() not in ['END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['END', 'data_heading'])) - END = self._scan('END') - else: # == 'END' - END = self._scan('END') - allblocks = prepared - return allblocks - - def dblock(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dblock', [prepared]) - data_heading = self._scan('data_heading') - heading = data_heading[5:];thisbc=StarFile(characterset='unicode',standard=prepared.standard);newname = thisbc.NewBlock(heading,StarBlock(overwrite=False));act_block=thisbc[newname] - while self._peek('save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(thisbc[heading], _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - thisbc.merge_fast(save_frame,parent=act_block) - if self._peek() not in ['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading'])) - thisbc[heading].setmaxnamelength(thisbc[heading].maxnamelength);return (monitor('dblock',thisbc)) - - def dataseq(self, starblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dataseq', [starblock]) - data = self.data(starblock, _context) - while self._peek('LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['LBLOCK', 'data_name']: - data = self.data(starblock, _context) - if self._peek() not in ['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - - def data(self, currentblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data', [currentblock]) - _token = self._peek('LBLOCK', 'data_name') - if _token == 'LBLOCK': - top_loop = self.top_loop(_context) - makeloop(currentblock,top_loop) - else: # == 'data_name' - datakvpair = self.datakvpair(_context) - currentblock.AddItem(datakvpair[0],datakvpair[1],precheck=True) - - def datakvpair(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'datakvpair', []) - data_name = self._scan('data_name') - data_value = self.data_value(_context) - return [data_name,data_value] - - def data_value(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data_value', []) - _token = self._peek('data_value_1', 'start_sc_line') - if _token == 'data_value_1': - data_value_1 = self._scan('data_value_1') - thisval = stripstring(data_value_1) - else: # == 'start_sc_line' - sc_lines_of_text = self.sc_lines_of_text(_context) - thisval = stripextras(sc_lines_of_text) - return monitor('data_value',thisval) - - def sc_lines_of_text(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'sc_lines_of_text', []) - start_sc_line = self._scan('start_sc_line') - lines = StringIO();lines.write(start_sc_line) - while self._peek('end_sc_line', 'sc_line_of_text') == 'sc_line_of_text': - sc_line_of_text = self._scan('sc_line_of_text') - lines.write(sc_line_of_text) - if self._peek() not in ['end_sc_line', 'sc_line_of_text']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['sc_line_of_text', 'end_sc_line'])) - end_sc_line = self._scan('end_sc_line') - lines.write(end_sc_line);return monitor('sc_line_of_text',lines.getvalue()) - - def top_loop(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'top_loop', []) - LBLOCK = self._scan('LBLOCK') - loopfield = self.loopfield(_context) - loopvalues = self.loopvalues(_context) - return loopfield,loopvalues - - def loopfield(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopfield', []) - toploop=[] - while self._peek('data_name', 'data_value_1', 'start_sc_line') == 'data_name': - data_name = self._scan('data_name') - toploop.append(data_name) - if self._peek() not in ['data_name', 'data_value_1', 'start_sc_line']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_name', 'data_value_1', 'start_sc_line'])) - return toploop - - def loopvalues(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopvalues', []) - data_value = self.data_value(_context) - dataloop=[data_value] - while self._peek('data_value_1', 'start_sc_line', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['data_value_1', 'start_sc_line']: - data_value = self.data_value(_context) - dataloop.append(monitor('loopval',data_value)) - if self._peek() not in ['data_value_1', 'start_sc_line', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'start_sc_line', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - return dataloop - - def save_frame(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'save_frame', []) - save_heading = self._scan('save_heading') - savehead = save_heading[5:];savebc = StarFile();newname=savebc.NewBlock(savehead,StarBlock(overwrite=False));act_block=savebc[newname] - while self._peek('save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(savebc[savehead], _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - savebc.merge_fast(save_frame,parent=act_block) - if self._peek() not in ['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading'])) - save_end = self._scan('save_end') - return monitor('save_frame',savebc) - - -def parse(rule, text): - P = StarParser(StarParserScanner(text)) - return yappsrt.wrap_error_reporter(P, rule) - -# End -- grammar generated by Yapps - - diff --git a/GSASII/CifFile/YappsStarParser_2_0.py b/GSASII/CifFile/YappsStarParser_2_0.py deleted file mode 100644 index 60582c06b..000000000 --- a/GSASII/CifFile/YappsStarParser_2_0.py +++ /dev/null @@ -1,362 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -from .StarFile import StarBlock,StarFile,StarList,StarDict -from io import StringIO -# An alternative specification for the Cif Parser, based on Yapps2 -# by Amit Patel (http://theory.stanford.edu/~amitp/Yapps) -# -# helper code: we define our match tokens -lastval = '' -def monitor(location,value): - global lastval - #print 'At %s: %s' % (location,repr(value)) - lastval = repr(value) - return value - -# Strip extras gets rid of leading and trailing whitespace, and -# semicolons. -def stripextras(value): - from .StarFile import remove_line_folding, remove_line_prefix - # we get rid of semicolons and leading/trailing terminators etc. - import re - jj = re.compile("[\n\r\f \t\v]*") - semis = re.compile("[\n\r\f \t\v]*[\n\r\f]\n*;") - cut = semis.match(value) - if cut: #we have a semicolon-delimited string - nv = value[cut.end():len(value)-2] - try: - if nv[-1]=='\r': nv = nv[:-1] - except IndexError: #empty data value - pass - # apply protocols - nv = remove_line_prefix(nv) - nv = remove_line_folding(nv) - return nv - else: - cut = jj.match(value) - if cut: - return stripstring(value[cut.end():]) - return value - -# helper function to get rid of inverted commas etc. - -def stripstring(value): - if value: - if value[0]== '\'' and value[-1]=='\'': - return value[1:-1] - if value[0]=='"' and value[-1]=='"': - return value[1:-1] - return value - -# helper function to get rid of triple quotes -def striptriple(value): - if value: - if value[:3] == '"""' and value[-3:] == '"""': - return value[3:-3] - if value[:3] == "'''" and value[-3:] == "'''": - return value[3:-3] - return value - -# helper function to populate a StarBlock given a list of names -# and values . -# -# Note that there may be an empty list at the very end of our itemlists, -# so we remove that if necessary. -# - -def makeloop(target_block,loopdata): - loop_seq,itemlists = loopdata - if itemlists[-1] == []: itemlists.pop(-1) - # print 'Making loop with %s' % repr(itemlists) - step_size = len(loop_seq) - for col_no in range(step_size): - target_block.AddItem(loop_seq[col_no], itemlists[col_no::step_size],precheck=True) - # print 'Makeloop constructed %s' % repr(loopstructure) - # now construct the loop - try: - target_block.CreateLoop(loop_seq) #will raise ValueError on problem - except ValueError: - error_string = 'Incorrect number of loop values for loop containing %s' % repr(loop_seq) - print(error_string, file=sys.stderr) - raise ValueError(error_string) - -# return an object with the appropriate amount of nesting -def make_empty(nestlevel): - gd = [] - for i in range(1,nestlevel): - gd = [gd] - return gd - -# this function updates a dictionary first checking for name collisions, -# which imply that the CIF is invalid. We need case insensitivity for -# names. - -# Unfortunately we cannot check loop item contents against non-loop contents -# in a non-messy way during parsing, as we may not have easy access to previous -# key value pairs in the context of our call (unlike our built-in access to all -# previous loops). -# For this reason, we don't waste time checking looped items against non-looped -# names during parsing of a data block. This would only match a subset of the -# final items. We do check against ordinary items, however. -# -# Note the following situations: -# (1) new_dict is empty -> we have just added a loop; do no checking -# (2) new_dict is not empty -> we have some new key-value pairs -# -def cif_update(old_dict,new_dict,loops): - old_keys = map(lambda a:a.lower(),old_dict.keys()) - if new_dict != {}: # otherwise we have a new loop - #print 'Comparing %s to %s' % (repr(old_keys),repr(new_dict.keys())) - for new_key in new_dict.keys(): - if new_key.lower() in old_keys: - raise CifError("Duplicate dataname or blockname %s in input file" % new_key) - old_dict[new_key] = new_dict[new_key] -# -# this takes two lines, so we couldn't fit it into a one line execution statement... -def order_update(order_array,new_name): - order_array.append(new_name) - return new_name - -# and finally...turn a sequence into a python dict (thanks to Stackoverflow) -def pairwise(iterable): - it = iter(iterable) - while 1: - yield next(it), next(it) - - -# Begin -- grammar generated by Yapps -import sys, re -from . import yapps3_compiled_rt as yappsrt - -class StarParserScanner(yappsrt.Scanner): - def __init__(self, *args,**kwargs): - patterns = [ - ('":"', ':'), - ('([ \t\n\r](?!;))|[ \t]', '([ \t\n\r](?!;))|[ \t]'), - ('(#.*[\n\r](?!;))|(#.*)', '(#.*[\n\r](?!;))|(#.*)'), - ('LBLOCK', '(L|l)(O|o)(O|o)(P|p)_'), - ('GLOBAL', '(G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_'), - ('STOP', '(S|s)(T|t)(O|o)(P|p)_'), - ('save_heading', u'(S|s)(A|a)(V|v)(E|e)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_\xa0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd-]+'), - ('save_end', '(S|s)(A|a)(V|v)(E|e)_'), - ('data_name', u'_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_\xa0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd-]+'), - ('data_heading', u'(D|d)(A|a)(T|t)(A|a)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_\xa0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd-]+'), - ('start_sc_line', '(\n|\r\n);([^\n\r])*(\r\n|\r|\n)+'), - ('sc_line_of_text', '[^;\r\n]([^\r\n])*(\r\n|\r|\n)+'), - ('end_sc_line', ';'), - ('c_c_b', '\\}'), - ('o_c_b', '\\{'), - ('c_s_b', '\\]'), - ('o_s_b', '\\['), - ('dat_val_internal_sq', '\\[([^\\s\\[\\]]*)\\]'), - ('triple_quote_data_value', '(?s)\'\'\'.*?\'\'\'|""".*?"""'), - ('single_quote_data_value', '\'([^\n\r\x0c\'])*\'+|"([^\n\r"])*"+'), - ('data_value_1', '((?!(((S|s)(A|a)(V|v)(E|e)_[^\\s]*)|((G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_[^\\s]*)|((S|s)(T|t)(O|o)(P|p)_[^\\s]*)|((D|d)(A|a)(T|t)(A|a)_[^\\s]*)))[^\\s"#$\'_\\{\\}\\[\\]][^\\s\\{\\}\\[\\]]*)'), - ('END', '$'), - ] - yappsrt.Scanner.__init__(self,patterns,['([ \t\n\r](?!;))|[ \t]', '(#.*[\n\r](?!;))|(#.*)'],*args,**kwargs) - -class StarParser(yappsrt.Parser): - Context = yappsrt.Context - def input(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'input', [prepared]) - _token = self._peek('END', 'data_heading') - if _token == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks = prepared; allblocks.merge_fast(dblock) - while self._peek('END', 'data_heading') == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks.merge_fast(dblock) - if self._peek() not in ['END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['END', 'data_heading'])) - END = self._scan('END') - else: # == 'END' - END = self._scan('END') - allblocks = prepared - return allblocks - - def dblock(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dblock', [prepared]) - data_heading = self._scan('data_heading') - heading = data_heading[5:];thisbc=StarFile(characterset='unicode',standard=prepared.standard);act_heading = thisbc.NewBlock(heading,StarBlock(overwrite=False));stored_block = thisbc[act_heading] - while self._peek('save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(stored_block, _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - thisbc.merge_fast(save_frame,parent=stored_block) - if self._peek() not in ['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading'])) - stored_block.setmaxnamelength(stored_block.maxnamelength);return (monitor('dblock',thisbc)) - - def dataseq(self, starblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dataseq', [starblock]) - data = self.data(starblock, _context) - while self._peek('LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['LBLOCK', 'data_name']: - data = self.data(starblock, _context) - if self._peek() not in ['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - - def data(self, currentblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data', [currentblock]) - _token = self._peek('LBLOCK', 'data_name') - if _token == 'LBLOCK': - top_loop = self.top_loop(_context) - makeloop(currentblock,top_loop) - else: # == 'data_name' - datakvpair = self.datakvpair(_context) - currentblock.AddItem(datakvpair[0],datakvpair[1],precheck=False) - - def datakvpair(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'datakvpair', []) - data_name = self._scan('data_name') - data_value = self.data_value(_context) - return [data_name,data_value] - - def data_value(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data_value', []) - _token = self._peek('data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') - if _token == 'data_value_1': - data_value_1 = self._scan('data_value_1') - thisval = data_value_1 - elif _token not in ['start_sc_line', 'o_s_b', 'o_c_b']: - delimited_data_value = self.delimited_data_value(_context) - thisval = delimited_data_value - elif _token == 'start_sc_line': - sc_lines_of_text = self.sc_lines_of_text(_context) - thisval = stripextras(sc_lines_of_text) - else: # in ['o_s_b', 'o_c_b'] - bracket_expression = self.bracket_expression(_context) - thisval = bracket_expression - return monitor('data_value',thisval) - - def delimited_data_value(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'delimited_data_value', []) - _token = self._peek('triple_quote_data_value', 'single_quote_data_value') - if _token == 'triple_quote_data_value': - triple_quote_data_value = self._scan('triple_quote_data_value') - thisval = striptriple(triple_quote_data_value) - else: # == 'single_quote_data_value' - single_quote_data_value = self._scan('single_quote_data_value') - thisval = stripstring(single_quote_data_value) - return thisval - - def sc_lines_of_text(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'sc_lines_of_text', []) - start_sc_line = self._scan('start_sc_line') - lines = StringIO();lines.write(start_sc_line) - while self._peek('end_sc_line', 'sc_line_of_text') == 'sc_line_of_text': - sc_line_of_text = self._scan('sc_line_of_text') - lines.write(sc_line_of_text) - if self._peek() not in ['end_sc_line', 'sc_line_of_text']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['sc_line_of_text', 'end_sc_line'])) - end_sc_line = self._scan('end_sc_line') - lines.write(end_sc_line);return monitor('sc_line_of_text',lines.getvalue()) - - def bracket_expression(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'bracket_expression', []) - _token = self._peek('o_s_b', 'o_c_b') - if _token == 'o_s_b': - square_bracket_expr = self.square_bracket_expr(_context) - return square_bracket_expr - else: # == 'o_c_b' - curly_bracket_expr = self.curly_bracket_expr(_context) - return curly_bracket_expr - - def top_loop(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'top_loop', []) - LBLOCK = self._scan('LBLOCK') - loopfield = self.loopfield(_context) - loopvalues = self.loopvalues(_context) - return loopfield,loopvalues - - def loopfield(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopfield', []) - loop_seq=[] - while self._peek('data_name', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') == 'data_name': - data_name = self._scan('data_name') - loop_seq.append(data_name) - if self._peek() not in ['data_name', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_name', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b'])) - return loop_seq - - def loopvalues(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopvalues', []) - data_value = self.data_value(_context) - dataloop=[data_value] - while self._peek('data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - data_value = self.data_value(_context) - dataloop.append(monitor('loopval',data_value)) - if self._peek() not in ['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - return dataloop - - def save_frame(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'save_frame', []) - save_heading = self._scan('save_heading') - savehead = save_heading[5:];savebc = StarFile();newname = savebc.NewBlock(savehead,StarBlock(overwrite=False));stored_block = savebc[newname] - while self._peek('save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(savebc[savehead], _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - savebc.merge_fast(save_frame,parent=stored_block) - if self._peek() not in ['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading'])) - save_end = self._scan('save_end') - return monitor('save_frame',savebc) - - def square_bracket_expr(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'square_bracket_expr', []) - o_s_b = self._scan('o_s_b') - this_list = [] - while self._peek('c_s_b', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') != 'c_s_b': - data_value = self.data_value(_context) - this_list.append(data_value) - while self._peek('data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'c_s_b', 'o_s_b', 'o_c_b') != 'c_s_b': - data_value = self.data_value(_context) - this_list.append(data_value) - if self._peek() not in ['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'c_s_b', 'o_s_b', 'o_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'c_s_b'])) - if self._peek() not in ['c_s_b', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'c_s_b', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b'])) - c_s_b = self._scan('c_s_b') - return StarList(this_list) - - def curly_bracket_expr(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'curly_bracket_expr', []) - o_c_b = self._scan('o_c_b') - table_as_list = [] - while self._peek('c_c_b', 'triple_quote_data_value', 'single_quote_data_value') != 'c_c_b': - delimited_data_value = self.delimited_data_value(_context) - table_as_list = [delimited_data_value] - self._scan('":"') - data_value = self.data_value(_context) - table_as_list.append(data_value) - while self._peek('triple_quote_data_value', 'single_quote_data_value', 'c_c_b') != 'c_c_b': - delimited_data_value = self.delimited_data_value(_context) - table_as_list.append(delimited_data_value) - self._scan('":"') - data_value = self.data_value(_context) - table_as_list.append(data_value) - if self._peek() not in ['triple_quote_data_value', 'single_quote_data_value', 'c_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['triple_quote_data_value', 'single_quote_data_value', 'c_c_b'])) - if self._peek() not in ['c_c_b', 'triple_quote_data_value', 'single_quote_data_value']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['triple_quote_data_value', 'single_quote_data_value', 'c_c_b'])) - c_c_b = self._scan('c_c_b') - return StarDict(pairwise(table_as_list)) - - -def parse(rule, text): - P = StarParser(StarParserScanner(text)) - return yappsrt.wrap_error_reporter(P, rule) - -# End -- grammar generated by Yapps - - diff --git a/GSASII/CifFile/YappsStarParser_STAR2.py b/GSASII/CifFile/YappsStarParser_STAR2.py deleted file mode 100644 index e04d026e7..000000000 --- a/GSASII/CifFile/YappsStarParser_STAR2.py +++ /dev/null @@ -1,365 +0,0 @@ -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -from .StarFile import StarBlock,StarFile,StarList,StarDict -from io import StringIO -# An alternative specification for the Cif Parser, based on Yapps2 -# by Amit Patel (http://theory.stanford.edu/~amitp/Yapps) -# -# helper code: we define our match tokens -lastval = '' -def monitor(location,value): - global lastval - #print 'At %s: %s' % (location,repr(value)) - lastval = repr(value) - return value - -# Strip extras gets rid of leading and trailing whitespace, and -# semicolons. -def stripextras(value): - from .StarFile import remove_line_folding, remove_line_prefix - # we get rid of semicolons and leading/trailing terminators etc. - import re - jj = re.compile("[\n\r\f \t\v]*") - semis = re.compile("[\n\r\f \t\v]*[\n\r\f]\n*;") - cut = semis.match(value) - if cut: #we have a semicolon-delimited string - nv = value[cut.end():len(value)-2] - try: - if nv[-1]=='\r': nv = nv[:-1] - except IndexError: #empty data value - pass - # apply protocols - nv = remove_line_prefix(nv) - nv = remove_line_folding(nv) - return nv - else: - cut = jj.match(value) - if cut: - return stripstring(value[cut.end():]) - return value - -# helper function to get rid of inverted commas etc. - -def stripstring(value): - if value: - if value[0]== '\'' and value[-1]=='\'': - return value[1:-1] - if value[0]=='"' and value[-1]=='"': - return value[1:-1] - return value - -# helper function to get rid of triple quotes -def striptriple(value): - if value: - if value[:3] == '"""' and value[-3:] == '"""': - return value[3:-3] - if value[:3] == "'''" and value[-3:] == "'''": - return value[3:-3] - return value - -# helper function to populate a StarBlock given a list of names -# and values . -# -# Note that there may be an empty list at the very end of our itemlists, -# so we remove that if necessary. -# - -def makeloop(target_block,loopdata): - loop_seq,itemlists = loopdata - if itemlists[-1] == []: itemlists.pop(-1) - # print 'Making loop with %s' % repr(itemlists) - step_size = len(loop_seq) - for col_no in range(step_size): - target_block.AddItem(loop_seq[col_no], itemlists[col_no::step_size],precheck=True) - # print 'Makeloop constructed %s' % repr(loopstructure) - # now construct the loop - try: - target_block.CreateLoop(loop_seq) #will raise ValueError on problem - except ValueError: - error_string = 'Incorrect number of loop values for loop containing %s' % repr(loop_seq) - print(error_string, file=sys.stderr) - raise ValueError(error_string) - -# return an object with the appropriate amount of nesting -def make_empty(nestlevel): - gd = [] - for i in range(1,nestlevel): - gd = [gd] - return gd - -# this function updates a dictionary first checking for name collisions, -# which imply that the CIF is invalid. We need case insensitivity for -# names. - -# Unfortunately we cannot check loop item contents against non-loop contents -# in a non-messy way during parsing, as we may not have easy access to previous -# key value pairs in the context of our call (unlike our built-in access to all -# previous loops). -# For this reason, we don't waste time checking looped items against non-looped -# names during parsing of a data block. This would only match a subset of the -# final items. We do check against ordinary items, however. -# -# Note the following situations: -# (1) new_dict is empty -> we have just added a loop; do no checking -# (2) new_dict is not empty -> we have some new key-value pairs -# -def cif_update(old_dict,new_dict,loops): - old_keys = map(lambda a:a.lower(),old_dict.keys()) - if new_dict != {}: # otherwise we have a new loop - #print 'Comparing %s to %s' % (repr(old_keys),repr(new_dict.keys())) - for new_key in new_dict.keys(): - if new_key.lower() in old_keys: - raise CifError("Duplicate dataname or blockname %s in input file" % new_key) - old_dict[new_key] = new_dict[new_key] -# -# this takes two lines, so we couldn't fit it into a one line execution statement... -def order_update(order_array,new_name): - order_array.append(new_name) - return new_name - -# and finally...turn a sequence into a python dict (thanks to Stackoverflow) -def pairwise(iterable): - it = iter(iterable) - while 1: - yield next(it), next(it) - - -# Begin -- grammar generated by Yapps -import sys, re -from . import yapps3_compiled_rt as yappsrt - -class StarParserScanner(yappsrt.Scanner): - def __init__(self, *args,**kwargs): - patterns = [ - ('":"', ':'), - ('","', ','), - ('([ \t\n\r](?!;))|[ \t]', '([ \t\n\r](?!;))|[ \t]'), - ('(#.*[\n\r](?!;))|(#.*)', '(#.*[\n\r](?!;))|(#.*)'), - ('LBLOCK', '(L|l)(O|o)(O|o)(P|p)_'), - ('GLOBAL', '(G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_'), - ('STOP', '(S|s)(T|t)(O|o)(P|p)_'), - ('save_heading', u'(S|s)(A|a)(V|v)(E|e)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_\xa0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd-]+'), - ('save_end', '(S|s)(A|a)(V|v)(E|e)_'), - ('data_name', u'_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_\xa0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd-]+'), - ('data_heading', u'(D|d)(A|a)(T|t)(A|a)_[][!%&\\(\\)*+,./:<=>?@0-9A-Za-z\\\\^`{}\\|~"#$\';_\xa0-\ud7ff\ue000-\ufdcf\ufdf0-\ufffd\U00010000-\U0001fffd\U00020000-\U0002fffd\U00030000-\U0003fffd\U00040000-\U0004fffd\U00050000-\U0005fffd\U00060000-\U0006fffd\U00070000-\U0007fffd\U00080000-\U0008fffd\U00090000-\U0009fffd\U000a0000-\U000afffd\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e0000-\U000efffd\U000f0000-\U000ffffd\U00100000-\U0010fffd-]+'), - ('start_sc_line', '(\n|\r\n);([^\n\r])*(\r\n|\r|\n)+'), - ('sc_line_of_text', '[^;\r\n]([^\r\n])*(\r\n|\r|\n)+'), - ('end_sc_line', ';'), - ('c_c_b', '\\}'), - ('o_c_b', '\\{'), - ('c_s_b', '\\]'), - ('o_s_b', '\\['), - ('dat_val_internal_sq', '\\[([^\\s\\[\\]]*)\\]'), - ('triple_quote_data_value', '(?s)\'\'\'.*?\'\'\'|""".*?"""'), - ('single_quote_data_value', '\'([^\n\r\x0c\'])*\'+|"([^\n\r"])*"+'), - ('END', '$'), - ('data_value_1', '((?!(((S|s)(A|a)(V|v)(E|e)_[^\\s]*)|((G|g)(L|l)(O|o)(B|b)(A|a)(L|l)_[^\\s]*)|((S|s)(T|t)(O|o)(P|p)_[^\\s]*)|((D|d)(A|a)(T|t)(A|a)_[^\\s]*)))[^\\s"#$\',_\\{\\}\\[\\]][^\\s,\\{\\}\\[\\]]*)'), - ] - yappsrt.Scanner.__init__(self,patterns,['([ \t\n\r](?!;))|[ \t]', '(#.*[\n\r](?!;))|(#.*)'],*args,**kwargs) - -class StarParser(yappsrt.Parser): - Context = yappsrt.Context - def input(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'input', [prepared]) - _token = self._peek('END', 'data_heading') - if _token == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks = prepared; allblocks.merge_fast(dblock) - while self._peek('END', 'data_heading') == 'data_heading': - dblock = self.dblock(prepared, _context) - allblocks.merge_fast(dblock) - if self._peek() not in ['END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['END', 'data_heading'])) - END = self._scan('END') - else: # == 'END' - END = self._scan('END') - allblocks = prepared - return allblocks - - def dblock(self, prepared, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dblock', [prepared]) - data_heading = self._scan('data_heading') - heading = data_heading[5:];thisbc=StarFile(characterset='unicode',standard=prepared.standard);act_heading = thisbc.NewBlock(heading,StarBlock(overwrite=False));stored_block = thisbc[act_heading] - while self._peek('save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(stored_block, _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - thisbc.merge_fast(save_frame,parent=stored_block) - if self._peek() not in ['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_heading', 'LBLOCK', 'data_name', 'save_end', 'END', 'data_heading'])) - stored_block.setmaxnamelength(stored_block.maxnamelength);return (monitor('dblock',thisbc)) - - def dataseq(self, starblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'dataseq', [starblock]) - data = self.data(starblock, _context) - while self._peek('LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['LBLOCK', 'data_name']: - data = self.data(starblock, _context) - if self._peek() not in ['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - - def data(self, currentblock, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data', [currentblock]) - _token = self._peek('LBLOCK', 'data_name') - if _token == 'LBLOCK': - top_loop = self.top_loop(_context) - makeloop(currentblock,top_loop) - else: # == 'data_name' - datakvpair = self.datakvpair(_context) - currentblock.AddItem(datakvpair[0],datakvpair[1],precheck=False) - - def datakvpair(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'datakvpair', []) - data_name = self._scan('data_name') - data_value = self.data_value(_context) - return [data_name,data_value] - - def data_value(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'data_value', []) - _token = self._peek('data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') - if _token == 'data_value_1': - data_value_1 = self._scan('data_value_1') - thisval = data_value_1 - elif _token not in ['start_sc_line', 'o_s_b', 'o_c_b']: - delimited_data_value = self.delimited_data_value(_context) - thisval = delimited_data_value - elif _token == 'start_sc_line': - sc_lines_of_text = self.sc_lines_of_text(_context) - thisval = stripextras(sc_lines_of_text) - else: # in ['o_s_b', 'o_c_b'] - bracket_expression = self.bracket_expression(_context) - thisval = bracket_expression - return monitor('data_value',thisval) - - def delimited_data_value(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'delimited_data_value', []) - _token = self._peek('triple_quote_data_value', 'single_quote_data_value') - if _token == 'triple_quote_data_value': - triple_quote_data_value = self._scan('triple_quote_data_value') - thisval = striptriple(triple_quote_data_value) - else: # == 'single_quote_data_value' - single_quote_data_value = self._scan('single_quote_data_value') - thisval = stripstring(single_quote_data_value) - return thisval - - def sc_lines_of_text(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'sc_lines_of_text', []) - start_sc_line = self._scan('start_sc_line') - lines = StringIO();lines.write(start_sc_line) - while self._peek('end_sc_line', 'sc_line_of_text') == 'sc_line_of_text': - sc_line_of_text = self._scan('sc_line_of_text') - lines.write(sc_line_of_text) - if self._peek() not in ['end_sc_line', 'sc_line_of_text']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['sc_line_of_text', 'end_sc_line'])) - end_sc_line = self._scan('end_sc_line') - lines.write(end_sc_line);return monitor('sc_line_of_text',lines.getvalue()) - - def bracket_expression(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'bracket_expression', []) - _token = self._peek('o_s_b', 'o_c_b') - if _token == 'o_s_b': - square_bracket_expr = self.square_bracket_expr(_context) - return square_bracket_expr - else: # == 'o_c_b' - curly_bracket_expr = self.curly_bracket_expr(_context) - return curly_bracket_expr - - def top_loop(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'top_loop', []) - LBLOCK = self._scan('LBLOCK') - loopfield = self.loopfield(_context) - loopvalues = self.loopvalues(_context) - return loopfield,loopvalues - - def loopfield(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopfield', []) - loop_seq=[] - while self._peek('data_name', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') == 'data_name': - data_name = self._scan('data_name') - loop_seq.append(data_name) - if self._peek() not in ['data_name', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_name', 'data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b'])) - return loop_seq - - def loopvalues(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'loopvalues', []) - data_value = self.data_value(_context) - dataloop=[data_value] - while self._peek('data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading') in ['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - data_value = self.data_value(_context) - dataloop.append(monitor('loopval',data_value)) - if self._peek() not in ['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b', 'LBLOCK', 'data_name', 'save_heading', 'save_end', 'END', 'data_heading'])) - return dataloop - - def save_frame(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'save_frame', []) - save_heading = self._scan('save_heading') - savehead = save_heading[5:];savebc = StarFile();newname = savebc.NewBlock(savehead,StarBlock(overwrite=False));stored_block = savebc[newname] - while self._peek('save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading') in ['save_heading', 'LBLOCK', 'data_name']: - _token = self._peek('save_heading', 'LBLOCK', 'data_name') - if _token != 'save_heading': - dataseq = self.dataseq(savebc[savehead], _context) - else: # == 'save_heading' - save_frame = self.save_frame(_context) - savebc.merge_fast(save_frame,parent=stored_block) - if self._peek() not in ['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['save_end', 'save_heading', 'LBLOCK', 'data_name', 'END', 'data_heading'])) - save_end = self._scan('save_end') - return monitor('save_frame',savebc) - - def square_bracket_expr(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'square_bracket_expr', []) - o_s_b = self._scan('o_s_b') - this_list = [] - while self._peek('c_s_b', 'data_value_1', '","', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') not in ['c_s_b', '","']: - data_value = self.data_value(_context) - this_list.append(data_value) - while self._peek('","', 'data_value_1', 'c_s_b', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b') == '","': - self._scan('","') - data_value = self.data_value(_context) - this_list.append(data_value) - if self._peek() not in ['","', 'data_value_1', 'c_s_b', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['","', 'data_value_1', 'c_s_b', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b'])) - if self._peek() not in ['c_s_b', 'data_value_1', '","', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', 'o_s_b', 'o_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['data_value_1', 'c_s_b', 'triple_quote_data_value', 'single_quote_data_value', 'start_sc_line', '","', 'o_s_b', 'o_c_b'])) - c_s_b = self._scan('c_s_b') - return StarList(this_list) - - def curly_bracket_expr(self, _parent=None): - _context = self.Context(_parent, self._scanner, self._pos, 'curly_bracket_expr', []) - o_c_b = self._scan('o_c_b') - table_as_list = [] - while self._peek('c_c_b', 'triple_quote_data_value', 'single_quote_data_value', '","') in ['triple_quote_data_value', 'single_quote_data_value']: - delimited_data_value = self.delimited_data_value(_context) - table_as_list = [delimited_data_value] - self._scan('":"') - data_value = self.data_value(_context) - table_as_list.append(data_value) - while self._peek('","', 'triple_quote_data_value', 'single_quote_data_value', 'c_c_b') == '","': - self._scan('","') - delimited_data_value = self.delimited_data_value(_context) - table_as_list.append(delimited_data_value) - self._scan('":"') - data_value = self.data_value(_context) - table_as_list.append(data_value) - if self._peek() not in ['","', 'triple_quote_data_value', 'single_quote_data_value', 'c_c_b']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['","', 'triple_quote_data_value', 'single_quote_data_value', 'c_c_b'])) - if self._peek() not in ['c_c_b', 'triple_quote_data_value', 'single_quote_data_value', '","']: - raise yappsrt.SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['triple_quote_data_value', 'single_quote_data_value', 'c_c_b', '","'])) - c_c_b = self._scan('c_c_b') - return StarDict(pairwise(table_as_list)) - - -def parse(rule, text): - P = StarParser(StarParserScanner(text)) - return yappsrt.wrap_error_reporter(P, rule) - -# End -- grammar generated by Yapps - - diff --git a/GSASII/CifFile/__init__.py b/GSASII/CifFile/__init__.py deleted file mode 100755 index 5186327a6..000000000 --- a/GSASII/CifFile/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import absolute_import -from .CifFile import CifFile,CifDic,CifError,CifBlock,ReadCif,ValidCifFile,ValidCifError,Validate -from .CifFile import get_number_with_esd,convert_type,validate_report -from .StarFile import StarError,ReadStar,StarList,apply_line_folding,apply_line_prefix -from .StarFile import remove_line_prefix,remove_line_folding -from .StarFile import check_stringiness diff --git a/GSASII/CifFile/parsetab.py b/GSASII/CifFile/parsetab.py deleted file mode 100644 index 870bf899a..000000000 --- a/GSASII/CifFile/parsetab.py +++ /dev/null @@ -1,185 +0,0 @@ - -# parsetab.py -# This file is automatically generated. Do not edit. -_tabversion = '3.2' - -_lr_method = 'LALR' - -_lr_signature = '\xcc\x83\xf4':([7,11,12,17,18,23,28,30,34,35,36,40,41,42,48,49,50,51,52,54,56,60,63,69,71,73,75,76,88,99,117,118,122,124,125,127,144,155,156,159,163,164,165,173,177,178,179,184,190,207,210,211,218,230,234,247,],[-61,-76,-56,-75,-58,-78,-65,-63,-74,-68,-66,-50,113,-79,-70,-46,-53,-69,-62,-59,-77,-55,-67,-71,-43,-60,-57,-64,-60,-52,-97,-95,-81,-72,-73,-51,-82,-44,-45,-84,-90,-54,-96,-80,-47,-48,-49,-112,-65,-100,-99,-98,-113,-83,-89,113,]),'}':([1,4,5,7,11,12,15,17,18,19,22,23,24,25,28,30,33,34,35,36,37,40,41,42,44,46,48,49,50,51,52,54,56,60,61,63,65,68,69,70,71,72,75,76,79,80,81,82,85,87,88,99,101,115,116,117,118,122,124,125,127,138,140,141,144,145,147,148,154,155,156,159,162,163,164,165,173,175,176,177,178,179,184,191,192,205,207,210,211,218,221,222,225,230,233,234,245,250,254,259,260,262,278,279,],[-154,-153,-152,-61,-76,-56,-127,-75,-58,-126,-124,-78,-23,-129,-65,-63,-125,-74,-68,-66,-5,-50,-32,-79,-30,-156,-70,-46,-53,-69,-62,-59,-77,-55,-131,-67,-27,-130,-71,-24,-43,-128,-57,-64,-155,-143,-156,-135,-139,-3,-60,-52,-146,-31,163,-97,-95,-81,-72,-73,-51,163,-147,-4,-82,-132,-26,-25,-33,-44,-45,-84,-156,-90,-54,-96,-80,-28,-29,-47,-48,-49,-112,222,-8,234,-100,-99,-98,-113,-9,-156,-137,-83,-94,-89,-136,-149,-91,-133,-134,-138,-92,-93,]),'OR':([7,11,12,17,18,23,24,28,30,34,35,36,40,41,42,44,48,49,50,51,52,54,56,60,63,65,69,70,71,73,75,76,88,99,115,117,118,122,124,125,127,144,147,148,154,155,156,159,163,164,165,173,175,176,177,178,179,184,190,207,210,211,218,230,234,],[-61,-76,-56,-75,-58,-78,93,-65,-63,-74,-68,-66,-50,-32,-79,-30,-70,-46,-53,-69,-62,-59,-77,-55,-67,-27,-71,-24,-43,-60,-57,-64,-60,-52,-31,-97,-95,-81,-72,-73,-51,-82,-26,-25,-33,-44,-45,-84,-90,-54,-96,-80,-28,-29,-47,-48,-49,-112,-65,-100,-99,-98,-113,-83,-89,]),'LOOP':([0,1,2,3,4,5,7,8,11,12,13,14,15,17,18,19,22,23,24,25,28,30,33,34,35,36,37,39,40,41,42,44,47,48,49,50,51,52,54,55,56,60,61,63,65,68,69,70,71,72,75,76,78,79,80,81,82,84,85,87,88,90,99,101,115,117,118,122,124,125,127,138,140,141,144,145,147,148,154,155,156,159,163,164,165,173,175,176,177,178,179,184,191,192,193,195,196,207,210,211,218,219,220,221,222,223,225,228,230,234,244,245,246,247,248,250,256,259,260,262,269,273,],[-156,-154,26,26,-153,-152,-61,26,-76,-56,-156,26,-127,-75,-58,-126,-124,-78,-23,-129,-65,-63,-125,-74,-68,-66,-5,26,-50,-32,-79,-30,-21,-70,-46,-53,-69,-62,-59,-1,-77,-55,-131,-67,-27,-130,-71,-24,-43,-128,-57,-64,-2,-155,-143,-156,-135,26,-139,-3,-60,26,-52,-146,-31,-97,-95,-81,-72,-73,-51,26,-147,-4,-82,-132,-26,-25,-33,-44,-45,-84,-90,-54,-96,-80,-28,-29,-47,-48,-49,-112,26,-8,-148,-140,26,-100,-99,-98,-113,-156,-22,-9,-156,-156,-137,26,-83,-89,26,-136,26,-141,26,-149,-144,-133,-134,-138,-142,-145,]),} - -_lr_action = { } -for _k, _v in _lr_action_items.items(): - for _x,_y in zip(_v[0],_v[1]): - if not _x in _lr_action: _lr_action[_x] = { } - _lr_action[_x][_k] = _y -del _lr_action_items - -_lr_goto_items = {'statements':([138,],[191,]),'comp_operator':([41,],[104,]),'small_stmt':([2,3,8,14,39,84,86,90,138,191,196,228,244,246,248,],[6,6,6,6,6,6,142,6,6,6,6,6,6,6,6,]),'fancy_drel_assignment_stmt':([2,3,8,14,39,84,86,90,138,191,196,228,244,246,248,],[27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,]),'primary':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,]),'stringliteral':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,116,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,266,267,268,271,276,277,],[28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,161,28,28,28,28,28,28,28,28,28,28,28,190,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,272,28,28,275,28,28,]),'item_tag':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,]),'not_test':([2,3,8,14,21,29,39,45,53,84,86,90,92,93,114,121,128,129,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[65,65,65,65,65,65,65,115,65,65,65,65,65,65,65,65,175,176,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,]),'listmaker':([114,],[158,]),'do_stmt_head':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[8,8,8,8,8,8,8,8,8,8,8,8,8,8,]),'func_arg':([133,143,217,],[180,180,243,]),'enclosure':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,]),'newlines':([0,13,16,43,46,81,86,136,158,162,203,219,222,223,231,255,265,],[5,5,87,5,5,5,141,5,5,5,5,5,5,5,5,5,5,]),'break_stmt':([2,3,8,14,39,84,86,90,138,191,196,228,244,246,248,],[66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,]),'dotlist':([133,],[181,]),'arglist':([153,],[199,]),'repeat_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[68,68,68,68,68,68,68,68,68,68,68,68,68,68,]),'u_expr':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[49,49,49,49,49,49,99,49,49,49,127,49,49,49,49,49,49,49,49,49,164,49,49,49,177,178,179,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,]),'if_else_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[33,33,33,33,33,33,33,33,33,33,33,33,33,33,]),'parenth_form':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,]),'literal':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,]),'attributeref':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,]),'call':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,]),'argument_list':([133,143,],[183,183,]),'statement':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[55,78,82,82,82,82,82,192,221,82,82,82,82,82,]),'string_conversion':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,]),'with_head':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[13,13,13,13,13,13,13,13,13,13,13,13,13,13,]),'input':([0,],[3,]),'loop_head':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[14,14,14,14,14,14,14,14,14,14,14,14,14,14,]),'do_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[15,15,15,15,15,15,15,15,15,15,15,15,15,15,]),'next_stmt':([2,3,8,14,39,84,86,90,138,191,196,228,244,246,248,],[57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,]),'empty':([0,13,43,46,81,136,158,162,203,219,222,223,231,255,265,],[4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,]),'listmaker2':([160,],[202,]),'short_slice':([121,206,],[167,167,]),'power':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,]),'a_expr':([2,3,8,14,21,29,39,45,53,84,86,90,92,93,104,114,121,128,129,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[41,41,41,41,41,41,41,41,41,41,41,41,41,41,154,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,]),'print_stmt':([2,3,8,14,39,84,86,90,138,191,196,228,244,246,248,],[58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,]),'and_test':([2,3,8,14,21,29,39,53,84,86,90,92,93,114,121,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[70,70,70,70,70,70,70,70,70,70,70,147,148,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,]),'maybe_nline':([0,13,43,46,81,136,158,162,203,219,222,223,231,255,265,],[2,84,114,116,138,188,201,205,232,244,245,246,252,266,271,]),'tablemaker2':([233,],[254,]),'slicing':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,]),'for_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[19,19,19,19,19,19,19,19,19,19,19,19,19,19,]),'m_expr':([2,3,8,14,21,29,39,45,53,84,86,90,92,93,104,105,107,114,121,128,129,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,155,156,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,]),'table_display':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,]),'restricted_comp_operator':([41,247,],[108,261,]),'atom':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,]),'funcdef':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[61,61,61,61,61,61,61,61,61,61,61,61,61,61,]),'expr_stmt':([2,3,8,14,39,84,86,90,138,191,196,228,244,246,248,],[20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,]),'slice_list':([121,],[166,]),'subscription':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,]),'comparison':([2,3,8,14,21,29,39,45,53,84,86,90,92,93,114,121,128,129,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,]),'attribute_tag':([50,],[118,]),'if_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[22,22,22,22,22,22,22,22,22,22,22,22,22,22,]),'id_list':([31,97,],[96,152,]),'proper_slice':([121,206,],[170,235,]),'list_display':([2,3,8,14,21,29,32,39,45,53,67,84,86,90,92,93,104,105,107,114,119,121,128,129,130,131,132,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,229,232,240,242,244,246,248,252,263,267,268,276,277,],[23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,251,23,23,23,23,23,23,23,270,23,23,23,23,]),'loop_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[72,72,72,72,72,72,72,72,72,72,72,72,72,72,]),'or_test':([2,3,8,14,21,29,39,53,84,86,90,114,121,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,]),'compound_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[37,37,37,37,37,37,37,37,37,37,37,37,37,37,]),'with_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[25,25,25,25,25,25,25,25,25,25,25,25,25,25,]),'tablemaker':([116,138,],[162,162,]),'long_slice':([121,206,],[169,169,]),'suite':([8,14,39,84,90,196,228,244,246,248,],[80,85,101,140,145,225,250,259,260,262,]),'simple_stmt':([2,3,8,14,39,84,90,138,191,196,228,244,246,248,],[16,16,16,16,16,16,16,16,16,16,16,16,16,16,]),'testlist_star_expr':([2,3,8,14,21,39,53,84,86,90,135,137,138,150,191,196,226,228,244,246,248,],[77,77,77,77,89,77,123,77,77,77,187,189,77,196,77,77,248,77,77,77,77,]),'slice_item':([121,206,],[171,236,]),'expression':([2,3,8,14,21,29,39,53,84,86,90,114,121,133,134,135,137,138,143,146,150,168,174,188,191,196,204,206,208,212,217,226,228,232,240,242,244,246,248,252,267,268,276,277,],[47,47,47,47,47,95,47,47,47,47,47,160,172,185,186,47,47,47,185,194,47,209,213,220,47,47,233,237,238,239,185,47,47,253,256,258,47,47,47,264,273,274,278,279,]),} - -_lr_goto = { } -for _k, _v in _lr_goto_items.items(): - for _x,_y in zip(_v[0],_v[1]): - if not _x in _lr_goto: _lr_goto[_x] = { } - _lr_goto[_x][_k] = _y -del _lr_goto_items -_lr_productions = [ - ("S' -> input","S'",1,None,None,None), - ('input -> maybe_nline statement','input',2,'p_input','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',19), - ('input -> input statement','input',2,'p_input','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',20), - ('statement -> simple_stmt newlines','statement',2,'p_statement','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',36), - ('statement -> simple_stmt ; newlines','statement',3,'p_statement','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',37), - ('statement -> compound_stmt','statement',1,'p_statement','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',38), - ('simple_stmt -> small_stmt','simple_stmt',1,'p_simple_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',44), - ('simple_stmt -> simple_stmt ; small_stmt','simple_stmt',3,'p_simple_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',45), - ('statements -> statement','statements',1,'p_statements','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',55), - ('statements -> statements statement','statements',2,'p_statements','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',56), - ('small_stmt -> expr_stmt','small_stmt',1,'p_small_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',61), - ('small_stmt -> print_stmt','small_stmt',1,'p_small_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',62), - ('small_stmt -> break_stmt','small_stmt',1,'p_small_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',63), - ('small_stmt -> next_stmt','small_stmt',1,'p_small_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',64), - ('break_stmt -> BREAK','break_stmt',1,'p_break_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',68), - ('next_stmt -> NEXT','next_stmt',1,'p_next_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',72), - ('print_stmt -> PRINT expression','print_stmt',2,'p_print_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',76), - ('expr_stmt -> testlist_star_expr','expr_stmt',1,'p_expr_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',84), - ('expr_stmt -> testlist_star_expr AUGOP testlist_star_expr','expr_stmt',3,'p_expr_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',85), - ('expr_stmt -> testlist_star_expr = testlist_star_expr','expr_stmt',3,'p_expr_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',86), - ('expr_stmt -> fancy_drel_assignment_stmt','expr_stmt',1,'p_expr_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',87), - ('testlist_star_expr -> expression','testlist_star_expr',1,'p_testlist_star_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',96), - ('testlist_star_expr -> testlist_star_expr , maybe_nline expression','testlist_star_expr',4,'p_testlist_star_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',97), - ('expression -> or_test','expression',1,'p_expression','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',107), - ('or_test -> and_test','or_test',1,'p_or_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',115), - ('or_test -> or_test OR and_test','or_test',3,'p_or_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',116), - ('or_test -> or_test BADOR and_test','or_test',3,'p_or_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',117), - ('and_test -> not_test','and_test',1,'p_and_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',122), - ('and_test -> and_test AND not_test','and_test',3,'p_and_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',123), - ('and_test -> and_test BADAND not_test','and_test',3,'p_and_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',124), - ('not_test -> comparison','not_test',1,'p_not_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',129), - ('not_test -> NOT not_test','not_test',2,'p_not_test','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',130), - ('comparison -> a_expr','comparison',1,'p_comparison','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',135), - ('comparison -> a_expr comp_operator a_expr','comparison',3,'p_comparison','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',136), - ('comp_operator -> restricted_comp_operator','comp_operator',1,'p_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',142), - ('comp_operator -> IN','comp_operator',1,'p_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',143), - ('comp_operator -> NOT IN','comp_operator',2,'p_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',144), - ('restricted_comp_operator -> <','restricted_comp_operator',1,'p_restricted_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',150), - ('restricted_comp_operator -> >','restricted_comp_operator',1,'p_restricted_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',151), - ('restricted_comp_operator -> GTE','restricted_comp_operator',1,'p_restricted_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',152), - ('restricted_comp_operator -> LTE','restricted_comp_operator',1,'p_restricted_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',153), - ('restricted_comp_operator -> NEQ','restricted_comp_operator',1,'p_restricted_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',154), - ('restricted_comp_operator -> ISEQUAL','restricted_comp_operator',1,'p_restricted_comp_operator','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',155), - ('a_expr -> m_expr','a_expr',1,'p_a_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',159), - ('a_expr -> a_expr + m_expr','a_expr',3,'p_a_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',160), - ('a_expr -> a_expr - m_expr','a_expr',3,'p_a_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',161), - ('m_expr -> u_expr','m_expr',1,'p_m_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',168), - ('m_expr -> m_expr * u_expr','m_expr',3,'p_m_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',169), - ('m_expr -> m_expr / u_expr','m_expr',3,'p_m_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',170), - ('m_expr -> m_expr ^ u_expr','m_expr',3,'p_m_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',171), - ('u_expr -> power','u_expr',1,'p_u_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',178), - ('u_expr -> - u_expr','u_expr',2,'p_u_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',179), - ('u_expr -> + u_expr','u_expr',2,'p_u_expr','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',180), - ('power -> primary','power',1,'p_power','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',187), - ('power -> primary POWER u_expr','power',3,'p_power','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',188), - ('primary -> atom','primary',1,'p_primary','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',196), - ('primary -> attributeref','primary',1,'p_primary','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',197), - ('primary -> subscription','primary',1,'p_primary','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',198), - ('primary -> slicing','primary',1,'p_primary','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',199), - ('primary -> call','primary',1,'p_primary','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',200), - ('atom -> ID','atom',1,'p_atom','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',205), - ('atom -> item_tag','atom',1,'p_atom','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',206), - ('atom -> literal','atom',1,'p_atom','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',207), - ('atom -> enclosure','atom',1,'p_atom','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',208), - ('item_tag -> ITEM_TAG','item_tag',1,'p_item_tag','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',213), - ('literal -> stringliteral','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',217), - ('literal -> INTEGER','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',218), - ('literal -> HEXINT','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',219), - ('literal -> OCTINT','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',220), - ('literal -> BININT','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',221), - ('literal -> REAL','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',222), - ('literal -> IMAGINARY','literal',1,'p_literal','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',223), - ('stringliteral -> STRPREFIX SHORTSTRING','stringliteral',2,'p_stringliteral','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',228), - ('stringliteral -> STRPREFIX LONGSTRING','stringliteral',2,'p_stringliteral','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',229), - ('stringliteral -> SHORTSTRING','stringliteral',1,'p_stringliteral','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',230), - ('stringliteral -> LONGSTRING','stringliteral',1,'p_stringliteral','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',231), - ('enclosure -> parenth_form','enclosure',1,'p_enclosure','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',236), - ('enclosure -> string_conversion','enclosure',1,'p_enclosure','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',237), - ('enclosure -> list_display','enclosure',1,'p_enclosure','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',238), - ('enclosure -> table_display','enclosure',1,'p_enclosure','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',239), - ('parenth_form -> OPEN_PAREN testlist_star_expr CLOSE_PAREN','parenth_form',3,'p_parenth_form','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',243), - ('parenth_form -> OPEN_PAREN CLOSE_PAREN','parenth_form',2,'p_parenth_form','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',244), - ('string_conversion -> ` testlist_star_expr `','string_conversion',3,'p_string_conversion','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',251), - ('list_display -> [ maybe_nline listmaker maybe_nline ]','list_display',5,'p_list_display','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',256), - ('list_display -> [ maybe_nline ]','list_display',3,'p_list_display','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',257), - ('listmaker -> expression listmaker2','listmaker',2,'p_listmaker','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',265), - ('listmaker2 -> , maybe_nline expression','listmaker2',3,'p_listmaker2','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',270), - ('listmaker2 -> listmaker2 , maybe_nline expression','listmaker2',4,'p_listmaker2','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',271), - ('listmaker2 -> ','listmaker2',0,'p_listmaker2','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',272), - ('table_display -> { maybe_nline tablemaker maybe_nline }','table_display',5,'p_table_display','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',282), - ('table_display -> { maybe_nline }','table_display',3,'p_table_display','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',283), - ('tablemaker -> stringliteral : expression tablemaker2','tablemaker',4,'p_tablemaker','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',290), - ('tablemaker2 -> , maybe_nline stringliteral : expression','tablemaker2',5,'p_tablemaker2','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',294), - ('tablemaker2 -> tablemaker2 , maybe_nline stringliteral : expression','tablemaker2',6,'p_tablemaker2','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',295), - ('tablemaker2 -> ','tablemaker2',0,'p_tablemaker2','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',296), - ('attributeref -> primary attribute_tag','attributeref',2,'p_attributeref','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',310), - ('attribute_tag -> . ID','attribute_tag',2,'p_attribute_tag','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',314), - ('attribute_tag -> REAL','attribute_tag',1,'p_attribute_tag','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',315), - ('subscription -> primary [ expression ]','subscription',4,'p_subscription','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',322), - ('slicing -> primary [ proper_slice ]','slicing',4,'p_slicing','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',326), - ('slicing -> primary [ slice_list ]','slicing',4,'p_slicing','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',327), - ('proper_slice -> short_slice','proper_slice',1,'p_proper_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',331), - ('proper_slice -> long_slice','proper_slice',1,'p_proper_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',332), - ('short_slice -> :','short_slice',1,'p_short_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',343), - ('short_slice -> expression : expression','short_slice',3,'p_short_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',344), - ('short_slice -> : expression','short_slice',2,'p_short_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',345), - ('short_slice -> expression :','short_slice',2,'p_short_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',346), - ('long_slice -> short_slice : expression','long_slice',3,'p_long_slice','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',355), - ('slice_list -> slice_item','slice_list',1,'p_slice_list','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',362), - ('slice_list -> slice_list , slice_item','slice_list',3,'p_slice_list','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',363), - ('slice_item -> expression','slice_item',1,'p_slice_item','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',370), - ('slice_item -> proper_slice','slice_item',1,'p_slice_item','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',371), - ('call -> ID OPEN_PAREN CLOSE_PAREN','call',3,'p_call','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',375), - ('call -> ID OPEN_PAREN argument_list CLOSE_PAREN','call',4,'p_call','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',376), - ('argument_list -> func_arg','argument_list',1,'p_argument_list','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',386), - ('argument_list -> argument_list , func_arg','argument_list',3,'p_argument_list','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',387), - ('func_arg -> expression','func_arg',1,'p_func_arg','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',394), - ('fancy_drel_assignment_stmt -> ID OPEN_PAREN dotlist CLOSE_PAREN','fancy_drel_assignment_stmt',4,'p_fancy_drel_assignment_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',398), - ('dotlist -> . ID = expression','dotlist',4,'p_dotlist','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',405), - ('dotlist -> dotlist , . ID = expression','dotlist',6,'p_dotlist','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',406), - ('exprlist -> a_expr','exprlist',1,'p_exprlist','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',413), - ('exprlist -> exprlist , a_expr','exprlist',3,'p_exprlist','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',414), - ('id_list -> ID','id_list',1,'p_id_list','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',421), - ('id_list -> id_list , ID','id_list',3,'p_id_list','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',422), - ('compound_stmt -> if_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',433), - ('compound_stmt -> if_else_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',434), - ('compound_stmt -> for_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',435), - ('compound_stmt -> do_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',436), - ('compound_stmt -> loop_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',437), - ('compound_stmt -> with_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',438), - ('compound_stmt -> repeat_stmt','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',439), - ('compound_stmt -> funcdef','compound_stmt',1,'p_compound_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',440), - ('if_else_stmt -> if_stmt ELSE suite','if_else_stmt',3,'p_if_else_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',447), - ('if_stmt -> IF OPEN_PAREN expression CLOSE_PAREN maybe_nline suite','if_stmt',6,'p_if_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',453), - ('if_stmt -> if_stmt ELSEIF OPEN_PAREN expression CLOSE_PAREN maybe_nline suite','if_stmt',7,'p_if_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',454), - ('suite -> statement','suite',1,'p_suite','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',473), - ('suite -> { maybe_nline statements } maybe_nline','suite',5,'p_suite','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',474), - ('for_stmt -> FOR id_list IN testlist_star_expr suite','for_stmt',5,'p_for_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',481), - ('for_stmt -> FOR [ id_list ] IN testlist_star_expr suite','for_stmt',7,'p_for_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',482), - ('loop_stmt -> loop_head suite','loop_stmt',2,'p_loop_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',489), - ('loop_head -> LOOP ID AS ID','loop_head',4,'p_loop_head','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',495), - ('loop_head -> LOOP ID AS ID : ID','loop_head',6,'p_loop_head','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',496), - ('loop_head -> LOOP ID AS ID : ID restricted_comp_operator ID','loop_head',8,'p_loop_head','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',497), - ('do_stmt -> do_stmt_head suite','do_stmt',2,'p_do_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',508), - ('do_stmt_head -> DO ID = expression , expression','do_stmt_head',6,'p_do_stmt_head','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',515), - ('do_stmt_head -> DO ID = expression , expression , expression','do_stmt_head',8,'p_do_stmt_head','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',516), - ('repeat_stmt -> REPEAT suite','repeat_stmt',2,'p_repeat_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',525), - ('with_stmt -> with_head maybe_nline suite','with_stmt',3,'p_with_stmt','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',529), - ('with_head -> WITH ID AS ID','with_head',4,'p_with_head','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',533), - ('funcdef -> FUNCTION ID OPEN_PAREN arglist CLOSE_PAREN suite','funcdef',6,'p_funcdef','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',537), - ('arglist -> ID : list_display','arglist',3,'p_arglist','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',541), - ('arglist -> arglist , ID : list_display','arglist',5,'p_arglist','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',542), - ('maybe_nline -> newlines','maybe_nline',1,'p_maybe_nline','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',549), - ('maybe_nline -> empty','maybe_nline',1,'p_maybe_nline','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',550), - ('newlines -> NEWLINE','newlines',1,'p_newlines','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',557), - ('newlines -> newlines NEWLINE','newlines',2,'p_newlines','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',558), - ('empty -> ','empty',0,'p_empty','/home/jrh/programs/CIF/pycifrw-git/pycifrw/drel/drel_ast_yacc.py',562), -] diff --git a/GSASII/CifFile/yapps3_compiled_rt.py b/GSASII/CifFile/yapps3_compiled_rt.py deleted file mode 100755 index e97502e04..000000000 --- a/GSASII/CifFile/yapps3_compiled_rt.py +++ /dev/null @@ -1,390 +0,0 @@ -# -# Yapps 2 Runtime, part of Yapps 2 - yet another python parser system -# Copyright 1999-2003 by Amit J. Patel -# -# This version of the Yapps 2 Runtime can be distributed under the -# terms of the MIT open source license, either found in the LICENSE file -# included with the Yapps distribution -# or at -# -# -# Modified for PyCIFRW by JRH to allow external scanner -# -# To maximize python3/python2 compatibility -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import - -""" Detail of JRH modifications. - -The compiled module handles all token administration by itself, but -does not deal with restrictions. It also effectively removes the -context-sensitivity of Yapps, as it ignores restrictions, but -these restrictions turn out to be unnecessary for CIF. - -Interestingly, the module scan function is never called directly -from python. - -""" - -"""Run time libraries needed to run parsers generated by Yapps. - -This module defines parse-time exception classes, a scanner class, a -base class for parsers produced by Yapps, and a context class that -keeps track of the parse stack. - -""" - -# TODO: it should be possible to embed yappsrt into the generated -# grammar to make a standalone module. - -import sys, re - - -# For normal installation this module is "CifFile.yapps3_compiled_rt" -# and StarScan is an extension module within the parent CifFile module. -if __name__.startswith('CifFile.'): - try: - from . import StarScan - have_star_scan = True - except ImportError: - have_star_scan = False -# Otherwise assume this is imported from the yapps3/yapps2.py script -# that is executed from Makefile to generate YappsStarParser sources. -else: - assert __name__ == 'yapps3_compiled_rt', "Unexpected module name." - assert sys.argv[0].endswith('yapps2.py'), ( - "This should be reached only when running yapps2.py in Makefile.") - have_star_scan = False - -class SyntaxError(Exception): - """When we run into an unexpected token, this is the exception to use""" - def __init__(self, charpos=-1, msg="Bad Token", context=None): - Exception.__init__(self) - self.charpos = charpos - self.msg = msg - self.context = context - - def __str__(self): - if self.charpos < 0: return 'SyntaxError' - else: return 'SyntaxError@char%s(%s)' % (repr(self.charpos), self.msg) - -class NoMoreTokens(Exception): - """Another exception object, for when we run out of tokens""" - pass - -class Scanner: - """Yapps scanner. - - The Yapps scanner can work in context sensitive or context - insensitive modes. The token(i) method is used to retrieve the - i-th token. It takes a restrict set that limits the set of tokens - it is allowed to return. In context sensitive mode, this restrict - set guides the scanner. In context insensitive mode, there is no - restriction (the set is always the full set of tokens). - - """ - - def __init__(self, patterns, ignore, input, scantype="standard"): - """Initialize the scanner. - - Parameters: - patterns : [(terminal, uncompiled regex), ...] or None - ignore : [terminal,...] - input : string - - If patterns is None, we assume that the subclass has - defined self.patterns : [(terminal, compiled regex), ...]. - Note that the patterns parameter expects uncompiled regexes, - whereas the self.patterns field expects compiled regexes. - """ - self.tokens = [] # [(begin char pos, end char pos, token name, matched text), ...] - self.restrictions = [] - self.input = input - self.pos = 0 - self.ignore = ignore - self.scantype = scantype - self.first_line_number = 1 - if self.scantype == "flex" and have_star_scan: - StarScan.prepare(input) - self.scan = self.compiled_scan - self.token = self.compiled_token - self.__del__ = StarScan.cleanup - elif self.scantype == "flex": - print("WARNING: using Python scanner although C scanner requested") - self.scantype = "standard" - if self.scantype != "flex": - self.scan = self.interp_scan - self.token = self.interp_token - - if patterns is not None: - # Compile the regex strings into regex objects - self.patterns = [] - for terminal, regex in patterns: - self.patterns.append( (terminal, re.compile(regex)) ) - - def get_token_pos(self): - """Get the current token position in the input text.""" - return len(self.tokens) - - def get_char_pos(self): - """Get the current char position in the input text.""" - return self.pos - - def get_prev_char_pos(self, i=None): - """Get the previous position (one token back) in the input text.""" - if self.pos == 0: return 0 - if i is None: i = -1 - return self.tokens[i][0] - - def get_line_number(self): - """Get the line number of the current position in the input text.""" - # TODO: make this work at any token/char position - return self.first_line_number + self.get_input_scanned().count('\n') - - def get_column_number(self): - """Get the column number of the current position in the input text.""" - s = self.get_input_scanned() - i = s.rfind('\n') # may be -1, but that's okay in this case - return len(s) - (i+1) - - def get_input_scanned(self): - """Get the portion of the input that has been tokenized.""" - return self.input[:self.pos] - - def get_input_unscanned(self): - """Get the portion of the input that has not yet been tokenized.""" - return self.input[self.pos:] - - def interp_token(self, i, restrict=None): - """Get the i'th token in the input. - - If i is one past the end, then scan for another token. - - Args: - - restrict : [token, ...] or None; if restrict is None, then any - token is allowed. You may call token(i) more than once. - However, the restrict set may never be larger than what was - passed in on the first call to token(i). - - """ - if i == len(self.tokens): - self.scan(restrict) - if i < len(self.tokens): - # Make sure the restriction is more restricted. This - # invariant is needed to avoid ruining tokenization at - # position i+1 and higher. - if restrict and self.restrictions[i]: - for r in restrict: - if r not in self.restrictions[i]: - raise NotImplementedError("Unimplemented: restriction set changed") - return self.tokens[i] - raise NoMoreTokens() - - def compiled_token(self,i,restrict=0): - try: - return StarScan.token(i) - except IndexError: - raise NoMoreTokens() - - def __repr__(self): - """Print the last 10 tokens that have been scanned in""" - output = '' - if self.scantype != "flex": - for t in self.tokens[-10:]: - output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3])) - else: - out_tokens = StarScan.last_ten() - for t in out_tokens: - output = '%s\n (~line %s) %s = %s' % (output,t[0],t[2],repr(t[3])) - return output - - def interp_scan(self, restrict): - """Should scan another token and add it to the list, self.tokens, - and add the restriction to self.restrictions""" - # Prepare accepted pattern list - if restrict: - # only patterns in the 'restrict' parameter or in self.ignore - # are accepted - accepted_patterns=[] - for p_name, p_regexp in self.patterns: - if p_name not in restrict and p_name not in self.ignore: - pass - else: - accepted_patterns.append((p_name,p_regexp)) - else: - # every pattern is good - accepted_patterns=self.patterns - # Keep looking for a token, ignoring any in self.ignore - while 1: - # Search the patterns for the longest match, with earlier - # tokens in the list having preference - best_match = -1 - best_pat = '(error)' - for p,regexp in accepted_patterns: - m = regexp.match(self.input, self.pos) - if m and len(m.group(0)) > best_match: - # We got a match that's better than the previous one - best_pat = p - best_match = len(m.group(0)) - - # If we didn't find anything, raise an error - if best_pat == '(error)' and best_match < 0: - msg = 'Bad Token' - if restrict: - msg = 'Trying to find one of '+', '.join(restrict) - raise SyntaxError(self.pos, msg) - - # If we found something that isn't to be ignored, return it - if best_pat not in self.ignore: - # Create a token with this data - token = (self.pos, self.pos+best_match, best_pat, - self.input[self.pos:self.pos+best_match]) - self.pos = self.pos + best_match - # Only add this token if it's not in the list - # (to prevent looping) - if not self.tokens or token != self.tokens[-1]: - self.tokens.append(token) - self.restrictions.append(restrict) - return - else: - # This token should be ignored .. - self.pos = self.pos + best_match - - def compiled_scan(self,restrict): - token = StarScan.scan() - print("Calling compiled scan, got %s" % repr(token)) - if token[2] not in restrict: - msg = "Bad Token" - if restrict: - msg = "Trying to find one of "+join(restrict,", ") - raise SyntaxError(self.pos,msg) - self.tokens.append(token) - self.restrictions.append(restrict) - return - -class Parser: - """Base class for Yapps-generated parsers. - - """ - - def __init__(self, scanner): - self._scanner = scanner - self._pos = 0 - - def _peek(self, *types): - """Returns the token type for lookahead; if there are any args - then the list of args is the set of token types to allow""" - tok = self._scanner.token(self._pos, types) - return tok[2] - - def _scan(self, type): - """Returns the matched text, and moves to the next token""" - tok = self._scanner.token(self._pos, [type]) - if tok[2] != type: - raise SyntaxError(tok[0], 'Trying to find '+type+' :'+ ' ,') - self._pos = 1 + self._pos - return tok[3] - -class Context: - """Class to represent the parser's call stack. - - Every rule creates a Context that links to its parent rule. The - contexts can be used for debugging. - - """ - - def __init__(self, parent, scanner, tokenpos, rule, args=()): - """Create a new context. - - Args: - parent: Context object or None - scanner: Scanner object - pos: integer (scanner token position) - rule: string (name of the rule) - args: tuple listing parameters to the rule - - """ - self.parent = parent - self.scanner = scanner - self.tokenpos = tokenpos - self.rule = rule - self.args = args - - def __str__(self): - output = '' - if self.parent: output = str(self.parent) + ' > ' - output += self.rule - return output - -# -# Note that this sort of error printout is useless with the -# compiled scanner -# - -def print_line_with_pointer(text, p): - """Print the line of 'text' that includes position 'p', - along with a second line with a single caret (^) at position p""" - - # TODO: separate out the logic for determining the line/character - # location from the logic for determining how to display an - # 80-column line to stderr. - - # Now try printing part of the line - text = text[max(p-80, 0):p+80] - p = p - max(p-80, 0) - - # Strip to the left - i = text[:p].rfind('\n') - j = text[:p].rfind('\r') - if i < 0 or (0 <= j < i): i = j - if 0 <= i < p: - p = p - i - 1 - text = text[i+1:] - - # Strip to the right - i = text.find('\n', p) - j = text.find('\r', p) - if i < 0 or (0 <= j < i): i = j - if i >= 0: - text = text[:i] - - # Now shorten the text - while len(text) > 70 and p > 60: - # Cut off 10 chars - text = "..." + text[10:] - p = p - 7 - - # Now print the string, along with an indicator - print('> ',text,file=sys.stderr) - print('> ',' '*p + '^',file=sys.stderr) - -def print_error(input, err, scanner): - """Print error messages, the parser stack, and the input text -- for human-readable error messages.""" - # NOTE: this function assumes 80 columns :-( - # Figure out the line number - line_number = scanner.get_line_number() - column_number = scanner.get_column_number() - print('%d:%d: %s' % (line_number, column_number, err.msg),file=sys.stderr) - - context = err.context - if not context: - print_line_with_pointer(input, err.charpos) - - while context: - # TODO: add line number - print('while parsing %s%s:' % (context.rule, tuple(context.args)),file=sys.stderr) - print_line_with_pointer(input, context.scanner.get_prev_char_pos(context.tokenpos)) - context = context.parent - -def wrap_error_reporter(parser, rule): - try: - return getattr(parser, rule)() - except SyntaxError as e: - input = parser._scanner.input - print_error(input, e, parser._scanner) - except NoMoreTokens: - print('Could not complete parsing; stopped around here:',file=sys.stderr) - print(parser._scanner,file=sys.stderr) diff --git a/GSASII/G2.py b/GSASII/G2.py new file mode 100644 index 000000000..e46930d44 --- /dev/null +++ b/GSASII/G2.py @@ -0,0 +1,29 @@ +# Starts GSAS-II from clone of GitHub repo when contents is not +# installed into current Python. +# +# This can be used when GSAS-II is installed, but why bother as the command: +# python -c "from GSASII.GSASIIGUI import main; main()" +# will do just as well. +# Even better: +# GSASII_NOPATHHACKING="true" python -c "from GSASII.GSASIIGUI import main; main()" + +import os +import sys +# patch 4/25: cleanup old GSASII.py script if still around (replaced by this file) +oldg2script = os.path.join(os.path.dirname(__file__),'GSASII.py') +if os.path.exists(oldg2script): + os.remove(oldg2script) + print(f'Cleanup: removing old {oldg2script!r} file') +# end patch +import importlib.util +try: + pkginfo = importlib.util.find_spec('GSASII.GSASIIGUI') +except ModuleNotFoundError: + pkginfo = None + +if __name__ == '__main__': + if pkginfo is None: # hack path if GSASII not installed into Python + print('Adding GSAS-II location to Python system path') + sys.path.insert(0,os.path.dirname(os.path.dirname(__file__))) + from GSASII.GSASIIGUI import main + main() diff --git a/GSASII/G2compare.py b/GSASII/G2compare.py index 0628abb2d..25c323241 100644 --- a/GSASII/G2compare.py +++ b/GSASII/G2compare.py @@ -14,14 +14,7 @@ import os import platform import glob -if '2' in platform.python_version_tuple()[0]: - import cPickle -else: - try: - import _pickle as cPickle - except: - print('Warning: failed to import the optimized Py3 pickle (_pickle)') - import pickle as cPickle +import pickle import wx import numpy as np @@ -32,19 +25,19 @@ pass import scipy as sp -import GSASIIpath -import GSASIIfiles as G2fil -import GSASIIplot as G2plt -import GSASIIdataGUI as G2gd -import GSASIIctrlGUI as G2G -import GSASIIobj as G2obj +from . import GSASIIpath +from . import GSASIIfiles as G2fil +from . import GSASIIplot as G2plt +from . import GSASIIdataGUI as G2gd +from . import GSASIIctrlGUI as G2G +from . import GSASIIobj as G2obj __version__ = '0.0.1' -# math to do F-test +# math to do F-test def RC2Ftest(npts,RChiSq0,nvar0,RChiSq1,nvar1): - '''Compute the F-test probability that a model expanded with added - parameters (relaxed model) is statistically more likely than the + '''Compute the F-test probability that a model expanded with added + parameters (relaxed model) is statistically more likely than the constrained (base) model :param int npts: number of observed diffraction data points :param float RChiSq0: Reduced Chi**2 for the base model @@ -65,8 +58,8 @@ def RC2Ftest(npts,RChiSq0,nvar0,RChiSq1,nvar1): return scipy.stats.f.cdf(F,nu1,nu2) def RwFtest(npts,Rwp0,nvar0,Rwp1,nvar1): - '''Compute the F-test probability that a model expanded with added - parameters (relaxed model) is statistically more likely than the + '''Compute the F-test probability that a model expanded with added + parameters (relaxed model) is statistically more likely than the constrained (base) model :param int npts: number of observed diffraction data points :param float Rwp0: Weighted profile R-factor or GOF for the base model @@ -86,17 +79,14 @@ def RwFtest(npts,Rwp0,nvar0,Rwp1,nvar1): import scipy.stats return scipy.stats.f.cdf(F,nu1,nu2) -def cPickleLoad(fp): - if '2' in platform.python_version_tuple()[0]: - return cPickle.load(fp) - else: - return cPickle.load(fp,encoding='latin-1') - +def pickleLoad(fp): + return pickle.load(fp,encoding='latin-1') + def main(application): '''Start up the GSAS-II GUI''' knownVersions = ['3.9','3.10','3.11','3.12'] - if '.'.join(platform.python_version().split('.')[:2]) not in knownVersions: - dlg = wx.MessageDialog(None, + if '.'.join(platform.python_version().split('.')[:2]) not in knownVersions: + dlg = wx.MessageDialog(None, f'GSAS-II Compare requires Python 3.9+\n Yours is {sys.version.split()[0]}', 'Python version error', wx.OK) try: @@ -104,7 +94,7 @@ def main(application): finally: dlg.Destroy() sys.exit() - + application.main = MakeTopWindow(None) # application.main is the main wx.Frame application.SetTopWindow(application.main) # save the current package versions @@ -116,7 +106,7 @@ def main(application): #application.GetTopWindow().SendSizeEvent() application.GetTopWindow().Show(True) return application.GetTopWindow() - + class MakeTopWindow(wx.Frame): '''Define the main frame and its associated menu items ''' @@ -125,7 +115,7 @@ def __init__(self, parent): wx.Frame.__init__(self, name='dComp', parent=parent, size=size,style=wx.DEFAULT_FRAME_STYLE, title='GSAS-II data/model comparison') # misc vars - self.VcovColor = 'RdYlGn' + self.VcovColor = 'RdYlGn' # plot window try: size = GSASIIpath.GetConfigValue('Plot_Size') @@ -136,7 +126,7 @@ def __init__(self, parent): else: raise Exception except: - size = wx.Size(700,600) + size = wx.Size(700,600) self.plotFrame = wx.Frame(None,-1,'dComp Plots',size=size, style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX) self.G2plotNB = G2plt.G2PlotNoteBook(self.plotFrame,G2frame=self) @@ -147,11 +137,11 @@ def __init__(self, parent): self.MenuBar = wx.MenuBar() File = wx.Menu(title='') self.MenuBar.Append(menu=File, title='&File') - item = File.Append(wx.ID_ANY,'&Import single project...\tCtrl+O','Open a GSAS-II project file (*.gpx)') + item = File.Append(wx.ID_ANY,'&Import single project...\tCtrl+O','Open a GSAS-II project file (*.gpx)') self.Bind(wx.EVT_MENU, self.onLoadGPX, id=item.GetId()) item = File.Append(wx.ID_ANY,'&Import multiple projects...\tCtrl+U','Open a GSAS-II project file (*.gpx)') self.Bind(wx.EVT_MENU, self.onLoadMultGPX, id=item.GetId()) - item = File.Append(wx.ID_ANY,'&Wildcard import of projects...\tCtrl+W','Open a GSAS-II project file (*.gpx)') + item = File.Append(wx.ID_ANY,'&Wildcard import of projects...\tCtrl+W','Open a GSAS-II project file (*.gpx)') self.Bind(wx.EVT_MENU, self.onLoadWildGPX, id=item.GetId()) # item = File.Append(wx.ID_ANY,'&Import selected...','Open a GSAS-II project file (*.gpx)') # self.Bind(wx.EVT_MENU, self.onLoadSel, id=item.GetId()) @@ -169,7 +159,7 @@ def __init__(self, parent): modeMenu = wx.Menu(title='') self.MenuBar.Append(menu=modeMenu, title='TBD') self.modeMenuPos = self.MenuBar.FindMenu('TBD') - + Frame.SetMenuBar(self.MenuBar) # status bar self.Status = self.CreateStatusBar() @@ -179,14 +169,14 @@ def __init__(self, parent): self.mainPanel.SetMinimumPaneSize(100) self.treePanel = wx.Panel(self.mainPanel, wx.ID_ANY, style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER) - + # self.dataWindow = G2DataWindow(self.mainPanel) self.dataWindow = wx.ScrolledWindow(self.mainPanel) dataSizer = wx.BoxSizer(wx.VERTICAL) self.dataWindow.SetSizer(dataSizer) self.mainPanel.SplitVertically(self.treePanel, self.dataWindow, 200) self.Status.SetStatusWidths([200,-1]) # make these match? - + treeSizer = wx.BoxSizer(wx.VERTICAL) self.treePanel.SetSizer(treeSizer) self.GPXtree = G2G.G2TreeCtrl(id=wx.ID_ANY, @@ -206,10 +196,10 @@ def __init__(self, parent): # self.GPXtree.Bind(wx.EVT_TREE_KEY_DOWN, # self.OnGPXtreeKeyDown, id=TreeId) # self.GPXtree.Bind(wx.EVT_TREE_BEGIN_RDRAG, - # self.OnGPXtreeBeginRDrag, id=TreeId) + # self.OnGPXtreeBeginRDrag, id=TreeId) # self.GPXtree.Bind(wx.EVT_TREE_END_DRAG, - # self.OnGPXtreeEndDrag, id=TreeId) - self.root = self.GPXtree.root + # self.OnGPXtreeEndDrag, id=TreeId) + self.root = self.GPXtree.root self.Bind(wx.EVT_CLOSE, lambda event: sys.exit()) self.fileList = [] # list of files read for use in Reload @@ -230,11 +220,11 @@ def __init__(self, parent): print('Value for config {} {} is invalid'.format(var,GSASIIpath.GetConfigValue(var))) win.Center() self.SetModeMenu() - + def SelectGPX(self): '''Select a .GPX file to be read ''' - dlg = wx.FileDialog(self, 'Choose GSAS-II project file', + dlg = wx.FileDialog(self, 'Choose GSAS-II project file', wildcard='GSAS-II project file (*.gpx)|*.gpx',style=wx.FD_OPEN) try: if dlg.ShowModal() != wx.ID_OK: return @@ -251,7 +241,7 @@ def SelectGPX(self): def SelectMultGPX(self): '''Select multiple .GPX files to be read ''' - dlg = wx.FileDialog(self, 'Choose GSAS-II project file', + dlg = wx.FileDialog(self, 'Choose GSAS-II project file', wildcard='GSAS-II project file (*.gpx)|*.gpx', style=wx.FD_OPEN|wx.FD_MULTIPLE) try: @@ -268,7 +258,7 @@ def SelectMultGPX(self): else: print('File {} not found, skipping'.format(fil)) return fileList - + def getMode(self): '''returns the display mode (one of "Histogram","Phase","Project"). Could return '?' in case of an error. @@ -279,7 +269,7 @@ def getMode(self): else: m = '?' return m - + def onRefresh(self,event): '''reread all files, in response to a change in mode, etc. ''' @@ -292,7 +282,7 @@ def onRefresh(self,event): self.loadFile(fil) self.doneLoad() self.SetModeMenu() - + def SetModeMenu(self): '''Create the mode-specific menu and its contents ''' @@ -350,18 +340,18 @@ def onLoadWildGPX(self,event,wildcard=None): '''Initial load of GPX file in response to a menu command ''' home = os.path.abspath(os.getcwd()) - hlp = '''Enter a wildcard version of a file name. + hlp = '''Enter a wildcard version of a file name. The directory is assumed to be "{}" unless specified otherwise. Extensions will be set to .gpx and .bak files will be ignored unless -the wildcard string includes "bak". For example, "*abc*" will match any +the wildcard string includes "bak". For example, "*abc*" will match any .gpx file in that directory containing "abc". String "/tmp/A*" will match -files in "/tmp" beginning with "A". Supplying two strings, "A*" and "B*bak*" -will match files names beginning with "A" or "B", but ".bak" files will +files in "/tmp" beginning with "A". Supplying two strings, "A*" and "B*bak*" +will match files names beginning with "A" or "B", but ".bak" files will be included for the files beginning with "B" only. '''.format(home) if wildcard is None: - dlg = G2G.MultiStringDialog(self, 'Enter wildcard file names', - ['wild-card 1'] , values=['*'], + dlg = G2G.MultiStringDialog(self, 'Enter wildcard file names', + ['wild-card 1'] , values=['*'], lbl='Provide string(s) with "*" to find matching files', addRows=True, hlp=hlp) res = dlg.Show() @@ -374,7 +364,7 @@ def onLoadWildGPX(self,event,wildcard=None): if not os.path.split(w)[0]: w = os.path.join(home,w) w = os.path.splitext(w)[0] + '.gpx' - for fil in glob.glob(w): + for fil in glob.glob(w): if not os.path.exists(fil): continue if '.bak' in fil and 'bak' not in w: continue if fil in [i for i,j in self.fileList]: continue @@ -395,7 +385,7 @@ def LoadPwdr(self,fil): try: while True: try: - data = cPickleLoad(filep) + data = pickleLoad(filep) except EOFError: break if not data[0][0].startswith('PWDR'): continue @@ -406,7 +396,7 @@ def LoadPwdr(self,fil): data[0][0] += shortname data[0][0] += ')' histLoadList.append(data) - + except Exception as errmsg: if GSASIIpath.GetConfigValue('debug'): print('\nError reading GPX file:',errmsg) @@ -446,7 +436,7 @@ def LoadPwdr(self,fil): # G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0) # G2frame.SetTitleByGPX() self.GPXtree.Expand(self.root) - + def onHistFilter(self,event): 'Load a filter string via a dialog in response to a menu event' defval = '' @@ -479,7 +469,7 @@ def LoadPhase(self,fil): try: while True: try: - data = cPickleLoad(filep) + data = pickleLoad(filep) except EOFError: break if not data[0][0].startswith('Phase'): continue @@ -490,12 +480,12 @@ def LoadPhase(self,fil): if Phases: data[0][0] += shortname data[0][0] += ')' - else: + else: data[0][0] += shortname data[0][0] += 'has no phases)' Phases = data break - + except Exception as errmsg: if GSASIIpath.GetConfigValue('debug'): print('\nError reading GPX file:',errmsg) @@ -549,10 +539,10 @@ def LoadProject(self,fil): try: while True: try: - data = cPickleLoad(filep) + data = pickleLoad(filep) except EOFError: break - if data[0][0].startswith('PWDR'): + if data[0][0].startswith('PWDR'): self.histListOrg.append(data[0][0]) if self.PWDRfilter is not None: # implement filter if self.PWDRfilter in data[0][0]: match = True @@ -586,7 +576,7 @@ def LoadProject(self,fil): finally: filep.close() wx.EndBusyCursor() - + def OnDataTreeSelChanged(self,event): def ClearData(self): '''Initializes the contents of the dataWindow panel @@ -645,7 +635,7 @@ def ClearData(self): else: self.plotFrame.Show(False) ClearData(G2frame.dataWindow) - + #print(self.GPXtree._getTreeItemsList(item)) # pltNum = self.G2plotNB.nb.GetSelection() # print(pltNum) @@ -657,7 +647,7 @@ def ClearData(self): #if self.getMode() == "Histogram": #self.PatternId = self.PickId = item #G2plt.PlotPatterns(self,plotType='PWDR',newPlot=NewPlot) - + # def OnGPXtreeItemExpanded(self,event): # item = event.GetItem() # print('expanded',item) @@ -666,7 +656,7 @@ def ClearData(self): # event.StopPropagation() # else: # event.Skip(False) - + def onProjFtest(self,event): '''Compare two projects (selected here if more than two are present) using the statistical F-test (aka Hamilton R-factor test), see: @@ -685,7 +675,7 @@ def onProjFtest(self,event): elif len(items) == 2: s0 = items[0] baseDict = self.GPXtree.GetItemPyData(s0) - s1 = items[1] + s1 = items[1] relxDict = self.GPXtree.GetItemPyData(s1) # sort out the dicts in order of number of refined variables if len(baseDict['varyList']) > len(relxDict['varyList']): @@ -732,8 +722,8 @@ def onProjFtest(self,event): G2G.G2MessageBox(self,'F-test requires same number of observations in each refinement','F-test not valid') return missingVars = [] - for var in baseDict['varyList']: - if var not in relxDict['varyList']: + for var in baseDict['varyList']: + if var not in relxDict['varyList']: missingVars.append(var) txt = '' postmsg = '' @@ -751,8 +741,8 @@ def onProjFtest(self,event): postmsg += ('\nThese parameters are in the relaxed fit and not'+ ' in the constrained fit:\n* ') i = 0 - for var in relxDict['varyList']: - if var not in baseDict['varyList']: + for var in relxDict['varyList']: + if var not in baseDict['varyList']: if i > 0: postmsg += ', ' i += 1 postmsg += var @@ -778,11 +768,11 @@ def onProjFtest(self,event): msg += "The constrained model is statistically preferred to the relaxed model." msg += postmsg G2G.G2MessageBox(self,msg,'F-test result') - + def onHistPrinceTest(self,event): '''Compare two histograms (selected here if more than two are present) - using the statistical test proposed by Ted Prince in - Acta Cryst. B35 1099-1100. (1982). Also see Int. Tables Vol. C + using the statistical test proposed by Ted Prince in + Acta Cryst. B35 1099-1100. (1982). Also see Int. Tables Vol. C (1st Ed.) chapter 8.4, 618-621 (1995). ''' items = [] @@ -837,41 +827,41 @@ def onHistPrinceTest(self,event): # Z = (data0[1] - (data0[3] + data1[3])/2) / np.sqrt(data0[1]) # lam = np.sum(X*Z) / np.sum(X) # sig = np.sqrt( - # (np.sum(Z*Z) - lam*lam*np.sum(X*X)) / + # (np.sum(Z*Z) - lam*lam*np.sum(X*X)) / # ((len(data0[0]) - 1) * np.sum(X*X)) # ) - + # 0 the x-postions (two-theta in degrees), # 1 the intensity values (Yobs), # 2 the weights for each Yobs value # 3 the computed intensity values (Ycalc) # 4 the background values # 5 Yobs-Ycalc - + GSASIIpath.IPyBreak_base() - -#====================================================================== + +#====================================================================== if __name__ == '__main__': - #if sys.platform == "darwin": + #if sys.platform == "darwin": # application = G2App(0) # create the GUI framework #else: application = wx.App(0) # create the GUI framework try: GSASIIpath.SetBinaryPath(True) except: - print('Unable to run with current setup, do you want to update to the') - try: -# if '2' in platform.python_version_tuple()[0]: -# ans = raw_input("latest GSAS-II version? Update ([Yes]/no): ") -# else: - ans = input("latest GSAS-II version? Update ([Yes]/no): ") - except: - ans = 'no' - if ans.strip().lower() == "no": - print('Exiting') - sys.exit() - print('Updating...') - GSASIIpath.svnUpdateProcess() + print('Unable to run with current setup')# , do you want to update to the') +# try: +# # if '2' in platform.python_version_tuple()[0]: +# # ans = raw_input("latest GSAS-II version? Update ([Yes]/no): ") +# # else: +# ans = input("latest GSAS-II version? Update ([Yes]/no): ") +# except: +# ans = 'no' +# if ans.strip().lower() == "no": +# print('Exiting') +# sys.exit() +# print('Updating...') +# GSASIIpath.svnUpdateProcess() GSASIIpath.InvokeDebugOpts() Frame = main(application) # start the GUI loadall = False @@ -902,5 +892,5 @@ def onHistPrinceTest(self,event): Frame.onLoadWildGPX(None,wildcard='*') Frame.Mode.FindItemById(Frame.wxID_Mode['Project']).Check(True) Frame.onRefresh(None) - + application.MainLoop() diff --git a/GSASII/GSASII.py b/GSASII/GSASII.py deleted file mode 100755 index 385062c79..000000000 --- a/GSASII/GSASII.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -#GSASII -''' -A single class, :class:`G2App`, is defined here to create -an wxPython application. This is only used on -MacOS. For other platforms ``wx.App()`` is called directly. -''' - -import sys -#import platform -import scipy.optimize # loading here addresses problem with build for wx on Pi -try: - import wx - # the next line removes the need for pythonw. Thanks to Matt Newville! - # appears unneeded from wx 4.2.1 on - if sys.platform.lower() == 'darwin': wx.PyApp.IsDisplayAvailable = lambda _: True -# importing the following wx modules at the same time as wx seems to eliminate -# the "Debug: Adding duplicate image handler for 'Windows bitmap file'" -# error message - import wx.grid as wg - import wx.aui - import wx.lib.scrolledpanel as wxscroll - import wx.lib.mixins.listctrl as listmix - import wx.richtext as wxrt - import wx.lib.filebrowsebutton as wxfilebrowse - wg,wx.aui,wxscroll,listmix,wxrt,wxfilebrowse,scipy.optimize # avoid unused warning -except ImportError: - pass -import GSASIIpath - -__version__ = '2.0.0' -try: - import git_verinfo - __version__ = git_verinfo.git_tags[0] -except: - pass - -class G2App(wx.App): - '''Used to create a wx python application for the GUI for Mac. - Customized to implement drop of GPX files onto app. - ''' - startupMode = True - def ClearStartup(self): - '''Call this after app startup complete because a Drop event is posted - when GSAS-II is initially started. - ''' - self.startupMode = False - def MacOpenFiles(self, filenames): - if self.startupMode: - return - import GSASIIfiles - for project in filenames: - #print("Start GSAS-II with project file "+str(project)) - #GSASIIpath.MacStartGSASII(__file__,project) - GSASIIfiles.openInNewTerm(project) - -if __name__ == '__main__': - if sys.platform == "darwin": - application = G2App(0) # create the GUI framework - else: - application = wx.App(0) # create the GUI framework - try: - GSASIIpath.SetBinaryPath(True) - except: - print('Unable to run with current installation, please reset or reinstall') - # if GSASIIpath.HowIsG2Installed().startswith('git'): - # print('use this command w/gitstrap') - # elif GSASIIpath.HowIsG2Installed().startswith('svn'): - # print('use this command w/bootstrap') - sys.exit() - # print('Unable to run with current setup, do you want to update to the') - # try: - # if '2' in platform.python_version_tuple()[0]: - # ans = raw_input("latest GSAS-II version? Update ([Yes]/no): ") - # else: - # ans = input("latest GSAS-II version? Update ([Yes]/no): ") - # except: - # ans = 'no' - # if ans.strip().lower() == "no": - # import sys - # print('Exiting') - # sys.exit() - # print('Updating...') - # import GSASIIctrlGUI as G2G - #GSASIIpath.svnUpdateProcess() - # if GSASIIpath.HowIsG2Installed().startswith('git'): - # gitCheckUpdates(None) - # elif GSASIIpath.HowIsG2Installed().startswith('svn'): - # svnCheckUpdates(None) - # else: - import GSASIIdataGUI as G2gd - G2gd.GSASIImain(application) # start the GUI - if sys.platform == "darwin": - wx.CallLater(50,application.ClearStartup) - GSASIIpath.InvokeDebugOpts() - application.MainLoop() diff --git a/GSASII/GSASIIElem.py b/GSASII/GSASIIElem.py index c042aca79..0a3f754c8 100644 --- a/GSASII/GSASIIElem.py +++ b/GSASII/GSASIIElem.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- # Copyright: 2008, Robert B. Von Dreele & Brian H. Toby (Argonne National Laboratory) """ -Routines used to define element settings follow. +Routines used to define element settings follow. """ import math import sys import os.path -import GSASIIpath +from . import GSASIIpath import copy import numpy as np -import atmdata -import GSASIImath as G2mth -import ElementTable as ET -#import GSASIIElem as G2elem # but this module is GSASIIElem. Why are we doing this? +from . import atmdata +from . import GSASIImath as G2mth +from . import ElementTable as ET + nxs = np.newaxis Bohr = 0.529177 @@ -32,46 +32,46 @@ def GetFormFactorCoeff(El): :param str El: element 1-2 character symbol, case irrevelant :return: `FormFactors`: list of form factor dictionaries - + Each X-ray form factor dictionary is: - + * `Symbol`: 4 character element symbol with valence (e.g. 'NI+2') * `Z`: atomic number * `fa`: 4 A coefficients * `fb`: 4 B coefficients * `fc`: C coefficient - + """ - + Els = El.capitalize().strip() valences = [ky for ky in atmdata.XrayFF.keys() if Els == getElSym(ky)] FormFactors = [atmdata.XrayFF[val] for val in valences] for Sy,FF in zip(valences,FormFactors): FF.update({'Symbol':Sy.upper()}) return FormFactors - + def GetEFormFactorCoeff(El): """Read electron form factor coefficients from `atomdata.py` file :param str El: element 1-2 character symbol, case irrevelant :return: `FormFactors`: list of form factor dictionaries - + Each electrn form factor dictionary is: - + * `Symbol`: 4 character element symbol (no valence) * `Z`: atomic number * `fa`: 5 A coefficients * `fb`: 5 B coefficients - + """ - + Els = El.capitalize().strip() valences = [ky for ky in atmdata.ElecFF.keys() if Els == getElSym(ky)] #will only be one FormFactors = [atmdata.ElecFF[val] for val in valences] for Sy,FF in zip(valences,FormFactors): FF.update({'Symbol':Sy.upper()}) return FormFactors - + def GetFFtable(atomTypes): ''' returns a dictionary of form factor data for atom types found in atomTypes @@ -86,7 +86,7 @@ def GetFFtable(atomTypes): if item['Symbol'] == El.upper(): FFtable[El] = item return FFtable - + def GetEFFtable(atomTypes): ''' returns a dictionary of electron form factor data for atom types found in atomTypes might not be needed? @@ -102,7 +102,7 @@ def GetEFFtable(atomTypes): if item['Symbol'] == El.upper(): FFtable[El] = item return FFtable - + def GetORBtable(atomTypes): ''' returns a dictionary of orbital form factor data for atom types found in atomTypes @@ -114,7 +114,7 @@ def GetORBtable(atomTypes): for El in atomTypes: ORBtable[El] = copy.deepcopy(atmdata.OrbFF[El]) return ORBtable - + def GetMFtable(atomTypes,Landeg): ''' returns a dictionary of magnetic form factor data for atom types found in atomTypes @@ -131,7 +131,7 @@ def GetMFtable(atomTypes,Landeg): item['gfac'] = gfac MFtable[El] = item return MFtable - + def GetBLtable(General): ''' returns a dictionary of neutron scattering length data for atom types & isotopes found in General @@ -148,7 +148,7 @@ def GetBLtable(General): else: BLtable[El] = [isotope[El],atmdata.AtmBlens[ElS+'_'+isotope[El]]] return BLtable - + def getFFvalues(FFtables,SQ,ifList=False): 'Needs a doc string' if ifList: @@ -160,7 +160,7 @@ def getFFvalues(FFtables,SQ,ifList=False): for El in FFtables: FFvals[El] = ScatFac(FFtables[El],SQ)[0] return FFvals - + def getBLvalues(BLtables,ifList=False): 'Needs a doc string' if ifList: @@ -178,7 +178,7 @@ def getBLvalues(BLtables,ifList=False): else: BLvals[El] = BLtables[El][1]['SL'][0] return BLvals - + def getMFvalues(MFtables,SQ,ifList=False): 'Needs a doc string' if ifList: @@ -190,14 +190,14 @@ def getMFvalues(MFtables,SQ,ifList=False): for El in MFtables: MFvals[El] = MagScatFac(MFtables[El],SQ)[0] return MFvals - + def GetFFC5(ElSym): '''Get 5 term form factor and Compton scattering data :param ElSym: str(1-2 character element symbol with proper case); :return El: dictionary with 5 term form factor & compton coefficients ''' - import FormFactors as FF + from . import FormFactors as FF El = {} FF5 = FF.FFac5term[ElSym] El['fa'] = FF5[:5] @@ -227,7 +227,7 @@ def GetBVS(Pair,atSeq,Valences): return 0.0 else: return 0.0 - + def CheckElement(El): '''Check if element El is in the periodic table @@ -241,7 +241,7 @@ def CheckElement(El): if El.capitalize() in Elements: return True else: - return False + return False def FixValence(El): 'Returns the element symbol, even when a valence is present' @@ -265,7 +265,7 @@ def SetAtomColor(El,RGB): ElS = getElSym(El) ET.ElTable[Elements.index(ElS)] = ET.ElTable[Elements.index(ElS)][0:6] + ( tuple(RGB),) - + def GetAtomInfo(El,ifMag=False): 'reads element information from atmdata.py' Elem = ET.ElTable @@ -299,11 +299,11 @@ def GetAtomInfo(El,ifMag=False): AtomInfo['Isotopes'][isotope.split('_')[1]] = data AtomInfo['Lande g'] = 2.0 return AtomInfo - + def GetElInfo(El,inst): ElemSym = El.strip().capitalize() - if 'X' in inst['Type'][0]: - keV = 12.397639/G2mth.getWave(inst) + if 'X' in inst['Type'][0]: + keV = 12.397639/G2mth.getWave(inst) FpMu = FPcalc(GetXsectionCoeff(ElemSym), keV) ElData = GetFormFactorCoeff(ElemSym)[0] ElData['FormulaNo'] = 0.0 @@ -316,7 +316,7 @@ def GetElInfo(El,inst): ElData['FormulaNo'] = 0.0 ElData.update({'mu':0.0,'fp':0.0,'fpp':0.0}) return ElData - + def GetXsectionCoeff(El): """Read atom orbital scattering cross sections for fprime calculations via Cromer-Lieberman algorithm @@ -400,7 +400,7 @@ def GetXsectionCoeff(El): Orbs.append(Orb) xsec.close() return Orbs - + def GetMagFormFacCoeff(El): """Read magnetic form factor data from atmdata.py @@ -417,7 +417,7 @@ def GetMagFormFacCoeff(El): * 'nfb': 4 NB coefficients * 'mfc': MC coefficient * 'nfc': NC coefficient - + """ Els = El.capitalize().strip() MagFormFactors = [] @@ -439,7 +439,7 @@ def GetMagFormFacCoeff(El): def ScatFac(El, SQ): """compute value of form factor - :param El: element dictionary defined in GetFormFactorCoeff + :param El: element dictionary defined in GetFormFactorCoeff :param SQ: (sin-theta/lambda)**2 :return: real part of form factor """ @@ -451,7 +451,7 @@ def ScatFac(El, SQ): def ScatFacDer(El, SQ): """compute derivative of form factor wrt SQ - :param El: element dictionary defined in GetFormFactorCoeff + :param El: element dictionary defined in GetFormFactorCoeff :param SQ: (sin-theta/lambda)**2 :return: real part of form factor """ @@ -459,11 +459,11 @@ def ScatFacDer(El, SQ): fb = np.array(El['fb']) t = -fb[:,np.newaxis]*SQ return -np.sum(fa[:,np.newaxis]*fb[:,np.newaxis]*np.exp(t)[:],axis=0) - + def MagScatFac(El, SQ): """compute value of form factor - :param El: element dictionary defined in GetFormFactorCoeff + :param El: element dictionary defined in GetFormFactorCoeff :param SQ: (sin-theta/lambda)**2 :param gfac: Lande g factor (normally = 2.0) :return: real part of form factor @@ -481,49 +481,15 @@ def MagScatFac(El, SQ): MF0 = MMF0+(2.0/El['gfac']-1.0)*NMF0 return (MMF+(2.0/El['gfac']-1.0)*NMF)/MF0 -def scaleCoef(terms): - ''' rescale J2K6 form factor coeff - matches J2K fortran result - ''' - terms = copy.deepcopy(terms) - for term in terms: - z2 = 2.*term[1]/Bohr - k = 2*term[2]+1 - rn = (z2**k)/math.factorial(k-1) - term[0] *= np.sqrt(rn) - return terms - -def J2Kff(fpsq,terms,orb): - - def Transo(nn,z,s): - d = s**2+z**2 - a = np.zeros((28,s.shape[0])) - tz = 2.0*z - a[2,:] = 1./d - for nx in list(range(1,nn)): - pp = (nx)*(nx-1) - i3 = nx+2 - a[nx+2,:] = (tz*nx*a[nx+1,:]-pp*a[nx,:])/d[nxs,:] - return a[i3] - - fjc = np.zeros_like(fpsq) - for term1 in terms: - for term2 in terms: - zz = (term1[1]+term2[1])/Bohr - nn = term1[2]+term2[2] - tf = Transo(nn,zz,fpsq) - ff = term1[0]*term2[0]*tf*orb - fjc += ff - return fjc - def ClosedFormFF(Z,SQ,k,N): """Closed form expressions for FT Slater fxns. IT B Table 1.2.7.4 (not used at present - doesn't make sense yet) - - :param Z: element zeta factor + + :param Z: element zeta factor :param SQ: (sin-theta/lambda)**2 :param k: int principal Bessel fxn order as in :param N: int power - + return: form factor """ Z2 = Z**2 @@ -610,7 +576,7 @@ def ClosedFormFF(Z,SQ,k,N): elif k == 7: if N == 8: return 645120.0*K**7/K2pZ2**8 - + def BlenResCW(Els,BLtables,wave): ''' Computes resonant scattering lengths - single wavelength version (CW) returns bo+b' and b"' @@ -633,7 +599,7 @@ def BlenResCW(Els,BLtables,wave): else: FPP[i] = BL['SL'][1] #for Li, B, etc. return FP,FPP - + def BlenResTOF(Els,BLtables,wave): ''' Computes resonant scattering lengths - multiple wavelength version (TOF) returns bo+b' and b"' @@ -656,19 +622,19 @@ def BlenResTOF(Els,BLtables,wave): else: FPP[i] = np.ones(len(wave))*BL[i]['SL'][1] #for Li, B, etc. return FP,FPP - + def ComptonFac(El,SQ): """compute Compton scattering factor - :param El: element dictionary + :param El: element dictionary :param SQ: (sin-theta/lambda)**2 :return: compton scattering factor - """ + """ ca = np.array(El['cmpa']) cb = np.array(El['cmpb']) - t = -cb[:,np.newaxis]*SQ - return El['cmpz']-np.sum(ca[:,np.newaxis]*np.exp(t),axis=0) - + t = -cb[:,np.newaxis]*SQ + return El['cmpz']-np.sum(ca[:,np.newaxis]*np.exp(t),axis=0) + def FPcalc(Orbs, KEv): """Compute real & imaginary resonant X-ray scattering factors @@ -693,13 +659,13 @@ def Aitken(Orb, LKev): T[2] = (T[1]*T[5]-T[2]*T[4])/(LEner[j+2]-LEner[j+1]) C = T[2] return C - + def DGauss(Orb,CX,RX,ISig): ALG = (0.11846344252810,0.23931433524968,0.284444444444, 0.23931433524968,0.11846344252810) XLG = (0.04691007703067,0.23076534494716,0.5, 0.76923465505284,0.95308992296933) - + D = 0.0 B2 = Orb['BB']**2 R2 = RX**2 @@ -719,8 +685,8 @@ def DGauss(Orb,CX,RX,ISig): S = BB*B2*(XS-Orb['SEdge']*X2)/(R2*X2**2-X2*B2) A = ALG[i] D += A*S - return D - + return D + AU = 2.80022e+7 C1 = 0.02721 C = 137.0367 @@ -752,9 +718,9 @@ def DGauss(Orb,CX,RX,ISig): FP += FPI FPP += FPPI FP -= ElEterm - + return (FP, FPP, Mu) - + mapDefault = {'MapType':'','RefList':'','GridStep':0.25,'Show bonds':True, 'rho':[],'rhoMax':0.,'mapSize':10.0,'cutOff':50.,'Flip':False} @@ -762,7 +728,7 @@ def SetupGeneral(data, dirname): '''Initialize the General sections of the Phase tree contents. Should be done after changes to the Atoms array. - Called by routine SetupGeneral (in :func:`GSASIIphsGUI.UpdatePhaseData`), + Called by routine SetupGeneral (in :func:`GSASIIphsGUI.UpdatePhaseData`), :func:`GSASIIphsGUI.makeIsoNewPhase`, :func:`GSASIImiscGUI.saveNewPhase`, and in :func:`GSASIIscriptable.SetupGeneral`. ''' @@ -911,7 +877,6 @@ def SetupGeneral(data, dirname): continue nSh = len(Srb['RBId']) for iSh in range(nSh): -# Info = G2elem.GetAtomInfo(Srb['atType'][iSh]) Info = GetAtomInfo(Srb['atType'][iSh]) if Info['Symbol'] not in generalData['AtomTypes']: generalData['AtomTypes'].append(Info['Symbol']) @@ -935,7 +900,7 @@ def SetupGeneral(data, dirname): generalData['Color'].append(Info['Color']) else: generalData['NoAtoms'][Info['Symbol']] += atom[cx+3]*atom[cs+1]*Srb['Natoms'][iSh] - + if generalData['Type'] == 'magnetic': generalData['Lande g'] = landeg[:len(generalData['AtomTypes'])] @@ -948,7 +913,7 @@ def SetupGeneral(data, dirname): generalData['F000X'] = F000X generalData['F000N'] = F000N generalData['Mass'] = G2mth.getMass(generalData) - + if badList: msg = 'Warning: element symbol(s) not found:' for key in badList: @@ -956,4 +921,4 @@ def SetupGeneral(data, dirname): if badList[key] > 1: msg += ' (' + str(badList[key]) + ' times)' #wx.MessageBox(msg,caption='Element symbol error') - raise ValueError("Phase error:\n" + msg) + raise ValueError("Phase error:\n" + msg) diff --git a/GSASII/GSASIIElemGUI.py b/GSASII/GSASIIElemGUI.py index 726ba50e9..fa830b6cc 100644 --- a/GSASII/GSASIIElemGUI.py +++ b/GSASII/GSASIIElemGUI.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -'''Routines for Periodic table wx.Frame follow. +'''Routines for Periodic table wx.Frame follow. ''' from __future__ import division, print_function -import GSASIIpath +from . import GSASIIpath import wx import os import wx.lib.colourselect as wscs @@ -19,10 +19,10 @@ class PickElement(wx.Dialog): Elem=None def _init_ctrls(self,prnt,ifMag=False,ifOrbs=False): wx.Dialog.__init__(self, id=-1, name='PickElement', - parent=prnt, pos=wx.DefaultPosition, + parent=prnt, pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE, title='Pick Element') - import ElementTable as ET - self.butWid = 60 + from . import ElementTable as ET + self.butWid = 90 if 'nt' in os.name: self.butWid = 50 self.SetClientSize(wx.Size(50+18*self.butWid, 250)) @@ -54,7 +54,7 @@ def __init__(self, parent,oneOnly=False,ifNone=False,ifMag=False,ifOrbs=False,mu self.multiple = multiple self._init_ctrls(parent,ifMag=ifMag,ifOrbs=ifOrbs) self.elementList = [] - + def ElButton(self, name, pos, tip, color): 'Creates an element button widget' self.color = color @@ -84,7 +84,7 @@ def ElButton(self, name, pos, tip, color): else: El.SetForegroundColour((10,10,10)) El.Bind(wx.EVT_COMBOBOX,self.OnElButton) - + El.SetBackgroundColour(color) if 'phoenix' in wx.version(): El.SetToolTip(tip) @@ -111,7 +111,7 @@ def OnElButton(self, event): def OnClose(self,event): self.EndModal(wx.ID_OK) - + class PickElements(wx.Dialog): """Makes periodic table widget for picking elements - caller maintains element list""" Elem = [] @@ -120,7 +120,7 @@ def _init_ctrls(self, prnt,list): parent=prnt, pos=wx.DefaultPosition, size=wx.Size(580, 360), style=wx.DEFAULT_DIALOG_STYLE, title='Pick Elements') panel = wx.Panel(self) - + REcolor = wx.Colour(128, 128, 255) Metcolor = wx.Colour(192, 192, 192) Noblecolor = wx.Colour(255, 128, 255) @@ -132,7 +132,7 @@ def _init_ctrls(self, prnt,list): self.Elem = [] for El in list: self.Elem.append(El.lower().capitalize()) - + self.ElTable = [ ("H", 0,0, "Hydrogen", White, 0.0000), ("He", 17,0, "Helium", Noblecolor, 0.0000), @@ -232,10 +232,10 @@ def _init_ctrls(self, prnt,list): ("Cm", 9.5,7.5, "Curium", REcolor, 1.669), ("Bk", 10.5,7.5, "Berkelium", REcolor, 1.716), ("Cf", 11.5,7.5, "Californium", REcolor, 1.764)] - + mainSizer = wx.BoxSizer(wx.VERTICAL) elPanel = wx.Panel(panel) - + i=0 for E in self.ElTable: PickElements.ElButton(self,parent=elPanel,name=E[0], @@ -243,7 +243,7 @@ def _init_ctrls(self, prnt,list): i+=1 mainSizer.Add(elPanel,0,wx.EXPAND) mainSizer.Add((10,10),0) - + btnSizer = wx.BoxSizer(wx.HORIZONTAL) OkBtn = wx.Button(panel,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, self.OnOk) @@ -257,19 +257,19 @@ def _init_ctrls(self, prnt,list): mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM, 10) panel.SetSizer(mainSizer) panel.Fit() - + def OnOk(self,event): if self.Elem: self.EndModal(wx.ID_OK) - else: - self.EndModal(wx.ID_CANCEL) - + else: + self.EndModal(wx.ID_CANCEL) + def OnCancel(self,event): - self.EndModal(wx.ID_CANCEL) + self.EndModal(wx.ID_CANCEL) def __init__(self, parent,list): self._init_ctrls(parent,list) - + def ElButton(self, parent, name, pos, tip, color): Black = wx.Colour(0,0,0) if name in self.Elem: @@ -298,12 +298,12 @@ def OnElButton(self, event): else: btn.SetColour(wx.Colour(255,0,0)) self.Elem.append(El) - + class DeleteElement(wx.Dialog): "Delete element from selected set widget" def _init_ctrls(self, parent,choice): l = len(choice)-1 - wx.Dialog.__init__(self, id=-1, name='Delete', parent=parent, + wx.Dialog.__init__(self, id=-1, name='Delete', parent=parent, pos=wx.DefaultPosition, size=wx.Size(max(128,64+l*24), 87), style=wx.DEFAULT_DIALOG_STYLE, title='Delete Element') self.Show(True) @@ -316,7 +316,7 @@ def _init_ctrls(self, parent,choice): for Elem in choice: self.ElButton(name=Elem,pos=wxPoint(16+i*24, 16)) i+=1 - + def __init__(self, parent,choice): DeleteElement.El = ' ' self._init_ctrls(parent,choice) @@ -327,14 +327,14 @@ def ElButton(self, name, pos): El = wscs.ColourSelect(label=name, parent=self, colour = White, pos=pos, size=wx.Size(24, 23), style=wx.RAISED_BORDER) El.Bind(wx.EVT_BUTTON, self.OnDeleteButton) - + def OnDeleteButton(self, event): DeleteElement.El=event.GetEventObject().GetLabel() self.EndModal(wx.ID_OK) - + def GetDeleteElement(self): return DeleteElement.El - + if __name__ == '__main__': app = wx.PySimpleApp() GSASIIpath.InvokeDebugOpts() @@ -345,4 +345,3 @@ def GetDeleteElement(self): if PE.ShowModal() == wx.ID_OK: print (PE.Elem) PE.Destroy() - diff --git a/GSASII/GSASIIGUI.py b/GSASII/GSASIIGUI.py new file mode 100755 index 000000000..22d73040f --- /dev/null +++ b/GSASII/GSASIIGUI.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +#GSASIIGUI +''' +GSASIIGUI provides a short file that is used to start the GSAS-II GUI. +It is usually called from `G2.py` but this routine can also be invoked +directly when GSAS-II has been installed into Python. +On MacOS, a class, :class:`G2App`, is defined inside :func:`main` that creates +a wxPython application. For other platforms ``wx.App()`` is called directly. +''' + +import sys + +from . import GSASIIpath + +__version__ = '5.0.0' +gv = GSASIIpath.getSavedVersionInfo() +if gv is not None: + if len(gv.git_tags): + __version__ = gv.git_tags[0] + elif len(gv.git_prevtags): + __version__ = gv.git_prevtags[0] + +def main(): + '''This routine is called to start the GSAS-II GUI + ''' + import scipy.optimize # loading here addresses problem with build for wx on Pi + import wx + # the next line removes the need for pythonw. Thanks to Matt Newville! + # appears unneeded from wx 4.2.1 on + if sys.platform.lower() == 'darwin': + wx.PyApp.IsDisplayAvailable = lambda _: True + # importing the following wx modules at the same time as wx seems to + # eliminate the "Debug: Adding duplicate image handler for 'Windows bitmap + # file'" error message + import wx.grid as wg + import wx.aui + import wx.lib.scrolledpanel as wxscroll + import wx.lib.mixins.listctrl as listmix + import wx.richtext as wxrt + import wx.lib.filebrowsebutton as wxfilebrowse + wg,wx.aui,wxscroll,listmix,wxrt,wxfilebrowse,scipy.optimize # avoid unused warning + + if sys.platform == "darwin": + class G2App(wx.App): + '''Used to create a wx python application for the GUI for Mac. + Customized to implement drop of GPX files onto app. + ''' + startupMode = True + def ClearStartup(self): + '''Call this after app startup complete because a Drop event is posted + when GSAS-II is initially started. + ''' + self.startupMode = False + def MacOpenFiles(self, filenames): + if self.startupMode: + return + from . import GSASIIfiles + for project in filenames: + #print("Start GSAS-II with project file "+str(project)) + #GSASIIpath.MacStartGSASII(__file__,project) + GSASIIfiles.openInNewTerm(project) + + application = G2App(0) # create the GUI framework + else: + application = wx.App(0) # create the GUI framework + try: + GSASIIpath.SetBinaryPath(True) + except: + print('Unable to run with current installation, please reset or reinstall') + # if GSASIIpath.HowIsG2Installed().startswith('git'): + # print('use this command w/gitstrap') + sys.exit() + # print('Unable to run with current setup, do you want to update to the') + # try: + # ans = input("latest GSAS-II version? Update ([Yes]/no): ") + # except: + # ans = 'no' + # if ans.strip().lower() == "no": + # import sys + # print('Exiting') + # sys.exit() + # print('Updating...') + # import GSASIIctrlGUI as G2G + # if GSASIIpath.HowIsG2Installed().startswith('git'): + # gitCheckUpdates(None) + from . import GSASIIdataGUI as G2gd + G2gd.GSASIImain(application) # start the GUI + if sys.platform == "darwin": + wx.CallLater(50,application.ClearStartup) + GSASIIpath.InvokeDebugOpts() + application.MainLoop() + +if __name__ == '__main__': + main() diff --git a/GSASII/GSASIIIntPDFtool.py b/GSASII/GSASIIIntPDFtool.py index 1cf856314..2abf33d91 100644 --- a/GSASII/GSASIIIntPDFtool.py +++ b/GSASII/GSASIIIntPDFtool.py @@ -18,13 +18,13 @@ import wx.lib.mixins.listctrl as listmix import wx.grid as wg import numpy as np -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath(True) -import GSASIIctrlGUI as G2G -import GSASIIobj as G2obj -import GSASIIimgGUI as G2imG -import GSASIIfiles as G2fil -import GSASIIscriptable as G2sc +from . import GSASIIctrlGUI as G2G +from . import GSASIIobj as G2obj +from . import GSASIIimgGUI as G2imG +from . import GSASIIfiles as G2fil +from . import GSASIIscriptable as G2sc import multiprocessing as mp try: # fails during doc build diff --git a/GSASII/GSASIIconstrGUI.py b/GSASII/GSASIIconstrGUI.py index e0d91562f..199e49175 100644 --- a/GSASII/GSASIIconstrGUI.py +++ b/GSASII/GSASIIconstrGUI.py @@ -17,21 +17,21 @@ import numpy as np import numpy.ma as ma import numpy.linalg as nl -import GSASIIpath -import GSASIIElem as G2elem -import GSASIIElemGUI as G2elemGUI -import GSASIIstrIO as G2stIO -import GSASIImapvars as G2mv -import GSASIImath as G2mth -import GSASIIlattice as G2lat -import GSASIIdataGUI as G2gd -import GSASIIctrlGUI as G2G -import GSASIIfiles as G2fil -import GSASIIplot as G2plt -import GSASIIobj as G2obj -import GSASIIspc as G2spc -import GSASIIphsGUI as G2phG -import GSASIIscriptable as G2sc +from . import GSASIIpath +from . import GSASIIElem as G2elem +from . import GSASIIElemGUI as G2elemGUI +from . import GSASIIstrIO as G2stIO +from . import GSASIImapvars as G2mv +from . import GSASIImath as G2mth +from . import GSASIIlattice as G2lat +from . import GSASIIdataGUI as G2gd +from . import GSASIIctrlGUI as G2G +from . import GSASIIfiles as G2fil +from . import GSASIIplot as G2plt +from . import GSASIIobj as G2obj +from . import GSASIIspc as G2spc +from . import GSASIIphsGUI as G2phG +from . import GSASIIscriptable as G2sc VERY_LIGHT_GREY = wx.Colour(235,235,235) WACV = wx.ALIGN_CENTER_VERTICAL @@ -3492,7 +3492,7 @@ def OnSymSel(event): bodSizer.Add(G2G.ValidatedTxtCtrl(SpinRBDisplay,data['Spin'][spinID],'RBname')) bodSizer.Add(wx.StaticText(SpinRBDisplay,label='Q'),0) data['Spin'][spinID]['rbType'] = 'Q' #patch - symchoice = ['53m','m3m','-43m','6/mmm','-6m2','-3m','3m','32','4/mmm','-42m','mmm','2/m','-1','1'] + symchoice = ['53m','m3m','-43m','6/mmm','-6m2','-3m','3m','32','3','4/mmm','-42m','mmm','2/m','2','m','-1','1'] data['Spin'][spinID]['RBsym'] = data['Spin'][spinID].get('RBsym','53m') simsel = wx.ComboBox(SpinRBDisplay,choices=symchoice,value=data['Spin'][spinID]['RBsym'], style=wx.CB_READONLY|wx.CB_DROPDOWN) diff --git a/GSASII/GSASIIctrlGUI.py b/GSASII/GSASIIctrlGUI.py index 6a99217d2..8dd22e11c 100644 --- a/GSASII/GSASIIctrlGUI.py +++ b/GSASII/GSASIIctrlGUI.py @@ -23,7 +23,7 @@ except ImportError: print('ImportError for wx/mpl in GSASIIctrlGUI: ignore if docs build') - + import time import glob import copy @@ -37,20 +37,19 @@ except ImportError: from matplotlib.backends.backend_wx import FigureCanvas as Canvas -import GSASIIpath -import GSASIIdataGUI as G2gd -import GSASIIpwdGUI as G2pdG -import GSASIIspc as G2spc -import GSASIIobj as G2obj -import GSASIIfiles as G2fil -import GSASIIElem as G2elem -import GSASIIpwd as G2pwd -import GSASIIlattice as G2lat -import GSASIImath as G2mth -import GSASIIstrMain as G2stMn -import GSASIImiscGUI as G2IO -import config_example -from tutorialIndex import tutorialIndex +from . import GSASIIpath +from . import GSASIIdataGUI as G2gd +from . import GSASIIpwdGUI as G2pdG +from . import GSASIIspc as G2spc +from . import GSASIIobj as G2obj +from . import GSASIIfiles as G2fil +from . import GSASIIElem as G2elem +from . import GSASIIpwd as G2pwd +from . import GSASIIlattice as G2lat +from . import GSASIImath as G2mth +from . import GSASIIstrMain as G2stMn +from . import GSASIImiscGUI as G2IO +from .tutorialIndex import tutorialIndex if sys.version_info[0] >= 3: unicode = str basestring = str @@ -77,7 +76,7 @@ #### Fixed definitions for wx Ids ################################################################################ def Define_wxId(*args): - '''routine to create unique global wx Id symbols in this module. + '''routine to create unique global wx Id symbols in this module. ''' for arg in args: if GSASIIpath.GetConfigValue('debug') and not arg.startswith('wxID_'): @@ -91,7 +90,7 @@ def Define_wxId(*args): #### Tree Control ################################################################################ class G2TreeCtrl(wx.TreeCtrl): '''Create a wrapper around the standard TreeCtrl so we can "wrap" - various events. + various events. ''' def __init__(self,parent=None,*args,**kwargs): super(self.__class__,self).__init__(parent=parent,*args,**kwargs) @@ -124,7 +123,7 @@ def _getTreeItemsList(self,item): textlist.insert(0,self.GetItemText(parent)) parent = self.GetItemParent(parent) return textlist - + def GetItemPyData(self,treeId): return wx.TreeCtrl.GetItemData(self,treeId) @@ -169,7 +168,7 @@ def ConvertRelativeHistNum(self,histtype,histnum): item, cookie = self.GetNextChild(self.root, cookie) else: raise Exception("Histogram #'+str(histnum)+' of type "+histtype+' not found') - + def GetRelativePhaseNum(self,phasename): '''Returns a phase number if the string matches a phase name or else returns the original string @@ -220,7 +219,7 @@ def GetImageLoc(self,TreeId): image name is specified, as well as where the image file name is a tuple containing the image file and an image number ''' - + size,imagefile = self.GetItemPyData(TreeId) if type(imagefile) is tuple or type(imagefile) is list: return size,imagefile[0],imagefile[1] @@ -232,14 +231,14 @@ def UpdateImageLoc(self,TreeId,imagefile): image name is specified, as well as where the image file name is a tuple containing the image file and an image number ''' - + idata = self.GetItemPyData(TreeId) if type(idata[1]) is tuple or type(idata[1]) is list: idata[1] = list(idata[1]) idata[1][0] = [imagefile,idata[1][1]] else: idata[1] = imagefile - + def SaveExposedItems(self): '''Traverse the top level tree items and save names of exposed (expanded) tree items. Done before a refinement. @@ -273,7 +272,7 @@ def ReadOnlyTextCtrl(*args,**kwargs): Txt = wx.TextCtrl(*args,**kwargs) Txt.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) return Txt - + #### TextCtrl that stores input as entered with optional validation ################################################################################ class ValidatedTxtCtrl(wx.TextCtrl): '''Create a TextCtrl widget that uses a validator to prevent the @@ -283,20 +282,20 @@ class ValidatedTxtCtrl(wx.TextCtrl): came from. The type of the initial value must be int, float or str or None (see :obj:`key` and :obj:`typeHint`); this type (or the one in :obj:`typeHint`) is preserved. - Values are processed and saved when Enter is pressed, when the - mouse is moved to another control (leave window or focus is lost) + Values are processed and saved when Enter is pressed, when the + mouse is moved to another control (leave window or focus is lost) or after a change and a delay of two seconds. Float values can be entered in the TextCtrl as numbers or also as algebraic expressions using operators (+ - / \\* () and \\*\\*), in addition pi, sind(), cosd(), tand(), and sqrt() can be used, - as well as appreviations s, sin, c, cos, t, tan and sq. + as well as appreviations s, sin, c, cos, t, tan and sq. :param wx.Panel parent: name of panel or frame that will be the parent to the TextCtrl. Can be None. :param dict/list loc: the dict or list with the initial value to be - placed in the TextCtrl. + placed in the TextCtrl. :param int/str key: the dict key or the list index for the value to be edited by the TextCtrl. The ``loc[key]`` element must exist, but may @@ -304,22 +303,22 @@ class ValidatedTxtCtrl(wx.TextCtrl): :obj:`typeHint` and the value for the control is set initially blank (and thus invalid.) This is a way to specify a field without a default value: a user must set a valid value. - + If the value is not None, it must have a base type of int, float, str or unicode; the TextCrtl will be initialized from this value. - - :param list nDig: number of digits, places and optionally the format - ([nDig,nPlc,fmt]) after decimal to use for display of float. The format - is either 'f' (default) or 'g'. Alternately, None can be specified which + + :param list nDig: number of digits, places and optionally the format + ([nDig,nPlc,fmt]) after decimal to use for display of float. The format + is either 'f' (default) or 'g'. Alternately, None can be specified which causes numbers to be displayed with approximately 5 significant figures for floats. If this is specified, then :obj:`typeHint` = float becomes the - default. + default. (Default=None). :param bool notBlank: if True (default) blank values are invalid for str inputs. - + :param number xmin: minimum allowed valid value. If None (default) the lower limit is unbounded. NB: test in NumberValidator is val >= xmin not val > xmin @@ -327,9 +326,9 @@ class ValidatedTxtCtrl(wx.TextCtrl): :param number xmax: maximum allowed valid value. If None (default) the upper limit is unbounded NB: test in NumberValidator is val <= xmax not val < xmax - - :param list exclLim: if True exclude min/max value ([exclMin,exclMax]); - (Default=[False,False]) + + :param list exclLim: if True exclude min/max value ([exclMin,exclMax]); + (Default=[False,False]) :param function OKcontrol: specifies a function or method that will be called when the input is validated. The called function is supplied @@ -397,7 +396,7 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None, self.timer = None # tracks pending updates for expressions in float textctrls self.delay = 2000 # delay for timer update (2 sec) self.type = str - + val = loc[key] if 'style' in kw: # add a "Process Enter" to style kw['style'] |= wx.TE_PROCESS_ENTER @@ -421,7 +420,7 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None, else: raise Exception("ValidatedTxtCtrl error: Unknown element ("+str(key)+ ") type: "+str(type(val))) - if self.type is int: + if self.type is int: wx.TextCtrl.__init__(self,parent,wx.ID_ANY, validator=NumberValidator(int,result=loc,key=key,xmin=xmin,xmax=xmax, exclLim=exclLim,OKcontrol=OKcontrol,CIFinput=CIFinput),**kw) @@ -455,7 +454,7 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None, else: self.invalid = False self.Bind(wx.EVT_CHAR,self._GetStringValue) - + # When the mouse is moved away or the widget loses focus, # display the last saved value, if an expression self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow) @@ -463,10 +462,10 @@ def __init__(self,parent,loc,key,nDig=None,notBlank=True,xmin=None,xmax=None, self.Bind(wx.EVT_TEXT_ENTER, self._onLoseFocus) def SetValue(self,val,warn=True): - '''Place a value into the text widget and save it into the + '''Place a value into the text widget and save it into the associated array element. Note that, unlike the stock wx.TextCtrl, - val is expected to be in the form expected by the widget - (float/int/str) rather than only str's. + val is expected to be in the form expected by the widget + (float/int/str) rather than only str's. For float val values, the value is formatted when placed in the TextCtrl, but the supplied value is what is actually saved. @@ -485,10 +484,10 @@ def SetValue(self,val,warn=True): wx.TextCtrl.SetValue(self,wx.TextCtrl.GetValue(self)) def ChangeValue(self,val): - '''Place a value into the text widget and save it into the + '''Place a value into the text widget and save it into the associated array element. Note that, unlike the stock wx.TextCtrl, - val is expected to be in the form expected by the widget - (float/int/str) rather than only str's. + val is expected to be in the form expected by the widget + (float/int/str) rather than only str's. For float val values, the value is formatted when placed in the TextCtrl, but the supplied value is what is actually saved. @@ -497,7 +496,7 @@ def ChangeValue(self,val): ''' if self.result is not None: self.result[self.key] = val - self._setValue(val) + self._setValue(val) def _setValue(self,val,show=True): '''Check the validity of an int or float value and convert to a str. @@ -550,7 +549,7 @@ def _setValue(self,val,show=True): wx.TextCtrl.ChangeValue(self,val) self.ShowStringValidity() # test if valid input return - + self._IndicateValidity() if self.OKcontrol: self.OKcontrol(not self.invalid) @@ -565,7 +564,7 @@ def OnKeyDown(self,event): if event: event.Skip() if self.timer: self.timer.Restart(self.delay) - + def _onStringKey(self,event): if event: event.Skip() if self.invalid: # check for validity after processing the keystroke @@ -583,7 +582,7 @@ def _IndicateValidity(self): self.SetFocus() self.Refresh() # this selects text on some Linuxes self.SetSelection(0,0) # unselect - self.SetInsertionPoint(ins) # put insertion point back + self.SetInsertionPoint(ins) # put insertion point back else: # valid input self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) self.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)) @@ -605,16 +604,16 @@ def _GetNumValue(self): else: self.invalid = True return val - + def ShowStringValidity(self,previousInvalid=True): '''Check if input is valid. Anytime the input is invalid, call self.OKcontrol (if defined) because it is fast. If valid, check for any other invalid entries only when changing from invalid to valid, since that is slower. - + :param bool previousInvalid: True if the TextCtrl contents were invalid prior to the current change. - + ''' val = self.GetValue().strip() if self.notBlank: @@ -634,7 +633,7 @@ def _GetStringValue(self,event): ''' if event: event.Skip() # process keystroke wx.CallAfter(self._SaveStringValue) - + def _SaveStringValue(self): try: val = self.GetValue().strip() @@ -642,13 +641,13 @@ def _SaveStringValue(self): return # always store the result if self.CIFinput and '2' in platform.python_version_tuple()[0]: # Py2/CIF make results ASCII - self.result[self.key] = val.encode('ascii','replace') + self.result[self.key] = val.encode('ascii','replace') else: self.result[self.key] = val def _onLeaveWindow(self,event): '''If the mouse leaves the text box, save the result, if valid, - but (unlike _onLoseFocus) there is a two second delay before + but (unlike _onLoseFocus) there is a two second delay before the textbox contents are updated with the value from the formula. ''' def delayedUpdate(): @@ -659,7 +658,7 @@ def delayedUpdate(): pass if self.type is not str: if not self.IsModified(): return #ignore mouse crusing - elif self.result[self.key] == self.GetValue(): # .IsModified() seems unreliable for str + elif self.result[self.key] == self.GetValue(): # .IsModified() seems unreliable for str return if self.evaluated and not self.invalid: # deal with computed expressions if self.timer: @@ -675,7 +674,7 @@ def delayedUpdate(): self.OnLeave(invalid=self.invalid,value=self.result[self.key], tc=self,**self.OnLeaveArgs) if event: event.Skip() - + def _onLoseFocus(self,event): '''Enter has been pressed or focus transferred to another control, Evaluate and update the current control contents @@ -683,17 +682,17 @@ def _onLoseFocus(self,event): if event: event.Skip() if self.type is not str: if not self.IsModified(): return #ignore mouse crusing - elif self.result[self.key] == self.GetValue(): # .IsModified() seems unreliable for str + elif self.result[self.key] == self.GetValue(): # .IsModified() seems unreliable for str return if self.evaluated: # deal with computed expressions if self.invalid: # don't substitute for an invalid expression - return + return self._setValue(self.result[self.key]) elif self.result is not None: # show formatted result, as Bob wants self.result[self.key] = self._GetNumValue() if not self.invalid: # don't update an invalid expression self._setValue(self.result[self.key]) - + if self.OnLeave: self.event = event try: @@ -706,7 +705,7 @@ class NumberValidator(wxValidator): '''A validator to be used with a TextCtrl to prevent entering characters other than digits, signs, and for float input, a period and exponents. - + The value is checked for validity after every keystroke If an invalid number is entered, the box is highlighted. If the number is valid, it is saved in result[key] @@ -722,21 +721,21 @@ class NumberValidator(wxValidator): :param number xmax: Maximum allowed value. If None (default) the upper limit is unbounded - - :param list exclLim: if True exclude xmin/xmax value ([exclMin,exclMax]); - (Default=[False,False]) + + :param list exclLim: if True exclude xmin/xmax value ([exclMin,exclMax]); + (Default=[False,False]) :param dict/list result: List or dict where value should be placed when valid :param any key: key to use for result (int for list) :param function OKcontrol: function or class method to control - an OK button for a window. + an OK button for a window. Ignored if None (default) :param bool CIFinput: allows use of a single '?' or '.' character as valid input. - + ''' def __init__(self, typ, positiveonly=False, xmin=None, xmax=None,exclLim=[False,False], result=None, key=None, OKcontrol=None, CIFinput=False): @@ -757,11 +756,11 @@ def __init__(self, typ, positiveonly=False, xmin=None, xmax=None,exclLim=[False, if self.typ == int and self.positiveonly: self.validchars = '0123456789' elif self.typ == int: - self.validchars = '0123456789+-' + self.validchars = '0123456789+-%' elif self.typ == float: # allow for above and sind, cosd, sqrt, tand, pi, and abbreviations # also addition, subtraction, division, multiplication, exponentiation - self.validchars = '0123456789.-+eE/cosindcqrtap()*,' + self.validchars = '0123456789.-+eE/cosindcqrtap()*,%' else: self.validchars = None return @@ -769,7 +768,7 @@ def __init__(self, typ, positiveonly=False, xmin=None, xmax=None,exclLim=[False, self.validchars += '?.' def Clone(self): 'Create a copy of the validator, a strange, but required component' - return NumberValidator(typ=self.typ, + return NumberValidator(typ=self.typ, positiveonly=self.positiveonly, xmin=self.xmin, xmax=self.xmax, result=self.result, key=self.key, @@ -788,7 +787,7 @@ def TestValid(self,tc): Set the invalid variable in the TextCtrl object accordingly. If the value is valid, save it in the dict/list where - the initial value was stored, if appropriate. + the initial value was stored, if appropriate. :param wx.TextCtrl tc: A reference to the TextCtrl that the validator is associated with. @@ -809,7 +808,7 @@ def TestValid(self,tc): return else: tc.evaluated = True - else: + else: tc.invalid = True return if self.xmax != None: @@ -839,7 +838,7 @@ def ShowValidity(self,tc): tc.SetFocus() tc.Refresh() # this selects text on some Linuxes tc.SetSelection(0,0) # unselect - tc.SetInsertionPoint(ins) # put insertion point back + tc.SetInsertionPoint(ins) # put insertion point back return False else: # valid input tc.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) @@ -852,7 +851,7 @@ def CheckInput(self,previousInvalid): to change the appearance of the TextCtrl Anytime the input is invalid, call self.OKcontrol - (if defined) because it is fast. + (if defined) because it is fast. If valid, check for any other invalid entries only when changing from invalid to valid, since that is slower. @@ -876,24 +875,24 @@ def OnChar(self, event): tc = self.GetWindow() if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER: if tc.invalid: - self.CheckInput(True) + self.CheckInput(True) else: - self.CheckInput(False) + self.CheckInput(False) if event: event.Skip() return if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: # control characters get processed if event: event.Skip() if tc.invalid: - wx.CallAfter(self.CheckInput,True) + wx.CallAfter(self.CheckInput,True) else: - wx.CallAfter(self.CheckInput,False) + wx.CallAfter(self.CheckInput,False) return elif chr(key) in self.validchars: # valid char pressed? if event: event.Skip() if tc.invalid: - wx.CallAfter(self.CheckInput,True) + wx.CallAfter(self.CheckInput,True) else: - wx.CallAfter(self.CheckInput,False) + wx.CallAfter(self.CheckInput,False) return return # Returning without calling event.Skip, which eats the keystroke @@ -901,7 +900,7 @@ def OnChar(self, event): class ASCIIValidator(wxValidator): '''A validator to be used with a TextCtrl to prevent entering characters other than ASCII characters. - + The value is checked for validity after every keystroke If an invalid number is entered, the box is highlighted. If the number is valid, it is saved in result[key] @@ -933,7 +932,7 @@ def TransferFromWindow(self): return True # Prevent wxDialog from complaining. def TestValid(self,tc): '''Check if the value is valid by casting the input string - into ASCII. + into ASCII. Save it in the dict/list where the initial value was stored @@ -970,14 +969,14 @@ class G2Slider(wx.Slider): Also casts floats as integers to avoid py3.10+ errors ''' global ci - ci = lambda x: int(x + 0.5) # closest integer + ci = lambda x: int(x + 0.5) # closest integer def __init__(self, parent, id=wx.ID_ANY, value=0, minValue=0, maxValue=100, *arg, **kwarg): wx.Slider.__init__(self, parent, id, ci(value), ci(minValue), ci(maxValue), *arg, **kwarg) self.iscale = 1 - + def SetScaling(self,iscale): self.iscale = iscale - + def SetScaledRange(self,xmin,xmax): self.SetRange(ci(xmin*self.iscale),ci(xmax*self.iscale)) @@ -986,19 +985,19 @@ def SetScaledValue(self,value): def GetScaledValue(self): return self.GetValue()/float(self.iscale) - + def SetValue(self,value): wx.Slider.SetValue(self, ci(value)) - + def SetMax(self,xmax): wx.Slider.SetMax(self,ci(xmax*self.iscale)) - + def SetMin(self,xmin): wx.Slider.SetMin(self,ci(xmin*self.iscale)) def G2SliderWidget(parent,loc,key,label,xmin,xmax,iscale, onChange=None,onChangeArgs=[],sizer=None,nDig=None,size=(50,20)): - '''A customized combination of a wx.Slider and a validated + '''A customized combination of a wx.Slider and a validated wx.TextCtrl (see :class:`ValidatedTxtCtrl`) that allows either a slider or text entry to set a value within a range. @@ -1009,25 +1008,25 @@ def G2SliderWidget(parent,loc,key,label,xmin,xmax,iscale, placed in the TextCtrl. :param int/str key: the dict key or the list index for the value to be - edited by the TextCtrl. The ``loc[key]`` element must exist and should - have a float value. It will be forced to an initial value + edited by the TextCtrl. The ``loc[key]`` element must exist and should + have a float value. It will be forced to an initial value between xmin and xmax. - - :param str label: A label to be placed to the left of the slider. - :param float xmin: the minimum allowed valid value. + :param str label: A label to be placed to the left of the slider. + + :param float xmin: the minimum allowed valid value. :param float xmax: the maximum allowed valid value. - :param float iscale: number to scale values to integers, which is what the + :param float iscale: number to scale values to integers, which is what the Scale widget uses. If the xmin=1 and xmax=4 and iscale=1 then values - only the values 1,2,3 and 4 can be set with the slider. However, + only the values 1,2,3 and 4 can be set with the slider. However, if iscale=2 then the values 1, 1.5, 2, 2.5, 3, 3.5 and 4 are all allowed. :param callable onChange: function to call when value is changed. - Default is None where nothing will be called. - - :param list onChangeArgs: arguments to be passed to onChange function + Default is None where nothing will be called. + + :param list onChangeArgs: arguments to be passed to onChange function when called. :returns: returns a wx.BoxSizer containing the widgets ''' @@ -1062,10 +1061,10 @@ def onValSet(*args,**kwargs): hSizer.Add(vEntry,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5) hSizer.Add(vScale,0,wx.ALL|wx.ALIGN_CENTER_VERTICAL) return vEntry,vScale - + def G2SpinWidget(parent,loc,key,label,xmin=None,xmax=None, onChange=None,onChangeArgs=[],hsize=35): - '''A customized combination of a wx.SpinButton and a validated + '''A customized combination of a wx.SpinButton and a validated wx.TextCtrl (see :class:`ValidatedTxtCtrl`) that allows either a the spin button or text entry to set a value within a range. @@ -1076,20 +1075,20 @@ def G2SpinWidget(parent,loc,key,label,xmin=None,xmax=None, placed in the TextCtrl. :param int/str key: the dict key or the list index for the value to be - edited by the TextCtrl. The ``loc[key]`` element must exist and should - have a float or int value. It will be forced to an integer initial value + edited by the TextCtrl. The ``loc[key]`` element must exist and should + have a float or int value. It will be forced to an integer initial value between xmin and xmax. - - :param str label: A label to be placed to the left of the entry widget. + + :param str label: A label to be placed to the left of the entry widget. :param int xmin: the minimum allowed valid value. If None it is ignored. :param int xmax: the maximum allowed valid value. If None it is ignored. :param callable onChange: function to call when value is changed. - Default is None where nothing will be called. - - :param list onChangeArgs: arguments to be passed to onChange function + Default is None where nothing will be called. + + :param list onChangeArgs: arguments to be passed to onChange function when called. :param int hsize: length of TextCtrl in pixels. Defaults to 35. @@ -1114,7 +1113,7 @@ def _onValSet(*args,**kwargs): if xmax is not None: loc[key] = min(xmax,loc[key]) hSizer = wx.BoxSizer(wx.HORIZONTAL) - if label: + if label: hSizer.Add(wx.StaticText(parent,wx.ID_ANY,label),0, wx.ALL|wx.ALIGN_CENTER_VERTICAL) spin = wx.SpinButton(parent,style=wx.SP_VERTICAL,size=wx.Size(20,20)) @@ -1131,7 +1130,7 @@ def _onValSet(*args,**kwargs): def HorizontalLine(sizer,parent): '''Draws a horizontal line as wide as the window. ''' - if sys.platform == "darwin": + if sys.platform == "darwin": #sizer.Add((-1,2)) line = wx.Panel(parent, size=(-1, 2)) #line.SetBackgroundColour('red') @@ -1161,7 +1160,7 @@ def __init__(self,parent,id=wx.ID_ANY,label='',locationcode='', def onPress(self,event): 'create log event and call handler' self.handler(event) - + ################################################################################ class EnumSelector(wx.ComboBox): '''A customized :class:`wxpython.ComboBox` that selects items from a list @@ -1172,30 +1171,30 @@ class EnumSelector(wx.ComboBox): frame or panel) :param dct: a dict or list to contain the value set for the :class:`~wxpython.ComboBox`. - :param item: the dict key (or list index) where ``dct[item]`` will + :param item: the dict key (or list index) where ``dct[item]`` will be set to the value selected in the :class:`~wxpython.ComboBox`. Also, dct[item] contains the starting value shown in the widget. If the value does not match an entry in :data:`values`, the first value in :data:`choices` is used as the default, but ``dct[item]`` is - not changed. + not changed. :param list choices: a list of choices to be displayed to the user such as :: - + ["default","option 1","option 2",] - Note that these options will correspond to the entries in - :data:`values` (if specified) item by item. + Note that these options will correspond to the entries in + :data:`values` (if specified) item by item. :param list values: a list of values that correspond to the options in :data:`choices`, such as :: - + [0,1,2] - + The default for :data:`values` is to use the same list as specified for :data:`choices`. :param function OnChange: an optional routine that will be called - when the + when the :class:`~wxpython.ComboBox` can be specified. :param (other): additional keyword arguments accepted by :class:`~wxpython.ComboBox` can be specified. @@ -1228,27 +1227,27 @@ def onSelection(self,event): ################################################################################ class popupSelectorButton(wx.Button): - '''Create a button that will invoke a menu with choices that can + '''Create a button that will invoke a menu with choices that can be selected. Do special stuff if the first item is "all" TODO: It might be better to make this a wx.ComboCtrl if I can figure out how to make that work, or perhaps make that an option - :param wx.Frame parent: a panel or frame that is the parent to this + :param wx.Frame parent: a panel or frame that is the parent to this button :param str lbl: a label for the button :param list choices: a list of str's with labels for the items in the menu :param list selected: a list of bool's that determine if the menu item is initial selected - :param: dict choiceDict: a dict with both choices and their - values (selections). Use this or choices & selected, not both. - If this is used, the values are set as radiobutton choices, - only the most recent setting is selected. - :param function OnChange: an optional function that is called after the + :param: dict choiceDict: a dict with both choices and their + values (selections). Use this or choices & selected, not both. + If this is used, the values are set as radiobutton choices, + only the most recent setting is selected. + :param function OnChange: an optional function that is called after the menu is removed - :param others: other keyword parameters are allowed. They will be - passed to the OnChange routine. + :param others: other keyword parameters are allowed. They will be + passed to the OnChange routine. ''' def __init__(self,parent,lbl,choices=None,selected=None, choiceDict=None, OnChange=None,**kw): @@ -1264,10 +1263,10 @@ def __init__(self,parent,lbl,choices=None,selected=None, self.kw = kw wx.Button.__init__(self,parent,label=lbl) self.Bind(wx.EVT_BUTTON, self.popupSelector) - + def popupSelector(self,event): - '''Show the menu and then get current values. Optionally call the - OnChange routine. + '''Show the menu and then get current values. Optionally call the + OnChange routine. ''' menu = wx.Menu() menuList = [] @@ -1292,7 +1291,7 @@ def popupSelector(self,event): # when all is selected. if new == 0: self.selected[:] = [False] + (len(self.selected)-1)*[True] - + menu.Destroy() if self.OnChange: wx.CallAfter(self.OnChange,**self.kw) @@ -1301,16 +1300,16 @@ class G2ChoiceButton(wx.Choice): '''A customized version of a wx.Choice that automatically initializes the control to match a supplied value and saves the choice directly into an array or list. Optionally a function can be called each time a - choice is selected. The widget can be used with an array item that is set to + choice is selected. The widget can be used with an array item that is set to to the choice by number (``indLoc[indKey]``) or by string value (``strLoc[strKey]``) or both. The initial value is taken from ``indLoc[indKey]`` - if not None or ``strLoc[strKey]`` if not None. + if not None or ``strLoc[strKey]`` if not None. :param wx.Panel parent: name of panel or frame that will be the parent to the widget. Can be None. :param list choiceList: a list or tuple of choices to offer the user. :param dict/list indLoc: a dict or list with the initial value to be - placed in the Choice button. If this is None, this is ignored. + placed in the Choice button. If this is None, this is ignored. :param int/str indKey: the dict key or the list index for the value to be edited by the Choice button. If indLoc is not None then this must be specified and the ``indLoc[indKey]`` will be set. If the value @@ -1318,8 +1317,8 @@ class G2ChoiceButton(wx.Choice): range(len(choiceList)). The Choice button will be initialized to the choice corresponding to the value in this element if not None. :param dict/list strLoc: a dict or list with the string value corresponding to - indLoc/indKey. Default (None) means that this is not used. - :param int/str strKey: the dict key or the list index for the string value + indLoc/indKey. Default (None) means that this is not used. + :param int/str strKey: the dict key or the list index for the string value The ``strLoc[strKey]`` element must exist or strLoc must be None (default). :param function onChoice: name of a function to call when the choice is made. @@ -1331,7 +1330,7 @@ class G2ChoiceButton(wx.Choice): data,'Orientation', onChoice=replot) - This will show "Vertical" as the initial value, and based on what is + This will show "Vertical" as the initial value, and based on what is selected, ``data['Orientation']`` will be set to 0 or 1 (as an int value). Example 2:: @@ -1344,7 +1343,7 @@ class G2ChoiceButton(wx.Choice): onChoice=replot) This will show "8" as the initial value, and based on what is selected, - ``data[0]['Font']`` will be set to a string with one of the size + ``data[0]['Font']`` will be set to a string with one of the size options ("6"... "16"). ''' def __init__(self,parent,choiceList,indLoc=None,indKey=None,strLoc=None,strKey=None, @@ -1397,14 +1396,14 @@ class G2CheckBox(wx.CheckBox): the parent to the widget. Can be None. :param str label: text to put on check button :param dict/list loc: the dict or list with the initial value to be - placed in the CheckBox. + placed in the CheckBox. :param int/str key: the dict key or the list index for the value to be edited by the CheckBox. The ``loc[key]`` element must exist. The CheckBox will be initialized from this value. If the value is anything other that True (or 1), it will be taken as False. :param function OnChange: specifies a function or method that will be - called when the CheckBox is changed (Default is None). + called when the CheckBox is changed (Default is None). The called function is supplied with one argument, the calling event. ''' def __init__(self,parent,label,loc,key,OnChange=None): @@ -1421,9 +1420,9 @@ def _OnCheckBox(self,event): def G2CheckBoxFrontLbl(parent,label,loc,key,OnChange=None): '''A customized version of a CheckBox that automatically initializes the control to a supplied list or dict entry and updates that - entry as the widget is used. Same as :class:`G2CheckBox` except the + entry as the widget is used. Same as :class:`G2CheckBox` except the label is placed before the CheckBox and returns a sizer rather than the - G2CheckBox. + G2CheckBox. If the CheckBox is needed, reference Sizer.myCheckBox. ''' @@ -1433,22 +1432,22 @@ def G2CheckBoxFrontLbl(parent,label,loc,key,OnChange=None): Sizer.Add(checkBox,0,WACV) Sizer.myCheckBox = checkBox return Sizer - + def G2RadioButtons(parent,loc,key,choices,values=None,OnChange=None): - '''A customized version of wx.RadioButton that returns a list + '''A customized version of wx.RadioButton that returns a list of coupled RadioButtons :param wx.Panel parent: name of panel or frame that will be the parent to the widgets. Can be None. :param dict/list loc: the dict or list with the initial value to be - placed in the CheckBox. + placed in the CheckBox. :param int/str key: the dict key or the list index for the value to be edited by the CheckBox. The ``loc[key]`` element must exist. The CheckButton will be initialized from this value. :param list choices: :param list values: :param function OnChange: specifies a function or method that will be - called when the CheckBox is changed (Default is None). + called when the CheckBox is changed (Default is None). The called function is supplied with one argument, the calling event. ''' def _OnEvent(event): @@ -1473,7 +1472,7 @@ def _OnEvent(event): def CallScrolledMultiEditor(parent,dictlst,elemlst,prelbl=[],postlbl=[], title='Edit items',header='',size=(300,250), CopyButton=False, ASCIIonly=False, **kw): - '''Shell routine to call a ScrolledMultiEditor dialog. See + '''Wrapper routine to call a ScrolledMultiEditor dialog. See :class:`ScrolledMultiEditor` for parameter definitions. :returns: True if the OK button is pressed; False if the window is closed @@ -1499,57 +1498,57 @@ class ScrolledMultiEditor(wx.Dialog): are provided, the TextCtrl is turned yellow and the OK button is disabled. The type for each TextCtrl validation is determined by the - initial value of the entry (int, float or string). + initial value of the entry (int, float or string). Float values can be entered in the TextCtrl as numbers or also as algebraic expressions using operators + - / \\* () and \\*\\*, in addition pi, sind(), cosd(), tand(), and sqrt() can be used, - as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq(). + as well as appreviations s(), sin(), c(), cos(), t(), tan() and sq(). :param wx.Frame parent: name of parent window, or may be None :param tuple dictlst: a list of dicts or lists containing values to edit - :param tuple elemlst: a list of keys/indices for items in dictlst. - Note that elemlst must have the same length as dictlst, where each + :param tuple elemlst: a list of keys/indices for items in dictlst. + Note that elemlst must have the same length as dictlst, where each item in elemlst will will match an entry for an entry for successive dicts/lists in dictlst. :param tuple prelbl: a list of labels placed before the TextCtrl for each item (optional) - + :param tuple postlbl: a list of labels placed after the TextCtrl for each item (optional) :param str title: a title to place in the frame of the dialog :param str header: text to place at the top of the window. May contain - new line characters. + new line characters. :param wx.Size size: a size parameter that dictates the size for the scrolled region of the dialog. The default is - (300,250). + (300,250). :param bool CopyButton: if True adds a small button that copies the value for the current row to all fields below (default is False) - + :param bool ASCIIonly: if set as True will remove unicode characters from strings - + :param list minvals: optional list of minimum values for validation of float or int values. Ignored if value is None. :param list maxvals: optional list of maximum values for validation of float or int values. Ignored if value is None. :param list sizevals: optional list of wx.Size values for each input widget. Ignored if value is None. - + :param tuple checkdictlst: an optional list of dicts or lists containing bool - values (similar to dictlst). + values (similar to dictlst). :param tuple checkelemlst: an optional list of dicts or lists containing bool key values (similar to elemlst). Must be used with checkdictlst. :param string checklabel: a string to use for each checkbutton - + :returns: the wx.Dialog created here. Use method .ShowModal() to display it. - + *Example for use of ScrolledMultiEditor:* :: @@ -1563,17 +1562,17 @@ class ScrolledMultiEditor(wx.Dialog): *Example definitions for dictlst and elemlst:* :: - + dictlst = (dict1,list1,dict1,list1) elemlst = ('a', 1, 2, 3) This causes items dict1['a'], list1[1], dict1[2] and list1[3] to be edited. - + Note that these items must have int, float or str values assigned to them. The dialog will force these types to be retained. String values - that are blank are marked as invalid. + that are blank are marked as invalid. ''' - + def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], title='Edit items',header='',size=(300,250), CopyButton=False, ASCIIonly=False, @@ -1616,7 +1615,7 @@ def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], self.CheckControlsList = [] # make list of CheckBoxes for i,(d,k) in enumerate(zip(dictlst,elemlst)): if i >= len(prelbl): # label before TextCtrl, or put in a blank - subSizer.Add((-1,-1)) + subSizer.Add((-1,-1)) else: subSizer.Add(wx.StaticText(panel,wx.ID_ANY,str(prelbl[i]))) kargs = {} @@ -1630,7 +1629,7 @@ def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], if i+1 == len(dictlst): but = (-1,-1) else: - import wx.lib.colourselect as wscs # is there a way to test? + import wx.lib.colourselect as wscs # is there a way to test? but = wscs.ColourSelect(label='v', # would like to use u'\u2193' or u'\u25BC' but not in WinXP parent=panel,colour=(255,255,200),size=wx.Size(30,23), style=wx.RAISED_BORDER) @@ -1642,6 +1641,9 @@ def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], self.ButtonIndex[but] = i subSizer.Add(but) # create the validated TextCrtl, store it and add it to the sizer + if type(d[k]) is str and d[k].startswith(' ') and GSASIIpath.GetConfigValue('debug'): + # seems to cause problems when the window resizes?!? See issue #171 + print(f'ScrolledMultiEditor Warning: initial space in {d[k]!r} may cause problems') ctrl = ValidatedTxtCtrl(panel,d,k,OKcontrol=self.ControlOKButton,ASCIIonly=ASCIIonly, **kargs) self.ValidatedControlsList.append(ctrl) @@ -1653,7 +1655,7 @@ def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], if i < len(checkdictlst): ch = G2CheckBox(panel,checklabel,checkdictlst[i],checkelemlst[i]) self.CheckControlsList.append(ch) - subSizer.Add(ch) + subSizer.Add(ch) else: subSizer.Add((-1,-1)) # finish up ScrolledPanel @@ -1663,7 +1665,7 @@ def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], # patch for wx 2.9 on Mac i,j= wx.__version__.split('.')[0:2] if int(i)+int(j)/10. > 2.8 and 'wxOSX' in wx.PlatformInfo: - panel.SetMinSize((subSizer.GetSize()[0]+30,panel.GetSize()[1])) + panel.SetMinSize((subSizer.GetSize()[0]+30,panel.GetSize()[1])) mainSizer.Add(panel,1, wx.ALL|wx.EXPAND,1) # Sizer for OK/Close buttons. N.B. on Close changes are discarded @@ -1671,7 +1673,7 @@ def __init__(self,parent,dictlst,elemlst,prelbl=[],postlbl=[], btnsizer = wx.BoxSizer(wx.HORIZONTAL) btnsizer.Add(self.OKbtn) self.OKbtn.Bind(wx.EVT_BUTTON,lambda event: self.EndModal(wx.ID_OK)) - btn = wx.Button(self, wx.ID_CLOSE,"Cancel") + btn = wx.Button(self, wx.ID_CLOSE,"Cancel") btn.Bind(wx.EVT_BUTTON,self._onClose) btnsizer.Add(btn) mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) @@ -1703,7 +1705,7 @@ def _onClose(self,event): for i in range(len(self.checkdictlst)): self.checkdictlst[i][self.checkelemlst[i]] = self.StartCheckValues[i] self.EndModal(wx.ID_CANCEL) - + def ControlOKButton(self,setvalue): '''Enable or Disable the OK button for the dialog. Note that this is passed into the ValidatedTxtCtrl for use by validators. @@ -1725,14 +1727,14 @@ def ControlOKButton(self,setvalue): ############################################### Multichoice Dialog with set all, toggle & filter options class G2MultiChoiceDialog(wx.Dialog): '''A dialog similar to wx.MultiChoiceDialog except that buttons are - added to set all choices and to toggle all choices and a filter is + added to set all choices and to toggle all choices and a filter is available to select from available entries. Note that if multiple - entries are placed in the filter box separated by spaces, all - of the strings must be present for an item to be shown. + entries are placed in the filter box separated by spaces, all + of the strings must be present for an item to be shown. :param wx.Frame ParentFrame: reference to parent frame :param str title: heading above list of choices - :param str header: Title to place on window frame + :param str header: Title to place on window frame :param list ChoiceList: a list of choices where one more will be selected :param bool toggle: If True (default) the toggle and select all buttons are displayed @@ -1741,15 +1743,15 @@ class G2MultiChoiceDialog(wx.Dialog): :param bool filterBox: If True (default) an input widget is placed on the window and only entries matching the entered text are shown. :param dict extraOpts: a dict containing a entries of form label_i and value_i with extra - options to present to the user, where value_i is the default value. + options to present to the user, where value_i is the default value. Options are listed ordered by the value_i values. - :param list selected: list of indicies for items that should be + :param list selected: list of indicies for items that should be :param kw: optional keyword parameters for the wx.Dialog may be included such as size [which defaults to `(320,310)`] and style (which defaults to `wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL`); - note that `wx.OK` and `wx.CANCEL` style items control + note that `wx.OK` and `wx.CANCEL` style items control the presence of the eponymous buttons in the dialog. - :returns: the name of the created dialog + :returns: the name of the created dialog ''' def __init__(self,parent, title, header, ChoiceList, toggle=True, monoFont=False, filterBox=True, extraOpts={}, selected=[], @@ -1774,7 +1776,7 @@ def __init__(self,parent, title, header, ChoiceList, toggle=True, useCANCEL = True options['style'] ^= wx.CANCEL else: - useCANCEL = False + useCANCEL = False # create the dialog frame wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options) # fill the dialog @@ -1859,17 +1861,17 @@ def __init__(self,parent, title, header, ChoiceList, toggle=True, self.SetSizer(Sizer) Sizer.Fit(self) self.CenterOnParent() - + def onOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def onCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() self.EndModal(wx.ID_CANCEL) - + def OnStride(self,event): self.Stride = int(self.stride.GetValue()) @@ -1881,9 +1883,9 @@ def SetRange(self,event): if self.settingRange: self.rangeCapt.SetLabel('Select range start') else: - self.rangeCapt.SetLabel('') + self.rangeCapt.SetLabel('') self.rangeFirst = None - + def GetSelections(self): 'Returns a list of the indices for the selected choices' # update self.Selections with settings for displayed items @@ -1891,7 +1893,7 @@ def GetSelections(self): self.Selections[self.filterlist[i]] = self.clb.IsChecked(i) # return all selections, shown or hidden return [i for i in range(len(self.Selections)) if self.Selections[i]] - + def SetSelections(self,selList): '''Sets the selection indices in selList as selected. Resets any previous selections for compatibility with wx.MultiChoiceDialog. Note that @@ -1915,7 +1917,7 @@ def _ShowSelections(self): self.clb.SetChecked( [i for i in range(len(self.filterlist)) if self.Selections[self.filterlist[i]]] ) # Note anything previously checked will be cleared. - + def _SetAll(self,event): 'Set all viewed choices on' if 'phoenix' in wx.version(): @@ -1924,12 +1926,12 @@ def _SetAll(self,event): self.clb.SetChecked(range(0,len(self.filterlist),self.Stride)) self.stride.SetValue('1') self.Stride = 1 - + def _ToggleAll(self,event): 'flip the state of all viewed choices' for i in range(len(self.filterlist)): self.clb.Check(i,not self.clb.IsChecked(i)) - + def onChar(self,event): 'Respond to keyboard events in the Filter box' self.OKbtn.Enable(False) @@ -1937,12 +1939,12 @@ def onChar(self,event): self.timer.Stop() self.timer.Start(1000,oneShot=True) if event: event.Skip() - + def OnCheck(self,event): '''for CheckListBox events; if Set Range is in use, this sets/clears all entries in range between start and end according to the value in start. Repeated clicks on the start change the checkbox state, but do not trigger - the range copy. + the range copy. The caption next to the button is updated on the first button press. ''' if self.settingRange: @@ -1959,7 +1961,7 @@ def OnCheck(self,event): self.rangeBut.SetValue(False) self.rangeCapt.SetLabel('') return - + def Filter(self,event): '''Read text from filter control and select entries that match. Called by Timer after a delay with no input or if Enter is pressed. @@ -1970,7 +1972,7 @@ def Filter(self,event): txt = self.filterBox.GetValue() txt = txt.lower() self.clb.Clear() - + self.Update() self.filterlist = [] if txt: @@ -1991,9 +1993,9 @@ def Filter(self,event): ############################################### Multichoice in a sizer with set all, toggle & filter options class G2MultiChoiceWindow(wx.BoxSizer): - '''Creates a sizer similar to G2MultiChoiceDialog except that + '''Creates a sizer similar to G2MultiChoiceDialog except that buttons are added to set all choices and to toggle all choices. This - is placed in a sizer, so that it can be used in a frame or panel. + is placed in a sizer, so that it can be used in a frame or panel. :param parent: reference to parent frame/panel :param str title: heading above list of choices @@ -2005,9 +2007,9 @@ class G2MultiChoiceWindow(wx.BoxSizer): if True use a equally-spaced font. :param bool filterBox: If True (default) an input widget is placed on the window and only entries matching the entered text are shown. - :param function OnChange: a reference to a callable object, that + :param function OnChange: a reference to a callable object, that is called each time any a choice is changed. Default is None which - will not be called. + will not be called. :param list OnChangeArgs: a list of arguments to be supplied to function OnChange. The default is a null list. :returns: the name of the created sizer @@ -2076,7 +2078,7 @@ def __init__(self, parent, title, ChoiceList, SelectList, toggle=True, tSizer.Add(self.rangeCapt,1,wx.EXPAND,1) Sizer.Add(tSizer,0,wx.LEFT,12) self.SetSelections(self.SelectList) - + def OnStride(self,event): self.Stride = int(self.stride.GetValue()) @@ -2088,9 +2090,9 @@ def SetRange(self,event): if self.settingRange: self.rangeCapt.SetLabel('Select range start') else: - self.rangeCapt.SetLabel('') + self.rangeCapt.SetLabel('') self.rangeFirst = None - + def GetSelections(self): 'Returns a list of the indices for the selected choices' # update self.Selections with settings for displayed items @@ -2098,7 +2100,7 @@ def GetSelections(self): self.Selections[self.filterlist[i]] = self.clb.IsChecked(i) # return all selections, shown or hidden return [i for i in range(len(self.Selections)) if self.Selections[i]] - + def SetSelections(self,selList): '''Sets the selection indices in selList as selected. Resets any previous selections for compatibility with wx.MultiChoiceDialog. Note that @@ -2142,26 +2144,26 @@ def _SetAll(self,event): self.Stride = 1 self.GetSelections() # record current selections self._ShowSelections() - + def _ToggleAll(self,event): 'flip the state of all viewed choices' for i in range(len(self.filterlist)): self.clb.Check(i,not self.clb.IsChecked(i)) self.GetSelections() # record current selections self._ShowSelections() - + def onChar(self,event): 'Respond to keyboard events in the Filter box' if self.timer.IsRunning(): self.timer.Stop() self.timer.Start(1000,oneShot=True) if event: event.Skip() - + def OnCheck(self,event): '''for CheckListBox events; if Set Range is in use, this sets/clears all entries in range between start and end according to the value in start. Repeated clicks on the start change the checkbox state, but do not trigger - the range copy. + the range copy. The caption next to the button is updated on the first button press. ''' if self.settingRange: @@ -2191,7 +2193,7 @@ def Filter(self,event): self.GetSelections() # record current selections txt = self.filterBox.GetValue() self.clb.Clear() - + self.filterlist = [] if txt: txt = txt.lower() @@ -2205,7 +2207,7 @@ def Filter(self,event): ChoiceList = self.ChoiceList self.clb.AppendItems(ChoiceList) self._ShowSelections() - + def SelectEdit1Var(G2frame,array,labelLst,elemKeysLst,dspLst,refFlgElem): '''Select a variable from a list, then edit it and select histograms to copy it to. @@ -2223,7 +2225,7 @@ def SelectEdit1Var(G2frame,array,labelLst,elemKeysLst,dspLst,refFlgElem): Example:: - array = data + array = data labelLst = ['v1','v2'] elemKeysLst = [['v1'], ['v2',0]] refFlgElem = [None, ['v2',1]] @@ -2260,14 +2262,14 @@ def OnChoice(event): elemKeysLst[i][-1], **args) copyopts['startvalue'] = unkey(array,elemKeysLst[i]) - #unkey(array,elemKeysLst[i][:-1])[elemKeysLst[i][-1]] = + #unkey(array,elemKeysLst[i][:-1])[elemKeysLst[i][-1]] = valSizer.Add(Val,0,wx.LEFT,5) dlg.SendSizeEvent() - + # SelectEdit1Var execution begins here saveArray = copy.deepcopy(array) # keep original values TreeItemType = G2frame.GPXtree.GetItemText(G2frame.PickId) - copyopts = {'InTable':False,"startvalue":None,'currentsel':None} + copyopts = {'InTable':False,"startvalue":None,'currentsel':None} hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) histList = G2pdG.GetHistsLikeSelected(G2frame) if not histList: @@ -2328,7 +2330,7 @@ def OnChoice(event): dlg.CenterOnParent() try: if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): copyList.append(histList[i]) else: # reset the parameter since cancel was pressed @@ -2389,7 +2391,7 @@ class G2SingleChoiceDialog(wx.Dialog): :param wx.Frame ParentFrame: reference to parent frame :param str title: heading above list of choices - :param str header: Title to place on window frame + :param str header: Title to place on window frame :param list ChoiceList: a list of choices where one will be selected :param bool monoFont: If False (default), use a variable-spaced font; if True use a equally-spaced font. @@ -2416,7 +2418,7 @@ class G2SingleChoiceDialog(wx.Dialog): dlg.Destroy() ''' - def __init__(self,parent, title, header, ChoiceList, + def __init__(self,parent, title, header, ChoiceList, monoFont=False, filterBox=True, **kw): # process keyword parameters, notably style options = {'size':(320,310), # default Frame keywords @@ -2434,7 +2436,7 @@ def __init__(self,parent, title, header, ChoiceList, useCANCEL = True options['style'] ^= wx.CANCEL else: - useCANCEL = False + useCANCEL = False # create the dialog frame wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options) # fill the dialog @@ -2507,7 +2509,7 @@ def Filter(self,event): self.OKbtn.Enable(True) def onDoubleClick(self,event): self.EndModal(wx.ID_OK) - + ################################################################################ class FlagSetDialog(wx.Dialog): ''' Creates popup with table of variables to be checked for e.g. refinement flags @@ -2521,15 +2523,15 @@ def __init__(self,parent,title,colnames,rownames,flags): self.flags = flags self.newflags = copy.copy(flags) self.Draw() - + def Draw(self): Indx = {} - + def OnSelection(event): Obj = event.GetEventObject() [name,ia] = Indx[Obj.GetId()] self.newflags[name][ia] = Obj.GetValue() - + if self.panel: self.panel.DestroyChildren() #safe: wx.Panel self.panel.Destroy() @@ -2549,7 +2551,7 @@ def OnSelection(event): flagSizer.Add(flg,0,WACV) else: flagSizer.Add(wx.StaticText(self.panel,label='na'),0,WACV) - + mainSizer.Add(flagSizer,0) OkBtn = wx.Button(self.panel,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, self.OnOk) @@ -2564,15 +2566,15 @@ def OnSelection(event): self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetSelection(self): return self.newflags def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() @@ -2584,7 +2586,7 @@ def G2MessageBox(parent,msg,title='Error'): TODO: replace wx.MessageDialog with one derived from wx.Dialog because on most platforms wx.MessageDialog is a native widget and CentreOnParent - will not function. + will not function. ''' dlg = wx.MessageDialog(parent,StripIndents(msg), title, wx.OK|wx.CENTRE) dlg.CentreOnParent() @@ -2596,15 +2598,15 @@ def ShowScrolledInfo(parent,txt,width=600,height=400,header='Warning info', '''Simple code to display possibly extensive error or warning text in a scrolled window. - :param wx.Frame parent: parent window for + :param wx.Frame parent: parent window for :param str txt: text to be displayed :param int width: lateral of window in pixels (defaults to 600) :param int height: vertical dimension of window in pixels (defaults to 400) :param str header: title to be placed on window - :param list buttonlist: list of button Ids to show, or one or more - pairs of values, where the first is a label to place on the button + :param list buttonlist: list of button Ids to show, or one or more + pairs of values, where the first is a label to place on the button and the second is a routine that is called if the button is pressed. - The default is None which places a single "Close" button that + The default is None which places a single "Close" button that returns wx.ID_CANCEL :returns: the wx Id for the selected button @@ -2617,7 +2619,7 @@ def ShowScrolledInfo(parent,txt,width=600,height=400,header='Warning info', if res == wx.ID_OK: pass ''' - + dlg = wx.Dialog(parent.GetTopLevelParent(),wx.ID_ANY,header, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) spanel = wxscroll.ScrolledPanel(dlg, wx.ID_ANY, size=(width-20, height)) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -2631,16 +2633,16 @@ def ShowScrolledInfo(parent,txt,width=600,height=400,header='Warning info', spanel.SetSizer(txtSizer) btnsizer = wx.BoxSizer(wx.HORIZONTAL) if buttonlist is None: - btn = wx.Button(dlg, wx.ID_CLOSE) + btn = wx.Button(dlg, wx.ID_CLOSE) btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) else: for b in buttonlist: if isinstance(b, (list, tuple)): - btn = wx.Button(dlg, wx.ID_ANY, b[0]) + btn = wx.Button(dlg, wx.ID_ANY, b[0]) btn.Bind(wx.EVT_BUTTON,b[1]) else: - btn = wx.Button(dlg, b) + btn = wx.Button(dlg, b) btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(event.Id)) btnsizer.Add(btn) btnsizer.Add((6,-1)) @@ -2649,29 +2651,29 @@ def ShowScrolledInfo(parent,txt,width=600,height=400,header='Warning info', mainSizer.Fit(dlg) spanel.SetAutoLayout(1) spanel.SetupScrolling() - #dlg.SetMaxSize((-1,400)) + #dlg.SetMaxSize((-1,400)) dlg.CenterOnParent() ans = dlg.ShowModal() dlg.Destroy() return ans def ShowScrolledColText(parent,txt,width=600,height=400,header='Warning info',col1len=999): - '''Simple code to display tabular information in a scrolled wx.Dialog + '''Simple code to display tabular information in a scrolled wx.Dialog window. - Lines ending with a colon (:) are centered across all columns - and have a grey background. - Lines beginning and ending with '**' are also are centered + Lines ending with a colon (:) are centered across all columns + and have a grey background. + Lines beginning and ending with '**' are also are centered across all columns and are given a yellow background - All other lines have columns split by tab (\\t) characters. + All other lines have columns split by tab (\\t) characters. - :param wx.Frame parent: parent window + :param wx.Frame parent: parent window :param str txt: text to be displayed :param int width: lateral of window in pixels (defaults to 600) :param int height: vertical dimension of window in pixels (defaults to 400) :param str header: title to be placed on window ''' - + dlg = wx.Dialog(parent.GetTopLevelParent(),wx.ID_ANY,header, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) spanel = wxscroll.ScrolledPanel(dlg, wx.ID_ANY, size=(width-20, height)) spanel.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) @@ -2708,7 +2710,7 @@ def ShowScrolledColText(parent,txt,width=600,height=400,header='Warning info',co for sym in (') ',' * ',' + ',' - ',' && '): b = max(b, t.rfind(sym,0,col1len)) if b > 20: - s += t[:b+1] + s += t[:b+1] t = '\n\t' + t[b+1:] continue break @@ -2729,22 +2731,22 @@ def ShowScrolledColText(parent,txt,width=600,height=400,header='Warning info',co mainSizer.Fit(dlg) spanel.SetAutoLayout(1) spanel.SetupScrolling() - #dlg.SetMaxSize((-1,400)) + #dlg.SetMaxSize((-1,400)) dlg.CenterOnParent() dlg.ShowModal() dlg.Destroy() - + def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300),comment=''): '''Display a scrolled table of information in a dialog window :param wx.Frame G2frame: parent for dialog - :param str lbl: label for window + :param str lbl: label for window :param str title: window title :param list tbl: list of lists where inner list is each row :param list colLbls: list of str with labels for each column - :param list colTypes: Data types for each column (such as + :param list colTypes: Data types for each column (such as wg.GRID_VALUE_STRING,wg.GRID_VALUE_FLOAT) - :param list maxSize: Maximum size for the table in points. Defaults to + :param list maxSize: Maximum size for the table in points. Defaults to (600,300) :param str comment: optional text that appears below table @@ -2756,7 +2758,7 @@ def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300),comm G2ScrolledGrid(frm,'window label','title',20*[row],colLbls,colTypes) ''' - + dlg = wx.Dialog(G2frame,title=title,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(wx.StaticText(dlg,label=lbl), @@ -2764,7 +2766,7 @@ def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300),comm sizer.Add((-1,15)) rowlbl = [str(i+1) for i in range(len(tbl))] wxtbl = Table(tbl,rowLabels=rowlbl,colLabels=colLbls,types=colTypes) - + scrGrid = wx.ScrolledWindow(dlg) wxGrid = GSGrid(scrGrid) wxGrid.SetTable(wxtbl, True) @@ -2774,7 +2776,7 @@ def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300),comm gridSizer.Add(wxGrid,1,wx.EXPAND,1) gridSizer.Layout() Size = gridSizer.GetMinSize() - + Size[0] = min(Size[0]+25,maxSize[0]) Size[1] = min(Size[1]+25,maxSize[1]) scrGrid.SetSizer(gridSizer) @@ -2784,7 +2786,7 @@ def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300),comm sizer.Add(scrGrid,1,wx.EXPAND,1) if len(comment): sizer.Add(wx.StaticText(dlg,label=comment)) - + btnsizer = wx.BoxSizer(wx.HORIZONTAL) btnsizer.Add((-1,-1),1,wx.EXPAND,1) btn = wx.Button(dlg, wx.ID_OK) @@ -2793,8 +2795,8 @@ def G2ScrolledGrid(G2frame,lbl,title,tbl,colLbls,colTypes,maxSize=(600,300),comm btnsizer.Add(btn) btnsizer.Add((-1,-1),1,wx.EXPAND,1) sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 5) - - sizer.Layout() + + sizer.Layout() dlg.SetSizer(sizer) sizer.Fit(dlg) dlg.CenterOnParent() @@ -2806,7 +2808,7 @@ class PickTwoDialog(wx.Dialog): '''This does not seem to be in use ''' def __init__(self,parent,title,prompt,names,choices): - wx.Dialog.__init__(self,parent,-1,title, + wx.Dialog.__init__(self,parent,-1,title, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = None self.prompt = prompt @@ -2816,13 +2818,13 @@ def __init__(self,parent,title,prompt,names,choices): def Draw(self): Indx = {} - + def OnSelection(event): Obj = event.GetEventObject() id = Indx[Obj.GetId()] self.choices[id] = Obj.GetValue().encode() #to avoid Unicode versions self.Draw() - + if self.panel: self.panel.DestroyChildren() #safe: wx.Panel self.panel.Destroy() @@ -2855,15 +2857,15 @@ def OnSelection(event): self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetSelection(self): return self.choices def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() @@ -2881,19 +2883,19 @@ class SingleFloatDialog(wx.Dialog): for no bounds checking, [None,val] for only upper bounds, etc. Default is [0,1]. Values outside of limits will be ignored. :param str format: string to format numbers. Defaults to '%.5g'. Use '%d' to have - integer input (but dlg.GetValue will still return a float). - + integer input (but dlg.GetValue will still return a float). + Typical usage:: limits = (0,1) dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for...',default,limits) if dlg.ShowModal() == wx.ID_OK: parm = dlg.GetValue() - dlg.Destroy() + dlg.Destroy() ''' def __init__(self,parent,title,prompt,value,limits=[0.,1.],fmt='%.5g'): - wx.Dialog.__init__(self,parent,-1,title, + wx.Dialog.__init__(self,parent,-1,title, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.CenterOnParent() mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -2929,12 +2931,12 @@ def __init__(self,parent,title,prompt,value,limits=[0.,1.],fmt='%.5g'): def GetValue(self): return self.buffer[0] - + def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() @@ -2963,14 +2965,14 @@ class SingleIntDialog(SingleFloatDialog): :param list limits: upper and lower value used to set bounds for entries. Default is [None,None] -- for no bounds checking; use [None,val] for only upper bounds, etc. Default is [0,1]. Values outside of limits will be ignored. - + Typical usage:: limits = (0,None) # allows zero or positive values only dlg = G2G.SingleIntDialog(G2frame,'New value','Enter new value for...',default,limits) if dlg.ShowModal() == wx.ID_OK: parm = dlg.GetValue() - dlg.Destroy() + dlg.Destroy() ''' def __init__(self,parent,title,prompt,value,limits=[None,None]): @@ -2980,49 +2982,49 @@ def GetValue(self): ################################################################################ class MultiDataDialog(wx.Dialog): - '''Dialog to obtain multiple values from user. Use ``dlg.GetValues()`` to + '''Dialog to obtain multiple values from user. Use ``dlg.GetValues()`` to get the values set in the window. :param wx.Frame parent: parent frame for dialog to be created :param str title: title to place on top of window :param list prompts: a string to describe each item. Each entry - in this list will designate a row in the generated window. - :param list values: a list of initial values for each item. Use a - nested list when multiple entries are placed on a single row of + in this list will designate a row in the generated window. + :param list values: a list of initial values for each item. Use a + nested list when multiple entries are placed on a single row of the window (see discussion of formats, below). Number of items - in the outer list should match the length of ``prompts``. The + in the outer list should match the length of ``prompts``. The total number of items should match ``formats``. - :param list limits: A nested list with an upper and lower value - for each item or for a choice/edit control a list of allowed - values. Use a nested list when multiple entries are placed on + :param list limits: A nested list with an upper and lower value + for each item or for a choice/edit control a list of allowed + values. Use a nested list when multiple entries are placed on a single row of the window (see discussion of formats, below). - Number of items in the outer list should match the length of + Number of items in the outer list should match the length of ``prompts``. The total number of items should match ``formats``. :param list testfxns: A nested list of string test functions. The total number of items should match ``formats`` or should be left as the default (None). - :param list formats: A list of values for each entry in the - window. Several different types of values are possible: + :param list formats: A list of values for each entry in the + window. Several different types of values are possible: - * An "old-style" format string (e.g. ``%5d`` or ``%.3f``) + * An "old-style" format string (e.g. ``%5d`` or ``%.3f``) which will be used to display each item's value - * Or a keyword that specifies how the values are used. + * Or a keyword that specifies how the values are used. Allowed keywords are: * ``choice``: for a pull-down list; * ``bool``: for a yes/no checkbox; - * ``str``: for a text entry + * ``str``: for a text entry * ``edit``: for a pull-down list that allows one to enter an arbitrary value. - * Alternately, a value can be a list of items, in which case multiple - entries are placed on a single row of the window. When this is done, - any value in the list other than ``choice`` or ``edit`` is used as + * Alternately, a value can be a list of items, in which case multiple + entries are placed on a single row of the window. When this is done, + any value in the list other than ``choice`` or ``edit`` is used as text to be placed between the ComboBoxes. - The number of items in the outer list should match the length + The number of items in the outer list should match the length of ``prompts``. - :param str header: a string to be placed at the top of the + :param str header: a string to be placed at the top of the window. Ignored if None (the default.) Example 1:: @@ -3053,7 +3055,7 @@ class MultiDataDialog(wx.Dialog): ''' def __init__(self,parent,title,prompts,values,limits=[[0.,1.],], testfxns=None,formats=['%.5g',],header=None): - wx.Dialog.__init__(self,parent,-1,title, + wx.Dialog.__init__(self,parent,-1,title, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = None self.limits = limits @@ -3066,14 +3068,14 @@ def __init__(self,parent,title,prompts,values,limits=[[0.,1.],], if type(i) is list: self.testfxns.append(len(i)*[None]) else: - self.testfxns.append(None) + self.testfxns.append(None) else: self.testfxns = testfxns self.header = header self.Draw() - + def Draw(self): - + def OnEditItem(event): if event: event.Skip() Obj = event.GetEventObject() @@ -3096,7 +3098,7 @@ def OnEditItem(event): return except: pass - + def OnValItem(event): if event: event.Skip() Obj = event.GetEventObject() @@ -3113,7 +3115,7 @@ def OnValItem(event): else: Obj.SetBackgroundColour(wx.YELLOW) Obj.SetForegroundColour("red") - Obj.SetValue('%s'%(val)) + Obj.SetValue('%s'%(val)) self.values[tid][idl] = Obj.GetValue() elif 'bool' in fmt: self.values[Indx[Obj][0]] = Obj.GetValue() @@ -3137,10 +3139,10 @@ def OnValItem(event): else: Obj.SetBackgroundColour(wx.YELLOW) Obj.SetForegroundColour("red") - Obj.SetValue('%s'%(val)) + Obj.SetValue('%s'%(val)) elif 'choice' in fmt: self.values[Indx[Obj][0]] = Obj.GetValue() - + Indx = {} if self.panel: self.panel.Destroy() self.panel = wx.Panel(self) @@ -3237,12 +3239,12 @@ def OnValItem(event): def GetValues(self): return self.values - + def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() @@ -3251,20 +3253,20 @@ def OnCancel(self,event): ################################################################################ class SingleStringDialog(wx.Dialog): '''Dialog to obtain a single string value from user - + :param wx.Frame parent: name of parent frame :param str title: title string for dialog :param str prompt: string to tell use what they are inputting :param str value: default input value, if any - :param tuple size: specifies default size and width for the text - entry section of the dialog [default (200,-1)]. If the vertical - size (the second number) is greater than 20 (~ a single line) then + :param tuple size: specifies default size and width for the text + entry section of the dialog [default (200,-1)]. If the vertical + size (the second number) is greater than 20 (~ a single line) then the textbox will allow inclusion of new-line characters. In single-line mode, return causes the dialog to close. :param str help: if supplied, a help button is added to the dialog that - can be used to display the supplied help text/URL for setting this + can be used to display the supplied help text/URL for setting this variable. (Default is '', which is ignored.) - :param list choices: a set of strings that provide optional values that + :param list choices: a set of strings that provide optional values that can be selected from; these can be edited if desired. ''' def __init__(self,parent,title,prompt,value='',size=(200,-1),help='', @@ -3326,22 +3328,22 @@ def GetValue(self): ################################################################################ class MultiStringDialog(wx.Dialog): '''Dialog to obtain a multi string values from user - + :param wx.Frame parent: name of parent frame :param str title: title string for dialog :param list prompts: list of strings to tell user what they are inputting :param list values: list of str default input values, if any :param int size: length of the input box in pixels - :param bool addRows: if True, users can add rows to the table + :param bool addRows: if True, users can add rows to the table (default is False) :param str hlp: if supplied, a help button is added to the dialog that can be used to display the supplied help text in this variable. - :param str lbl: label placed at top of dialog + :param str lbl: label placed at top of dialog :returns: a wx.Dialog instance ''' def __init__(self,parent,title,prompts,values=[],size=-1, addRows=False,hlp=None, lbl=None): - wx.Dialog.__init__(self,parent,wx.ID_ANY,title, + wx.Dialog.__init__(self,parent,wx.ID_ANY,title, pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) self.values = list(values) @@ -3397,7 +3399,7 @@ def onExpand(self,event): self.values.append('') self.prompts.append('item '+str(len(self.values))) self.Paint() - + def newValue(self,event): Obj = event.GetEventObject() item = self.Indx[Obj.GetId()] @@ -3406,7 +3408,7 @@ def newValue(self,event): def Show(self): '''Use this method after creating the dialog to post it - + :returns: True if the user pressed OK; False if the User pressed Cancel ''' if self.ShowModal() == wx.ID_OK: @@ -3416,7 +3418,7 @@ def Show(self): def GetValues(self): '''Use this method to get the value(s) entered by the user - + :returns: a list of strings entered by user ''' return self.values @@ -3424,10 +3426,10 @@ def GetValues(self): ################################################################################ class G2ColumnIDDialog(wx.Dialog): '''A dialog for matching column data to desired items; some columns may be ignored. - + :param wx.Frame ParentFrame: reference to parent frame :param str title: heading above list of choices - :param str header: Title to place on window frame + :param str header: Title to place on window frame :param list ChoiceList: a list of possible choices for the columns :param list ColumnData: lists of column data to be matched with ChoiceList :param bool monoFont: If False (default), use a variable-spaced font; @@ -3438,7 +3440,7 @@ class G2ColumnIDDialog(wx.Dialog): note that ``wx.OK`` and ``wx.CANCEL`` controls the presence of the eponymous buttons in the dialog. :returns: the name of the created dialog - + ''' def __init__(self,parent, title, header,Comments,ChoiceList, ColumnData, @@ -3460,7 +3462,7 @@ def OnOk(sevent): return if parent is not None: parent.Raise() self.EndModal(wx.ID_OK) - + def OnModify(event): if event: event.Skip() Obj = event.GetEventObject() @@ -3473,7 +3475,7 @@ def OnModify(event): self.ColumnData[icol][i] = str(eval(item+modify)) colData.SetValue('\n'.join(self.ColumnData[icol])) Obj.SetValue('') - + # process keyword parameters, notably style options = {'size':(600,310), # default Frame keywords 'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL, @@ -3491,7 +3493,7 @@ def OnModify(event): useCANCEL = True options['style'] ^= wx.CANCEL else: - useCANCEL = False + useCANCEL = False # create the dialog frame wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options) panel = wxscroll.ScrolledPanel(self) @@ -3535,7 +3537,7 @@ def OnModify(event): colSizer.Add(mod) columnsSizer.Add(colSizer,0,wx.ALL|wx.EXPAND,10) Sizer.Add(columnsSizer,1,wx.ALL|wx.EXPAND,1) - Sizer.Add(wx.StaticText(panel,label=' For modify by, enter arithmetic string eg. "-12345.67". "+", "-", "*", "/", "**" all allowed'),0) + Sizer.Add(wx.StaticText(panel,label=' For modify by, enter arithmetic string eg. "-12345.67". "+", "-", "*", "/", "**" all allowed'),0) Sizer.Add((-1,10)) # OK/Cancel buttons btnsizer = wx.StdDialogButtonSizer() @@ -3559,21 +3561,21 @@ def OnModify(event): panel.SetSize(Size) Size[0] += 25; Size[1]+= 25+txtSize self.SetSize(Size) - + def GetSelection(self): 'Returns the selected sample parm for each column' selCols = [] for item in self.sel: selCols.append(item.GetValue()) return selCols,self.ColumnData - + ################################################################################ class G2HistoDataDialog(wx.Dialog): '''A dialog for editing histogram data globally. - + :param wx.Frame ParentFrame: reference to parent frame :param str title: heading above list of choices - :param str header: Title to place on window frame + :param str header: Title to place on window frame :param list ParmList: a list of names for the columns :param list ParmFmt: a list of formatting strings for the columns :param list: HistoList: a list of histogram names @@ -3582,11 +3584,11 @@ class G2HistoDataDialog(wx.Dialog): if True use a equally-spaced font. :param kw: optional keyword parameters for the wx.Dialog may be included such as size [which defaults to `(320,310)`] and - style (which defaults to + style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``); note that ``wx.OK`` and ``wx.CANCEL`` controls the presence of the eponymous buttons in the dialog. :returns: the modified ParmData - + ''' def __init__(self,parent, title, header,ParmList,ParmFmt,HistoList,ParmData, @@ -3595,7 +3597,7 @@ def __init__(self,parent, title, header,ParmList,ParmFmt,HistoList,ParmData, def OnOk(sevent): if parent is not None: parent.Raise() self.EndModal(wx.ID_OK) - + def OnModify(event): Obj = event.GetEventObject() irow,it = Indx[Obj.GetId()] @@ -3605,7 +3607,7 @@ def OnModify(event): val = self.ParmData[irow][it] self.ParmData[irow][it] = val Obj.SetValue(self.ParmFmt[it]%val) - + # process keyword parameters, notably style options = {'size':(600,310), # default Frame keywords 'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL, @@ -3625,7 +3627,7 @@ def OnModify(event): useCANCEL = True options['style'] ^= wx.CANCEL else: - useCANCEL = False + useCANCEL = False # create the dialog frame wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options) panel = wxscroll.ScrolledPanel(self) @@ -3671,11 +3673,11 @@ def OnModify(event): panel.SetSize(Size) Size[0] += 25; Size[1]+= 25 self.SetSize(Size) - + def GetData(self): 'Returns the modified ParmData' return self.ParmData - + ################################################################################ def ItemSelector(ChoiceList, ParentFrame=None, title='Select an item', @@ -3690,10 +3692,10 @@ def ItemSelector(ChoiceList, ParentFrame=None, :param str header: Title to place on window frame (default 'Item Selector') :param bool useCancel: If True (default) both the OK and Cancel buttons are offered :param bool multiple: If True then multiple items can be selected (default False) - + :returns: the selection index or None or a selection list if multiple is true - Called by GSASIIdataGUI.OnReOrgSelSeq() Which is not fully implemented. + Called by GSASIIdataGUI.OnReOrgSelSeq() Which is not fully implemented. ''' if multiple: if useCancel: @@ -3728,14 +3730,14 @@ def ItemSelector(ChoiceList, ParentFrame=None, # Column-order selection dialog def GetItemOrder(parent,keylist,vallookup,posdict): '''Creates a dialog where items can be ordered into columns - + :param list keylist: is a list of keys for column assignments - :param dict vallookup: is a dict keyed by names in keylist where each item is a dict. - Each inner dict contains variable names as keys and their associated values - :param dict posdict: is a dict keyed by names in keylist where each item is a dict. + :param dict vallookup: is a dict keyed by names in keylist where each item is a dict. + Each inner dict contains variable names as keys and their associated values + :param dict posdict: is a dict keyed by names in keylist where each item is a dict. Each inner dict contains column numbers as keys and their associated variable name as a value. This is used for both input and output. - + ''' dlg = wx.Dialog(parent,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) sizer = wx.BoxSizer(wx.VERTICAL) @@ -3759,15 +3761,15 @@ class MultiIntegerDialog(wx.Dialog): '''Input a series of integers based on prompts ''' def __init__(self,parent,title,prompts,values): - wx.Dialog.__init__(self,parent,-1,title, + wx.Dialog.__init__(self,parent,-1,title, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.values = values self.prompts = prompts self.Draw() - + def Draw(self): - + def OnValItem(event): event.Skip() Obj = event.GetEventObject() @@ -3780,7 +3782,7 @@ def OnValItem(event): val = self.values[ind] self.values[ind] = val Obj.SetValue('%d'%(val)) - + self.panel.Destroy() self.panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -3808,12 +3810,12 @@ def OnValItem(event): def GetValues(self): return self.values - + def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() @@ -3823,8 +3825,8 @@ def OnCancel(self,event): class MultiColumnSelection(wx.Dialog): '''Defines a Dialog widget that can be used to select an item from a multicolumn list. The first column should be short, but remaining columns are word-wrapped if the - length of the information extends beyond the column. - + length of the information extends beyond the column. + When created, the dialog will be shown and .Selection will be set to the index of the selected row, or -1. Be sure to use .Destroy() to remove the window after reading the selection. If the dialog cannot be shown because a very old @@ -3832,7 +3834,7 @@ class MultiColumnSelection(wx.Dialog): If checkLbl is provided with a value, then a set of check buttons starts the table and .Selections has the checked rows. - + :param wx.Frame parent: the parent frame (or None) :param str title: A title for the dialog window :param list colLabels: labels for each column @@ -3846,9 +3848,9 @@ class MultiColumnSelection(wx.Dialog): This option seems to be broken. :param int height: an optional height (pixels) for the table (defaults to 400) :param bool centerCols: if True, items in each column are centered. Default is False - + Example use:: - + lbls = ('col 1','col 2','col 3') choices=(['test1','explanation of test 1'], ['b', 'a really really long line that will be word-wrapped'], @@ -3857,7 +3859,7 @@ class MultiColumnSelection(wx.Dialog): dlg = MultiColumnSelection(frm,'select tutorial',lbls,choices,colWidths) value = choices[dlg.Selection][0] dlg.Destroy() - + ''' def __init__(self, parent, title, colLabels, choices, colWidths, checkLbl="", height=400, centerCols=False, *args, **kw): @@ -3883,7 +3885,7 @@ def __init__(self, parent, title, colLabels, choices, colWidths, checkLbl="", colPosition = ULC.ULC_FORMAT_CENTER else: colPosition = ULC.ULC_FORMAT_LEFT - + if checkLbl: self.list.InsertColumn(0, checkLbl, width=8*len(checkLbl), format=colPosition) inc = 1 @@ -3917,7 +3919,7 @@ def OnCheck(event,row=i): OKbtn.SetDefault() btnsizer.Add(OKbtn) if not checkLbl: - btn = wx.Button(self, wx.ID_CLOSE,"Cancel") + btn = wx.Button(self, wx.ID_CLOSE,"Cancel") btnsizer.Add(btn) mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) # bindings for close of window, double-click,... @@ -3925,7 +3927,7 @@ def OnCheck(event,row=i): if not checkLbl: OKbtn.Bind(wx.EVT_BUTTON,self._onSelect) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._onSelect) - btn.Bind(wx.EVT_BUTTON,self._onClose) + btn.Bind(wx.EVT_BUTTON,self._onClose) self.SetSizer(mainSizer) self.ShowModal() def _onClose(self,event): @@ -3938,12 +3940,12 @@ def _onSelect(self,event): def MultiColMultiSelDlg(parent, title, header, colInfo, choices): '''Provides a dialog widget that can be used to select multiple items - from a multicolumn list. - + from a multicolumn list. + :param wx.Frame parent: the parent frame (or None) :param str title: A title for the dialog window :param str header: A instruction string for the dialog window - :param list colInfo: contains three items for each column: a label for the column, + :param list colInfo: contains three items for each column: a label for the column, a width for the column (in pixels), and True if the column should be right justified. :param list choices: a nested list with values for each row in the table. Within each row should be a list of values for each column. There must be at least one value, but it is @@ -3951,7 +3953,7 @@ def MultiColMultiSelDlg(parent, title, header, colInfo, choices): and unspecified columns are left blank. :returns: a list of bool values for each entry in choices, True if selected, or None is the dialog is cancelled. - + Example use:: choices = [('xmltodict', 'Bruker .brml Importer'), @@ -4005,18 +4007,18 @@ def MultiColMultiSelDlg(parent, title, header, colInfo, choices): return finally: dlg.Destroy() - + ################################################################################ class OrderBox(wxscroll.ScrolledPanel): '''Creates a panel with scrollbars where items can be ordered into columns - + :param list keylist: is a list of keys for column assignments - :param dict vallookup: is a dict keyed by names in keylist where each item is a dict. - Each inner dict contains variable names as keys and their associated values - :param dict posdict: is a dict keyed by names in keylist where each item is a dict. + :param dict vallookup: is a dict keyed by names in keylist where each item is a dict. + Each inner dict contains variable names as keys and their associated values + :param dict posdict: is a dict keyed by names in keylist where each item is a dict. Each inner dict contains column numbers as keys and their associated variable name as a value. This is used for both input and output. - + ''' def __init__(self,parent,keylist,vallookup,posdict,*arg,**kw): self.keylist = keylist @@ -4117,11 +4119,11 @@ def OnChoice(self,event): wid.SetSelection(self.chceDict[wid][1]) self.GBsizer.Layout() self.FitInside() - + ################################################################################ def GetImportFile(G2frame, message, defaultDir="", defaultFile="", style=wx.FD_OPEN, parent=None,*args, **kwargs): - '''Uses a standard or GSASII-customized dialog that gets files + '''Uses a standard or GSASII-customized dialog that gets files from the appropriate import directory. Arguments are used the same as in :func:`wx.FileDialog`. Selection of multiple files is allowed if argument style includes wx.FD_MULTIPLE. @@ -4383,7 +4385,7 @@ def GetImportPath(G2frame): '''Determines the default location to use for importing files. Tries sequentially G2frame.TutorialImportDir, config var Import_directory, G2frame.LastImportDir and G2frame.LastGPXdir - + :returns: a string containing the path to be used when reading files or '.' if none of the above are specified. ''' @@ -4412,7 +4414,7 @@ def GetImportPath(G2frame): def GetExportPath(G2frame): '''Determines the default location to use for writing files. Tries sequentially G2frame.LastExportDir and G2frame.LastGPXdir. - + :returns: a string containing the path to be used when writing files or '.' if none of the above are specified. ''' @@ -4451,7 +4453,7 @@ def __init__(self,parent,title,text,table,spins=[],): tableSizer = wx.FlexGridSizer(0,2*ncol+3,0,0) continue num,flds = item.split(')') - tableSizer.Add(wx.StaticText(self.panel,label=' %s '%(num+')')),0,WACV|wx.ALIGN_LEFT) + tableSizer.Add(wx.StaticText(self.panel,label=' %s '%(num+')')),0,WACV|wx.ALIGN_LEFT) flds = flds.replace(' ','').split(',') for i,fld in enumerate(flds): if i < ncol-1: @@ -4464,7 +4466,7 @@ def __init__(self,parent,title,text,table,spins=[],): if not j%2: tableSizer.Add((20,0)) j += 1 - + def OnPrintOps(event): print(' Symmetry operations for %s:'%self.text[0].split(':')[1]) for iop,opText in enumerate(G2spc.TextOps(self.text,self.table,reverse=True)): @@ -4475,12 +4477,12 @@ def OnPrintOps(event): print('%s,+%d'%(opText.replace(' ',''),self.spins[iop])) else: print('%s,%d'%(opText.replace(' ',''),self.spins[iop])) - else: + else: print(opText.replace(' ','')) - + def OnAlt(event): self.useAlt = altBtn.GetValue() - + mainSizer.Add(tableSizer,0,wx.ALIGN_LEFT) btnsizer = wx.BoxSizer(wx.HORIZONTAL) OKbtn = wx.Button(self.panel, wx.ID_OK) @@ -4559,7 +4561,7 @@ def __init__(self,parent,title,text,table,Cents,names,spins,ifGray): if ')' not in item: continue flds = item.split(')')[1] - tableSizer.Add(wx.StaticText(self.panel,label=' (%2d) '%(j+1)),0,WACV) + tableSizer.Add(wx.StaticText(self.panel,label=' (%2d) '%(j+1)),0,WACV) flds = flds.replace(' ','').split(',') for i,fld in enumerate(flds): if i < ncol-1: @@ -4590,12 +4592,12 @@ def __init__(self,parent,title,text,table,Cents,names,spins,ifGray): tableSizer.Add((20,0)) j += 1 mainSizer.Add(tableSizer,0) - - + + def OnPrintOps(event): for item in self.PrintTable: print(item) - + btnsizer = wx.BoxSizer(wx.HORIZONTAL) OKbtn = wx.Button(self.panel, wx.ID_OK) btnsizer.Add(OKbtn) @@ -4605,7 +4607,7 @@ def OnPrintOps(event): OKbtn.SetFocus() mainSizer.Add((0,10)) mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER) - + self.panel.SetSizer(mainSizer) self.panel.SetAutoLayout(True) self.panel.SetScrollRate(10,10) @@ -4617,7 +4619,7 @@ def Show(self): ''' self.ShowModal() return - + ################################################################################ class DisAglDialog(wx.Dialog): @@ -4629,7 +4631,7 @@ class DisAglDialog(wx.Dialog): :param dict data: a dict containing the current search ranges or an empty dict, which causes default values to be used. - Will be used to set element `DisAglCtls` in + Will be used to set element `DisAglCtls` in :ref:`Phase Tree Item ` :param dict default: A dict containing the default search ranges for each element. @@ -4640,7 +4642,7 @@ def __init__(self,parent,data,default,Reset=True,Angle=True): text = 'Distance Angle Controls' if not Angle: text = 'Distance Controls' - wx.Dialog.__init__(self,parent,wx.ID_ANY,text, + wx.Dialog.__init__(self,parent,wx.ID_ANY,text, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.default = default self.Reset = Reset @@ -4649,7 +4651,7 @@ def __init__(self,parent,data,default,Reset=True,Angle=True): self._default(data,self.default) self.Draw(self.data) self.CenterOnParent() - + def _default(self,data,default): '''Set starting values for the search values, either from the input array or from defaults, if input is null @@ -4673,7 +4675,7 @@ def Draw(self,data): mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(wx.StaticText(self.panel,-1,'Controls for phase '+data['Name']),0,wx.LEFT,10) mainSizer.Add((10,10),1) - + ncol = 3 if not self.Angle: ncol=2 @@ -4700,7 +4702,7 @@ def Draw(self,data): bondFact = ValidatedTxtCtrl(self.panel,data['Factors'],i,nDig=(10,3)) factorSizer.Add(bondFact) mainSizer.Add(factorSizer,0,wx.EXPAND) - + OkBtn = wx.Button(self.panel,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, self.OnOk) btnSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -4715,30 +4717,30 @@ def Draw(self,data): self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetData(self): 'Returns the values from the dialog' return self.data - + def OnOk(self,event): 'Called when the OK button is pressed' parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnReset(self,event): 'Called when the Reset button is pressed' data = {} self._default(data,self.default) wx.CallAfter(self.Draw,self.data) - + ################################################################################ class ShowLSParms(wx.Dialog): '''Create frame to show least-squares parameters ''' def __init__(self,G2frame,title,parmDict,varyList,fullVaryList, Controls, size=(650,430)): - + wx.Dialog.__init__(self,G2frame,wx.ID_ANY,title,size=size, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) self.parmChoice = 'Phase' @@ -4762,7 +4764,7 @@ def __init__(self,G2frame,title,parmDict,varyList,fullVaryList, self.frozenList = [] # make lists of variables of different types along with lists of parameter names, histogram #s, phase #s,... self.parmNames = sorted(list(parmDict.keys())) - if '2' in platform.python_version_tuple()[0]: + if '2' in platform.python_version_tuple()[0]: basestr = basestring else: basestr = str @@ -4784,13 +4786,13 @@ def __init__(self,G2frame,title,parmDict,varyList,fullVaryList, hapNames = [':'.join(item) for item in splitNames if item[0] and item[1]] self.choiceDict['Phase/Histo'] = G2obj.SortVariables(hapNames) self.hapVars = sorted(list(set([' ',]+[item[2] for item in splitNames if item[0] and item[1]]))) - + self.hisNum = '*' self.phasNum = '*' self.varName = ' ' self.listSel = 'Refined' self.DrawPanel() - + def repaintScrollTbl(self): '''Shows the selected variables in a ListCtrl ''' @@ -4799,7 +4801,7 @@ def repaintScrollTbl(self): self.SendSizeEvent() #if GSASIIpath.GetConfigValue('debug'): # print('repaintScrollTbl',time.time()-start) - + def DrawPanel(self): '''Draws the contents of the entire dialog. Called initially & when radio buttons are pressed ''' @@ -4810,7 +4812,7 @@ def _OnParmSel(event): varSel.SetSelection(0) self.varName = ' ' wx.CallLater(100,self.DrawPanel) - + def OnPhasSel(event): 'phase has been selected' event.Skip() @@ -4834,7 +4836,7 @@ def OnHistSel(event): varSel.SetSelection(0) self.varName = ' ' wx.CallAfter(self.repaintScrollTbl) - + def OnVarSel(event): 'parameter name has been selected' event.Skip() @@ -4852,11 +4854,11 @@ def OnVarSel(event): histSel.SetSelection(0) self.hisNum = '*' wx.CallAfter(self.repaintScrollTbl) - + def OnListSel(event): self.listSel = listSel.GetStringSelection() wx.CallLater(100,self.DrawPanel) - + def OnVarSpin(event): '''Respond when any of the SpinButton widgets are pressed''' event.Skip() @@ -4922,11 +4924,11 @@ def AddSpinner(varSizer,label,SelCtrl,binding): parmSel.Bind(wx.EVT_RADIOBOX,_OnParmSel) parmSel.SetStringSelection(self.parmChoice) parmSizer.Add(parmSel,0) - - selectionsSizer = wx.BoxSizer(wx.VERTICAL) + + selectionsSizer = wx.BoxSizer(wx.VERTICAL) varSizer = wx.BoxSizer(wx.VERTICAL) varSel = None - if self.parmChoice != 'Global': + if self.parmChoice != 'Global': if self.parmChoice in ['Phase',]: varSel = wx.ComboBox(self,choices=self.phasVars,value=self.varName, style=wx.CB_READONLY|wx.CB_DROPDOWN) @@ -4938,7 +4940,7 @@ def AddSpinner(varSizer,label,SelCtrl,binding): style=wx.CB_READONLY|wx.CB_DROPDOWN) AddSpinner(varSizer,'Parameter',varSel,OnVarSel) selectionsSizer.Add(varSizer,0) - + varSizer = wx.BoxSizer(wx.HORIZONTAL) phasSel = None if self.parmChoice in ['Phase','Phase/Histo'] and len(self.phasNums) > 1: @@ -4963,7 +4965,7 @@ def AddSpinner(varSizer,label,SelCtrl,binding): if fcount: refChoices += ['Frozen'] txt += '\n"F" indicates a variable that is Frozen due to exceeding min/max' - + listSel = wx.RadioBox(self,wx.ID_ANY,'Refinement Status:', choices=refChoices, majorDimension=0,style=wx.RA_SPECIFY_COLS) @@ -4971,24 +4973,24 @@ def AddSpinner(varSizer,label,SelCtrl,binding): listSel.Bind(wx.EVT_RADIOBOX,OnListSel) parmSizer.Add(listSel,0,wx.CENTER|wx.ALL,15) mainSizer.Add(parmSizer,0) - + self.countSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(self.countSizer) - self.headSizer = wx.BoxSizer(wx.HORIZONTAL) # non-scrolling header + self.headSizer = wx.BoxSizer(wx.HORIZONTAL) # non-scrolling header mainSizer.Add(self.headSizer,0) self.varBox = VirtualVarBox(self) mainSizer.Add(self.varBox,1,wx.ALL|wx.EXPAND,1) mainSizer.Add( wx.StaticText(self,label=txt),0, wx.ALL,0) - - btnsizer = wx.BoxSizer(wx.HORIZONTAL) # make Close button - btn = wx.Button(self, wx.ID_CLOSE,"Close") + + btnsizer = wx.BoxSizer(wx.HORIZONTAL) # make Close button + btn = wx.Button(self, wx.ID_CLOSE,"Close") btn.Bind(wx.EVT_BUTTON,self._onClose) btnsizer.Add(btn) mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) self.SetSizer(mainSizer) wx.CallAfter(self.repaintScrollTbl) - + def _onClose(self,event): self.EndModal(wx.ID_CANCEL) @@ -5005,7 +5007,7 @@ def __init__(self, parent): for i,(lbl,wid) in enumerate(zip( ('#', "Parameter", "Ref", "Value", "Min", "Max", "Explanation"), - (40 , 125 , 30 , 100 , 75 , 75 , 700),)): + (40 , 125 , 30 , 100 , 75 , 75 , 700),)): self.InsertColumn(i, lbl) self.SetColumnWidth(i, wid) @@ -5022,7 +5024,7 @@ def __init__(self, parent): def SetContents(self,parent): self.varList = [] for name in parent.choiceDict[parent.parmChoice]: - if '2' in platform.python_version_tuple()[0]: + if '2' in platform.python_version_tuple()[0]: basestr = basestring else: basestr = str @@ -5042,11 +5044,11 @@ def SetContents(self,parent): self.varList.append(name) oldlen = self.GetItemCount() self.SetItemCount(len(self.varList)) - + def OnRowSelected(self, event, row=None): 'Creates an edit window when a parameter is selected' def ResetFrozen(event): - '''release a frozen parameter (from all histograms in the case of a + '''release a frozen parameter (from all histograms in the case of a sequential fit). ''' if name in self.parmWin.frozenList: @@ -5078,7 +5080,7 @@ def delMafter(d,name): if val is not None: del d[key] self.OnRowSelected(None, row) - + def AddM(event): 'Get event info & add a Max or Min limit' if hasattr(event.EventObject,'max'): @@ -5106,7 +5108,7 @@ def SetWild(event): ns[1] = '*' else: ns[3] = '*' - wname = ':'.join(ns) + wname = ':'.join(ns) # close sub-dialog then delete item and redraw dlg.EndModal(wx.ID_OK) wx.CallAfter(SetWildAfter,d,name,wname,event.EventObject.GetValue()) @@ -5127,7 +5129,7 @@ def SetWildAfter(d,name,wname,mode): d[G2obj.G2VarObj(name)] = val self.OnRowSelected(None, row) - # start of OnRowSelected + # start of OnRowSelected if event is not None: row = event.Index elif row is None: @@ -5190,7 +5192,7 @@ def SetWildAfter(d,name,wname,mode): d = self.parmWin.Controls[key] n,v = G2obj.prmLookup(name,d) if v is not None and str(n) == name: - try: # strange hard to reproduce problem with this not working + try: # strange hard to reproduce problem with this not working del d[n] except: if GSASIIpath.GetConfigValue('debug'): @@ -5198,7 +5200,7 @@ def SetWildAfter(d,name,wname,mode): else: n,val = G2obj.prmLookup(name,self.parmWin.Controls['parmMinDict']) # is this a wild-card? - if val is None: + if val is None: addMbtn = wx.Button(dlg, wx.ID_ANY,'Add Lower limit') addMbtn.Bind(wx.EVT_BUTTON, AddM) mainSizer.Add(addMbtn,0) @@ -5227,7 +5229,7 @@ def SetWildAfter(d,name,wname,mode): # draw max value widgets mainSizer.Add((-1,10),0) n,val = G2obj.prmLookup(name,self.parmWin.Controls['parmMaxDict']) # is this a wild-card? - if val is None: + if val is None: addMbtn = wx.Button(dlg, wx.ID_ANY,'Add Upper limit') addMbtn.Bind(wx.EVT_BUTTON, AddM) addMbtn.max = True @@ -5257,7 +5259,7 @@ def SetWildAfter(d,name,wname,mode): wild.max = True subSizer.Add(wild,0,WACV) mainSizer.Add(subSizer,0) - + btnsizer = wx.StdDialogButtonSizer() OKbtn = wx.Button(dlg, wx.ID_OK) OKbtn.SetDefault() @@ -5276,7 +5278,7 @@ def SetWildAfter(d,name,wname,mode): return dlg.Destroy() self.parmWin.SendSizeEvent() - + #----------------------------------------------------------------- # Callbacks to display info in table def OnGetItemText(self, item, col): @@ -5328,7 +5330,7 @@ def OnGetItemAttr(self, item): else: return None -##### Customized Grid Support ################################################################################ +##### Customized Grid Support ################################################################################ class GSGrid(wg.Grid): '''Basic wx.Grid implementation ''' @@ -5337,13 +5339,13 @@ def __init__(self, parent, name=''): if hasattr(parent.TopLevelParent,'currentGrids'): parent.TopLevelParent.currentGrids.append(self) # save a reference to the grid in the Frame self.SetScrollRate(0,0) #GSAS-II grids have no scroll bars by default - + def Clear(self): wg.Grid.ClearGrid(self) - + def SetCellReadOnly(self,r,c,readonly=True): self.SetReadOnly(r,c,isReadOnly=readonly) - + def SetCellStyle(self,r,c,color="white",readonly=True): self.SetCellBackgroundColour(r,c,color) self.SetReadOnly(r,c,isReadOnly=readonly) @@ -5427,13 +5429,13 @@ def OnMouseMotion(event): def setupPopup(self,lblList,callList): '''define a callback that creates a popup menu. The rows associated - with the items selected items are selected in the table and if - an item is called from the menu, the corresponding function - is called to perform an action on the + with the items selected items are selected in the table and if + an item is called from the menu, the corresponding function + is called to perform an action on the - :param list lblList: list of str items that will be placed in the + :param list lblList: list of str items that will be placed in the popup menu - :param list callList: list of functions to be called when a + :param list callList: list of functions to be called when a :returns: a callback that can be used to create the menu Sample usage:: @@ -5480,15 +5482,15 @@ def OnPopup(event): self.PopupMenu(menu) menu.Destroy() return createPopup - + def completeEdits(self): 'complete any outstanding edits' if self.IsCellEditControlEnabled(): # complete any grid edits in progress self.SaveEditControlValue() self.HideCellEditControl() self.DisableCellEditControl() - -################################################################################ + +################################################################################ class Table(wg.GridTableBase): '''Basic data table for use with GSgrid ''' @@ -5498,11 +5500,11 @@ def __init__(self, data=[], rowLabels=None, colLabels=None, types = None): self.rowLabels = rowLabels self.dataTypes = types self.data = data - + def AppendRows(self, numRows=1): self.data.append([]) return True - + def CanGetValueAs(self, row, col, typeName): if self.dataTypes: colType = self.dataTypes[col].split(':')[0] @@ -5524,42 +5526,42 @@ def DeleteRow(self,pos): if irow != pos: new.append(row) self.SetData(new) - + def GetColLabelValue(self, col): if self.colLabels: return self.colLabels[col] - + def GetData(self): data = [] for row in range(self.GetNumberRows()): data.append(self.GetRowValues(row)) return data - + def GetNumberCols(self): try: return len(self.colLabels) except TypeError: return None - + def GetNumberRows(self): return len(self.data) - + def GetRowLabelValue(self, row): if self.rowLabels: return self.rowLabels[row] - + def GetColValues(self, col): data = [] for row in range(self.GetNumberRows()): data.append(self.GetValue(row, col)) return data - + def GetRowValues(self, row): data = [] for col in range(self.GetNumberCols()): data.append(self.GetValue(row, col)) return data - + def GetTypeName(self, row, col): try: if self.data[row][col] is None: @@ -5574,48 +5576,48 @@ def GetValue(self, row, col): return self.data[row][col] except IndexError: return None - + def InsertRows(self, pos, rows): for row in range(rows): self.data.insert(pos,[]) pos += 1 - + def IsEmptyCell(self,row,col): try: return not self.data[row][col] except IndexError: return True - + def OnKeyPress(self, event): dellist = self.GetSelectedRows() if event.GetKeyCode() == wx.WXK_DELETE and dellist: grid = self.GetView() for i in dellist: grid.DeleteRow(i) - + def SetColLabelValue(self, col, label): numcols = self.GetNumberCols() if col > numcols-1: self.colLabels.append(label) else: self.colLabels[col]=label - + def SetData(self,data): for row in range(len(data)): self.SetRowValues(row,data[row]) - + def SetRowLabelValue(self, row, label): self.rowLabels[row]=label - + def SetRowValues(self,row,data): self.data[row] = data - + def SetValue(self, row, col, value): def innerSetValue(row, col, value): try: self.data[row][col] = value except TypeError: return - except IndexError: # has this been tested? + except IndexError: # has this been tested? #print row,col,value if self.GetNumberRows() == 0: return # add a new row @@ -5631,11 +5633,11 @@ def innerSetValue(row, col, value): ################################################################################ class GridFractionEditor(wg.PyGridCellEditor): '''A grid cell editor class that allows entry of values as fractions as well - as sine and cosine values [as s() and c(), sin() or sind(), etc]. Any valid - Python expression will be evaluated. + as sine and cosine values [as s() and c(), sin() or sind(), etc]. Any valid + Python expression will be evaluated. - The current value can be incremented, multiplied or divided by prefixing - an expression by +, * or / respectively. + The current value can be incremented, multiplied or divided by prefixing + an expression by +, * or / respectively. ''' def __init__(self,grid): if 'phoenix' in wx.version(): @@ -5672,7 +5674,7 @@ def EndEdit(self, row, col, grid, oldVal=None): self.nextval = self.startValue val = self._tc.GetValue().lower().strip() - val = val.replace(',','.') # allow , for decimal + val = val.replace(',','.') # allow , for decimal if val != str(self.startValue): changed = True neg = False @@ -5717,7 +5719,7 @@ def EndEdit(self, row, col, grid, oldVal=None): self.startValue = '' self._tc.SetValue('') return changed - + def ApplyEdit(self, row, col, grid): """ Called only in wx >= 2.9 Save the value of the control into the grid if EndEdit() returns as True @@ -5747,7 +5749,7 @@ def OnChar(self, evt): ##### Get an output file or directory ################################################################################ def askSaveFile(G2frame,defnam,extension,longFormatName,parent=None): - '''Ask the user to supply a file name; used for svn + '''Ask the user to supply a file name :param wx.Frame G2frame: The main GSAS-II window :param str defnam: a default file name @@ -5780,7 +5782,7 @@ def askSaveFile(G2frame,defnam,extension,longFormatName,parent=None): def askSaveDirectory(G2frame): '''Ask the user to supply a directory name. Path name is used as the - starting point for the next export path search. + starting point for the next export path search. :returns: a directory name (str) or None if Cancel is pressed ''' @@ -5797,7 +5799,7 @@ def askSaveDirectory(G2frame): dlg.Destroy() return filename -##### Customized Notebook ################################################################################ +##### Customized Notebook ################################################################################ class GSNoteBook(wx.aui.AuiNotebook): '''Notebook used in various locations; implemented with wx.aui extension ''' @@ -5806,13 +5808,13 @@ def __init__(self, parent, name='',size = None,style=wxaui_NB_TOPSCROLL): if size: self.SetSize(size) self.parent = parent self.PageChangeHandler = None - + def PageChangeEvent(self,event): pass - + def Clear(self): GSNoteBook.DeleteAllPages(self) - + def FindPage(self,name): numPage = self.GetPageCount() for page in range(numPage): @@ -5843,7 +5845,7 @@ def ChangeSelection(self,page): # return newfunc # else: # return attr - + #### Help support routines ################################################################################ class MyHelp(wx.Menu): ''' @@ -5870,7 +5872,7 @@ def __init__(self,frame,includeTree=False,morehelpitems=[]): self.Append(wx.ID_ABOUT,'&About GSAS-II', 'Shows version and citation info') frame.Bind(wx.EVT_MENU, self.OnHelpAbout, id=wx.ID_ABOUT) - if GSASIIpath.HowIsG2Installed(): + if GSASIIpath.HowIsG2Installed().startswith('git'): helpobj = self.Append(wx.ID_ANY,'&Check for updates\tCtrl+U', 'Updates to latest GSAS-II version') if os.access(GSASIIpath.path2GSAS2, os.W_OK): @@ -5884,10 +5886,15 @@ def __init__(self,frame,includeTree=False,morehelpitems=[]): else: helpobj.Enable(False) if (GSASIIpath.HowIsG2Installed().startswith('git') - and GSASIIpath.GetConfigValue('debug')): + and GSASIIpath.GetConfigValue('debug')): helpobj = self.Append(wx.ID_ANY,'Switch to/from branch', 'Switch to/from a GSAS-II development branch') frame.Bind(wx.EVT_MENU, gitSelectBranch, helpobj) + # test if conda present? + helpobj = self.Append(wx.ID_ANY,'Add packages for more functionality', + 'Install optional Python packages to provide more GSAS-II capabilities') + helpobj.Enable(bool(G2fil.condaRequestList)) + frame.Bind(wx.EVT_MENU, SelectPkgInstall, id=helpobj.GetId()) # provide special help topic names for extra items in help menu for lbl,indx in morehelpitems: helpobj = self.Append(wx.ID_ANY,lbl,'') @@ -5903,7 +5910,10 @@ def __init__(self,frame,includeTree=False,morehelpitems=[]): helpobj = self.Append(wx.ID_ANY,'Help on current data tree item\tF1', 'Access web page on selected item in tree') frame.Bind(wx.EVT_MENU, self.OnHelpById, id=helpobj.GetId()) - + helpobj = self.Append(wx.ID_ANY,'Citation information', + 'Show papers that GSAS-II users may wish to cite') + frame.Bind(wx.EVT_MENU, ShowCitations, id=helpobj.GetId()) + def OnHelpById(self,event): '''Called when Help on... is pressed in a menu. Brings up a web page for documentation. Uses the helpKey value from the dataWindow window @@ -5918,7 +5928,7 @@ def OnHelpById(self,event): else: print('help error: not called from standard menu?') print (self) - return + return try: helpKey = dW.helpKey # look up help from helpKey in data window #if GSASIIpath.GetConfigValue('debug'): print 'DBG_dataWindow help: key=',helpKey @@ -5944,24 +5954,22 @@ def OnHelpAbout(self, event): info = wxadv.AboutDialogInfo() info.Name = 'GSAS-II' info.SetVersion(GSASIIpath.getG2VersionInfo()) - #info.Developers = ['Robert B. Von Dreele','Brian H. Toby'] - info.Copyright = ('(c) ' + time.strftime('%Y') + -''' Argonne National Laboratory -This product includes software developed -by the UChicago Argonne, LLC, as -Operator of Argonne National Laboratory.''') - info.Description = '''General Structure Analysis System-II (GSAS-II) -Robert B. Von Dreele and Brian H. Toby - -Please cite as: - B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013) -For small angle use cite: - R.B. Von Dreele, J. Appl. Cryst. 47, 1748-9 (2014) -For DIFFaX use cite: - M.M.J. Treacy, J.M. Newsam & M.W. Deem, - Proc. Roy. Soc. Lond. A 433, 499-520 (1991) -''' + info.Developers = ['Robert B. Von Dreele','Brian H. Toby'] info.WebSite = ("https://gsasii.github.io","GSAS-II home page") + msg = '''Argonne National Laboratory +This product includes software developed +by the UChicago Argonne, LLC, as Operator +of Argonne National Laboratory.''' + info.Copyright = f'(c) {time.strftime("%Y")} {msg}' + msg = '''General Structure Analysis System-II (GSAS-II). Please cite as: + B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. + 46, 544-549 (2013) +Also see Help/"Citation information" for other works used in GSAS-II. Citations encourage scientists to make their software available.''' +# msg += '\n' +# for key in CitationDict: +# msg += f"\n * For {key} use cite:\n" +# msg += GetCite(key,wrap=50,indent=3) + info.Description = msg wxadv.AboutBox(info) def OnCheckUpdates(self,event): @@ -5969,35 +5977,7 @@ def OnCheckUpdates(self,event): and perform that update if requested. ''' if GSASIIpath.HowIsG2Installed().startswith('git'): - # Patch: switch from master to main - if GSASIIpath.getG2Branch() == 'master': - gitSwitchMaster2Main() - return gitCheckUpdates(self.frame) - elif GSASIIpath.HowIsG2Installed().startswith('svn'): - msg = ''' -As of October 2024, updates for GSAS-II are no longer available from the APS -subversion server (where your current version of GSAS-II was installed from.) - -To obtain updates to GSAS-II, you must reinstall GSAS-II from https://bit.ly/G2download -(https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/tag/v1.0.1). - -See web page GSASII.github.io for information on how to install. -''' - res = ShowScrolledInfo(self.frame,msg,header='Please Note', - height=200, - buttonlist=[ - ('Open download site', - lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK)), - ('Skip download for now', - lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_CANCEL)) - ]) - if res == wx.ID_OK: - ShowWebPage( - 'https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/tag/v1.0.1', - self.frame, - browser=True) - svnCheckUpdates(self.frame) else: dlg = wx.MessageDialog(self.frame, 'Cannot update GSAS-II because it was not installed with a version control system or the VCS system could not be accessed.', @@ -6011,8 +5991,6 @@ def OnSelectVersion(self,event): ''' if GSASIIpath.HowIsG2Installed().startswith('git'): gitSelectVersion(self.frame) - elif GSASIIpath.HowIsG2Installed().startswith('svn'): - svnSelectVersion(self.frame) else: dlg = wx.MessageDialog(self.frame, 'Cannot update GSAS-II because it was not installed with a version control system or the VCS system could not be accessed.', @@ -6021,30 +5999,62 @@ def OnSelectVersion(self,event): dlg.Destroy() return +def ShowCitations(event): + '''Show all work that GSAS-II users may wish to cite + ''' + parent = wx.GetApp().GetMainTopWindow() + def copy2clip(event): + 'copy citation info to clipboard' + if wx.TheClipboard.Open(): + wx.TheClipboard.SetData(wx.TextDataObject(msg)) + wx.TheClipboard.Close() + else: + G2frame.ErrorDialog('Clipboard locked','Sorry, unable to access the clipboard, try again later. You might need to restart GSAS-II or reboot') + return + G2MessageBox(parent, + 'Citation information placed in clipboard. ', + 'Citations copied') + event.GetEventObject().GetParent().EndModal(wx.ID_OK) + msg = '''You are using GSAS-II. Please cite it as: + + B.H. Toby & R.B. Von Dreele, J. Appl. Cryst. 46, 544-549 (2013). + +Depending on what sections of the code you are using, you may wish to +cite some of the following works as well:''' + for key in CitationDict: + msg += f"\n\n * For {key} use cite:\n" + msg += GetCite(key,wrap=95,indent=6) + msg += '\n\nNote that your citations are one of the strongest ways you can say thank you to the\nscientists who make their software available to you.' + res = ShowScrolledInfo(parent,msg,header='Please Cite', + buttonlist=[ + ('Close', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK)), + ('Copy to clipboard', copy2clip), + ]) + ################################################################################ class HelpButton(wx.Button): '''Create a help button that displays help information. - The text can be displayed in a modal message window or it can be - a reference to a location in the gsasII.html (etc.) help web page, in which - case that page is opened in a web browser. + The text can be displayed in a modal message window or it can be + a reference to a location in the gsasII.html (etc.) help web page, in which + case that page is opened in a web browser. TODO: it might be nice if it were non-modal: e.g. it stays around until the parent is deleted or the user closes it, but this did not work for - me. + me. :param parent: the panel/frame where the button will be placed - :param str msg: the help text to be displayed. Indentation on + :param str msg: the help text to be displayed. Indentation on multiline help text is stripped (see :func:`StripIndents`). If wrap - is set as non-zero, all new lines are + is set as non-zero, all new lines are :param str helpIndex: location of the help information in the gsasII.html - help file in the form of an anchor string. The URL will be + help file in the form of an anchor string. The URL will be constructed from: location + gsasII.html + "#" + helpIndex :param int wrap: if specified, the text displayed is reformatted by - wrapping it to fit in wrap pixels. Default is None which prevents + wrapping it to fit in wrap pixels. Default is None which prevents wrapping. ''' def __init__(self,parent,msg='',helpIndex='',wrap=None): - if sys.platform == "darwin": + if sys.platform == "darwin": wx.Button.__init__(self,parent,wx.ID_HELP) else: wx.Button.__init__(self,parent,wx.ID_ANY,' ? ',style=wx.BU_EXACTFIT) @@ -6069,7 +6079,7 @@ def _onPress(self,event): if self.helpIndex: ShowHelp(self.helpIndex,self.parent) return - self.dlg = wx.Dialog(self.parent,wx.ID_ANY,'Help information', + self.dlg = wx.Dialog(self.parent,wx.ID_ANY,'Help information', style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) mainSizer = wx.BoxSizer(wx.VERTICAL) txt = wx.StaticText(self.dlg,wx.ID_ANY,self.msg) @@ -6078,7 +6088,7 @@ def _onPress(self,event): mainSizer.Add(txt,1,wx.ALL|wx.EXPAND,10) txt.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(self.dlg, wx.ID_CLOSE) + btn = wx.Button(self.dlg, wx.ID_CLOSE) btn.Bind(wx.EVT_BUTTON,self._onClose) btnsizer.Add(btn) mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) @@ -6090,13 +6100,13 @@ def _onPress(self,event): ################################################################################ updateNoticeDict = {4919:True} # example: {1234:True, 5000:False} '''A dict with versions that should be noted. The value associated with the -tag is if all older projects should show the warning, or only the first -to be opened. +tag is if all older projects should show the warning, or only the first +to be opened. ''' def updateNotifier(G2frame,fileVersion): - '''Posts an update notice when a a specially tagged GSAS-II version + '''Posts an update notice when a a specially tagged GSAS-II version is seen for the first time. Versions to be tagged are set in global - updateNoticeDict; version info is found in file versioninfo.txt. + updateNoticeDict; version info is found in file versioninfo.txt. :param wx.Frame G2frame: GSAS-II main window :param int fileVersion: version of GSAS-II used to create the current @@ -6137,14 +6147,14 @@ def tblLine(dlg,pixels=3): filnam = os.path.join(GSASIIpath.path2GSAS2,'inputs','versioninfo.txt') if not os.path.exists(filnam): # patch 3/2024 for svn dir organization - filnam = os.path.join(GSASIIpath.path2GSAS2,'versioninfo.txt') + filnam = os.path.join(GSASIIpath.path2GSAS2,'versioninfo.txt') if not os.path.exists(filnam): print('Warning: file versioninfo.txt not found') return fp = open(filnam, 'r') vers = None noticeDict = {} - for line in fp: + for line in fp: if line.strip().startswith('#'): continue if vers is not None: if len(line.strip()) == 0: @@ -6221,26 +6231,26 @@ def tblLine(dlg,pixels=3): dlg.Destroy() return dlg.Destroy() - + ################################################################################ def viewWebPage(parent,URL,size=(750,450),newFrame=False,HTML=''): - '''Creates a child wx.Frame with an OS-managed web browser. The window + '''Creates a child wx.Frame with an OS-managed web browser. The window is modeless, so it can be left open without affecting GSAS-II operations, - but will be closed when GSAS-II is ended if a ``parent`` window is + but will be closed when GSAS-II is ended if a ``parent`` window is specified. - The web browser is filled with a supplied URL or HTML text. + The web browser is filled with a supplied URL or HTML text. Reuses the previous window unless ``newFrame`` is set to True. :param wx.Frame parent: name of main GSAS-II window (G2frame), if None a toplevel window is created (probably not a good idea). - :param str URL: web page to be viewed. This is ignored if ``HTML`` + :param str URL: web page to be viewed. This is ignored if ``HTML`` (below) is specified, but argument ``URL`` is not optional. - :param wx.Size size: initial size of Frame to be created. Defaults + :param wx.Size size: initial size of Frame to be created. Defaults to (750,450). - :param bool newFrame: When True, a new frame is opened even if the + :param bool newFrame: When True, a new frame is opened even if the previously-used frame exists. Default is False. - :param str HTML: HTML text of a web page to be displayed. If this + :param str HTML: HTML text of a web page to be displayed. If this is specified, the contents of the URL argument is ignored. :returns: the wx.Frame object used to display the web page @@ -6307,15 +6317,15 @@ def copyURL(event): ################################################################################ def StripIndents(msg,singleLine=False): - '''Strip unintended indentation from multiline strings. + '''Strip unintended indentation from multiline strings. When singleLine is True, all newline are removed, but inserting "%%" into the string will cause a blank line to be inserted at that point and %t% will generate a new line and tab (to indent a line) - :param str msg: a string containing one or more lines of text. + :param str msg: a string containing one or more lines of text. spaces or tabs following a newline are removed. - :param bool singleLine: removes all newlines from the msg so that - the text may be wrapped. + :param bool singleLine: removes all newlines from the msg so that + the text may be wrapped. :returns: the string but reformatted ''' msg1 = msg.replace('\n ','\n') @@ -6339,7 +6349,7 @@ def StripIndents(msg,singleLine=False): def StripUnicode(string,subs='.'): '''Strip non-ASCII characters from strings - + :param str string: string to strip Unicode characters from :param str subs: character(s) to place into string in place of each Unicode character. Defaults to '.' @@ -6358,12 +6368,12 @@ def getTextSize(txt): 'Get the size of the text string txt in points, returns (x,y)' dc = wx.ScreenDC() return tuple(dc.GetTextExtent(txt)) - + # wx classes for reading various types of data files ###################################################################### def BlockSelector(ChoiceList, ParentFrame=None,title='Select a block', size=None, header='Block Selector',useCancel=True): - ''' Provide a wx dialog to select a single block where one must - be selected. Used for selecting for banks for instrument + ''' Provide a wx dialog to select a single block where one must + be selected. Used for selecting for banks for instrument parameters if the file contains more than one set. ''' if useCancel: @@ -6410,12 +6420,12 @@ def MultipleBlockSelector(ChoiceList, ParentFrame=None, class MultipleChoicesDialog(wx.Dialog): '''A dialog that offers a series of choices, each with a - title and a wx.Choice widget. Intended to be used Modally. + title and a wx.Choice widget. Intended to be used Modally. typical input: * choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)] * headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here'] - + selections are placed in self.chosen when OK is pressed Also see GSASIIctrlGUI @@ -6426,7 +6436,7 @@ def __init__(self,choicelist,headinglist, parent=None): self.chosen = [] wx.Dialog.__init__( - self,parent,wx.ID_ANY,head, + self,parent,wx.ID_ANY,head, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -6456,7 +6466,7 @@ def __init__(self,choicelist,headinglist, panel.SetSizer(mainSizer) panel.Fit() self.Fit() - + def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() @@ -6464,30 +6474,30 @@ def OnOk(self,event): self.chosen = [] for w in self.ChItems: self.chosen.append(w.GetSelection()) - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() self.chosen = [] - self.EndModal(wx.ID_CANCEL) + self.EndModal(wx.ID_CANCEL) def MultipleChoicesSelector(choicelist, headinglist, ParentFrame=None, **kwargs): '''A modal dialog that offers a series of choices, each with a title and a wx.Choice widget. Used in :mod:`G2pwd_CIF` only. Typical input: - + * choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)] - + * headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here'] - + optional keyword parameters are: head (window title) and title returns a list of selected indicies for each choice (or None) ''' result = None dlg = MultipleChoicesDialog(choicelist,headinglist, - parent=ParentFrame, **kwargs) + parent=ParentFrame, **kwargs) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: result = dlg.chosen @@ -6496,7 +6506,7 @@ def MultipleChoicesSelector(choicelist, headinglist, ParentFrame=None, **kwargs) def PhaseSelector(ChoiceList, ParentFrame=None, title='Select a phase', size=None,header='Phase Selector'): - ''' Provide a wx dialog to select a phase, used in importers if a file + ''' Provide a wx dialog to select a phase, used in importers if a file contains more than one phase ''' return BlockSelector(ChoiceList,ParentFrame,title, @@ -6511,7 +6521,7 @@ def XformMatrix(panel,Trans,Uvec,Vvec,OnLeave=None,OnLeaveArgs={}): Trmat.Add((10,0),0) Trmat.Add(wx.StaticText(panel,label=' U'),wx.ALIGN_CENTER) Trmat.Add(wx.StaticText(panel,label=' V'),wx.ALIGN_CENTER) - + for iy,line in enumerate(Trans): for ix,val in enumerate(line): item = ValidatedTxtCtrl(panel,Trans[iy],ix,nDig=(10,3),size=(65,25), @@ -6528,8 +6538,8 @@ def XformMatrix(panel,Trans,Uvec,Vvec,OnLeave=None,OnLeaveArgs={}): def showUniqueCell(frame,cellSizer,row,cell,SGData=None, editAllowed=False,OnCellChange=None): - '''function to put cell values into a GridBagSizer. - First column (#0) is reserved for labels etc. + '''function to put cell values into a GridBagSizer. + First column (#0) is reserved for labels etc. if editAllowed is True, values are placed in a wx.TextCtrl and if needed two rows are used in the table. ''' @@ -6569,7 +6579,7 @@ def showUniqueCell(frame,cellSizer,row,cell,SGData=None, cellSizer.Add(wx.StaticText(frame,label=txt),(cellrow,col)) if editAllowed and indx >= 0: Fmt = (10,5) - if '.3' in fmt: Fmt = (10,3) + if '.3' in fmt: Fmt = (10,3) cellVal = ValidatedTxtCtrl(frame,cell,indx, xmin=0.1,xmax=500.,nDig=Fmt,OnLeave=OnCellChange) cellSizer.Add(cellVal,(cellrow,col+1)) @@ -6578,7 +6588,7 @@ def showUniqueCell(frame,cellSizer,row,cell,SGData=None, cellSizer.Add(wx.StaticText(frame,label=fmt.format(cell[abs(indx)])),(cellrow,col+1)) #volume volCol = 13 - if editAllowed: + if editAllowed: volCol = 8 cellSizer.Add(wx.StaticText(frame,label=' Vol = '),(row,volCol)) if editAllowed: @@ -6591,7 +6601,7 @@ def showUniqueCell(frame,cellSizer,row,cell,SGData=None, ################################################################################ -# configuration routines (for editing config.py) +# configuration settings routines def SaveGPXdirectory(path,write=True): if GSASIIpath.GetConfigValue('Starting_directory') == path: return vars = GetConfigValsDocs() @@ -6626,7 +6636,7 @@ def GetConfigValsDocs(): * item 3: the "docstring" that follows variable definition ''' - import config_example + from . import config_example import ast fname = os.path.splitext(config_example.__file__)[0]+'.py' # convert .pyc to .py with open(fname, 'r',encoding='utf-8') as f: @@ -6649,113 +6659,23 @@ def GetConfigValsDocs(): key = None return d -inhibitSave = False def SaveConfigVars(vars,parent=None): - '''Write the current config variable values to config.py + '''Write the current config variable values to ~/.GSASII/config.ini :params dict vars: a dictionary of variable settings and meanings as - created in :func:`GetConfigValsDocs`. - :param parent: wx.Frame object or None (default) for parent - of error message if no file can be written. + created in :func:`GetConfigValsDocs`. Most of the information gathered + in GetConfigValsDocs is no longer used here. + :param parent: wx.Frame object or None. No longer used. :returns: True if unable to write the file, None otherwise ''' - if inhibitSave: - if GSASIIpath.GetConfigValue('debug'): - print('inhibitSave prevents saving configuration') - return - - # try to write to where an old config file is located - try: - import config - savefile = config.__file__ - except ImportError: # no config.py file yet - savefile = os.path.join(GSASIIpath.path2GSAS2,'config.py') - except Exception: # import failed - # find the bad file, save it in a new name and prepare to overwrite it - for p in sys.path: - savefile = os.path.join(p,'config.py') - if os.path.exists(savefile): - import distutils.file_util as dfu - keepfile = os.path.join(p,'config.py_corrupt') - print('Current config file contains an error:',savefile) - print('saving that file as',keepfile) - dfu.copy_file(savefile,keepfile) - print('preparing to overwrite...') - break - else: - print('unexpected error importing config.py') - savefile = os.path.join(GSASIIpath.path2GSAS2,'config.py') - - # try to open file for write - try: - savefile = os.path.splitext(savefile)[0]+'.py' # convert .pyc to .py - fp = open(savefile,'w') - except IOError: # can't write there, write in local mods directory - # create a local mods directory, if needed - g2local = os.path.expanduser('~/.G2local/') - if not os.path.exists(g2local): - try: - print(u'Creating directory '+g2local) - os.mkdir(g2local) - except: - if parent: - G2MessageBox(parent, - f'Error trying to create directory {g2local}. Unable to save') - else: - print(u'Error trying to create directory '+g2local) - return True - sys.path.insert(0,os.path.expanduser('~/.G2local/')) - savefile = os.path.join(os.path.expanduser('~/.G2local/'),'config.py') - try: - fp = open(savefile,'w') - except IOError: - if parent: - G2MessageBox(parent,'Error trying to write configuration to '+savefile, - 'Unable to save') - else: - print('Error trying to write configuration to '+savefile) - return True - import datetime - fp.write("# -*- coding: utf-8 -*-\n'''\n") - fp.write("*config.py: Configuration options*\n----------------------------------\n") - fp.write("This file created in SelectConfigSetting on {:%d %m %Y %H:%M}\n". - format(datetime.datetime.now())) - fp.write("'''\n\n") - fp.write("import os.path\n") - fp.write("import GSASIIpath\n\n") + configDict = {} for var in sorted(vars.keys(),key=lambda s: s.lower()): if vars[var][1] is None: continue if vars[var][1] == '': continue if vars[var][0] == vars[var][1]: continue - try: - float(vars[var][1]) # test for number - fp.write(var + ' = ' + str(vars[var][1])+'\n') - except: - if type(vars[var][1]) is list: - fp.write(var + ' = [\n') - for varstr in vars[var][1]: - if '\\' in varstr: - fp.write('\t os.path.normpath("' + varstr.replace('\\','/') +'"),\n') - else: - fp.write('\t "' + str(varstr)+'",\n') - fp.write(' ]\n') - elif type(vars[var][1]) is tuple: - fp.write(var + ' = ' + str(vars[var][1])+'\n') - else: - try: - eval(vars[var][1]) # test for an expression - fp.write(var + ' = ' + str(vars[var][1])+'\n') - except: # must be a string - varstr = vars[var][1] - if '\\' in varstr: - fp.write(var + ' = os.path.normpath("' + varstr.replace('\\','/') +'")\n') - else: - fp.write(var + ' = "' + str(varstr)+'"\n') - if vars[var][3]: - fp.write("'''" + str(vars[var][3]) + "\n'''\n\n") - fp.close() - print('wrote file '+savefile) - + configDict[var] = vars[var][1] + return GSASIIpath.WriteConfig(configDict) + class SelectConfigSetting(wx.Dialog): '''Dialog to select configuration variables and set associated values. ''' @@ -6782,7 +6702,7 @@ def __init__(self,parent): self.varsizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.varsizer,1,wx.ALL|wx.EXPAND,1) - + self.doclbl = wx.StaticBox(self, wx.ID_ANY, "") self.doclblsizr = wx.StaticBoxSizer(self.doclbl) self.docinfo = wx.StaticText(self, wx.ID_ANY, "") @@ -6790,18 +6710,18 @@ def __init__(self,parent): self.sizer.Add(self.doclblsizr, 0, wx.EXPAND|wx.ALL, 5) btnsizer = wx.BoxSizer(wx.HORIZONTAL) self.saveBtn = wx.Button(self,-1,"Save current settings") - btnsizer.Add(self.saveBtn, 0, wx.ALL, 2) + btnsizer.Add(self.saveBtn, 0, wx.ALL, 2) self.saveBtn.Bind(wx.EVT_BUTTON, self.OnSave) self.saveBtn.Enable(False) - + btn = wx.Button(self,wx.ID_CANCEL) - btnsizer.Add(btn, 0, wx.ALL, 2) - self.sizer.Add(btnsizer, 0, wx.ALIGN_CENTRE|wx.ALL, 5) - + btnsizer.Add(btn, 0, wx.ALL, 2) + self.sizer.Add(btnsizer, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + self.SetSizer(self.sizer) self.sizer.Fit(self) self.CenterOnParent() - + def OnChange(self,event=None): ''' Check if anything been changed. Turn the save button on/off. ''' @@ -6839,25 +6759,13 @@ def ShowColor(self): self.colorChip.SetBackgroundColour('yellow') self.colorChip.SetForegroundColour('black') - def OnApplyChanges(self,event=None): - 'Set config variables to match the current settings' - GSASIIpath.SetConfigValue(self.vars) - self.EndModal(wx.ID_OK) - global inhibitSave - if event is not None: inhibitSave = True - import GSASIImpsubs as G2mp - G2mp.ResetMP() - def OnSave(self,event): - '''Write the config variables to config.py and then set them + '''Write the config variables to ~/.GSASII/config.ini as the current settings ''' - global inhibitSave - inhibitSave = False - if not SaveConfigVars(self.vars,parent=self): - self.OnApplyChanges() # force a reload of the config settings - else: - self.EndModal(wx.ID_OK) + SaveConfigVars(self.vars,parent=self) + GSASIIpath.SetConfigValue(self.vars) + self.EndModal(wx.ID_OK) def OnBoolSelect(self,event): 'Respond to a change in a True/False variable' @@ -6866,14 +6774,14 @@ def OnBoolSelect(self,event): self.vars[var][1] = (rb.GetSelection() == 0) self.OnChange() wx.CallAfter(self.OnSelection) - + def onSelDir(self,event): 'Select a directory from a menu' dlg = wx.DirDialog(self, "Choose a directory:",style=wx.DD_DEFAULT_STYLE) if dlg.ShowModal() == wx.ID_OK: var = self.choice[0] self.vars[var][1] = dlg.GetPath() - self.strEd.SetValue(self.vars[var][1]) + self.strEd.ChangeValue(self.vars[var][1]) self.OnChange() dlg.Destroy() @@ -6891,7 +6799,7 @@ def onSelColor(self,event): #self.strEd.SetValue(self.vars[var][1]) self.OnChange() dlg.Destroy() - + def onSelExec(self,event): 'Select an executable file from a menu' var = self.choice[0] @@ -6915,7 +6823,7 @@ def onSelExec(self,event): val = dlg.GetPath() if os.path.exists(val) and is_exe(val): self.vars[var][1] = val - self.strEd.SetValue(self.vars[var][1]) + self.strEd.ChangeValue(self.vars[var][1]) self.OnChange() else: dlg.Destroy() @@ -6925,7 +6833,7 @@ def onSelExec(self,event): continue dlg.Destroy() - + def OnSelection(self): 'show a selected variable and allow it to be changed' def OnNewColorBar(event): @@ -6945,7 +6853,7 @@ def OnNewColorBar(event): if 'enum_'+var in self.vars: choices = self.vars['enum_'+var][0] self.colSel = EnumSelector(self,self.vars[var],1,choices, - OnChange=self.OnChange) + OnChange=self.OnChange) self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5) elif type(self.vars[var][0]) is int: ed = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=int,OKcontrol=self.OnChange) @@ -7001,11 +6909,11 @@ def OnNewColorBar(event): self.colSel.Bind(wx.EVT_COMBOBOX, OnNewColorBar) self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5) elif var == 'Image_calibrant': - import ImageCalibrants as calFile + from . import ImageCalibrants as calFile calList = sorted([m for m in calFile.Calibrants.keys()], key=lambda s: s.lower()) self.colSel = EnumSelector(self,self.vars[var],1,calList, - OnChange=self.OnChange) + OnChange=self.OnChange) self.varsizer.Add(self.colSel, 0, wx.ALIGN_CENTRE|wx.ALL, 5) elif self.colorChip: hSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -7017,7 +6925,7 @@ def OnNewColorBar(event): self.strEd = ValidatedTxtCtrl(self,self.vars[var],1,typeHint=str, OKcontrol=self.OnChange,size=sz,notBlank=False) if self.vars[var][1] is not None: - self.strEd.SetValue(self.vars[var][1]) + self.strEd.ChangeValue(self.vars[var][1]) if btn: self.varsizer.Add(self.strEd, 0, wx.ALL|wx.EXPAND, 5) self.varsizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) @@ -7060,10 +6968,10 @@ def OnClear(self, event): ################################################################################ class RefinementProgress(wx.ProgressDialog): - '''Defines a wrapper to place around wx.ProgressDialog to be used for + '''Defines a wrapper to place around wx.ProgressDialog to be used for showing refinement progress. At some point a better progress window should be - created that keeps useful info on the screen such as some starting and - current fit metrics, but for now all this adds is window defaults + created that keeps useful info on the screen such as some starting and + current fit metrics, but for now all this adds is window defaults and a wx.Yield call during progress update calls. ''' def __init__(self, title='Residual', message='All data Rw =', @@ -7077,34 +6985,34 @@ def __init__(self, title='Residual', message='All data Rw =', self.SetSize((int(Size[0]*1.2),Size[1])) # increase size a bit along x self.CenterOnParent() self.Show() - + def Update(self,value, newmsg=""): wx.GetApp().Yield() #print('wx Yield called') #print('Update:',value,newmsg) return super(self.__class__,self).Update(int(value), newmsg) - + ################################################################################ fmtRw = lambda value: '{:.2f}'.format(float(value)) class G2RefinementProgress(wx.Dialog): - '''Defines an replacement for wx.ProgressDialog to be used for + '''Defines an replacement for wx.ProgressDialog to be used for showing refinement progress. - :param str title: string to place on border of window (default is + :param str title: string to place on border of window (default is 'Refinement progress'). :param str message: initial string to place on top line of window. - :param int maximum: maximum value for progress gauge bar on bottom - of window. + :param int maximum: maximum value for progress gauge bar on bottom + of window. :param wx.Frame parent: parent window for creation of this dialog :param bool trialMode: Set to True for Levenberg-Marquardt fitting where Rw may be computed several times for a single cycle. Call :meth:`AdvanceCycle` when trialMode is True to indicate that a cycle has been completed. Default is False. - :param int seqLen: Number of histograms in sequential fit. A value of + :param int seqLen: Number of histograms in sequential fit. A value of zero (default) means that the fit is not a sequential fit. :param int seqShow: Number of histograms to shown in a sequential fit (default 3) :param int style: optional parameters that determine how the dialog is - is displayed. + is displayed. ''' def __init__(self, title='Refinement progress', message='All data Rw =', maximum=101, parent=None, trialMode=False,seqLen=0, seqShow=3,style=None): @@ -7113,7 +7021,7 @@ def __init__(self, title='Refinement progress', message='All data Rw =', self.SeqLen = seqLen self.seqShow = seqShow if self.SeqLen: - self.maxCycle = self.SeqLen + self.maxCycle = self.SeqLen self.SeqCount = -1 self.rows = 4 if self.trialRw: self.rows = 5 @@ -7164,7 +7072,7 @@ def __init__(self, title='Refinement progress', message='All data Rw =', vSizer.Add(tSizer) hSizer.Add(vSizer,1,wx.EXPAND,1) hSizer.Add((10,-1)) - btn = wx.Button(self, wx.ID_CLOSE,"Abort refinement") + btn = wx.Button(self, wx.ID_CLOSE,"Abort refinement") btn.Bind(wx.EVT_BUTTON,self._onClose) hSizer.Add(btn,0,wx.ALIGN_CENTER_VERTICAL) mainSizer.Add(hSizer,0,wx.EXPAND,5) @@ -7174,14 +7082,14 @@ def __init__(self, title='Refinement progress', message='All data Rw =', self.cycleLbl.SetLabel('Cycle ?') if self.trialRw: self.Labels.label[4].SetLabel('trial parms') - + self.Show() self.Layout() self.SetSizer(mainSizer) mainSizer.Fit(self) self.CenterOnParent() self.SendSizeEvent() - + self.tblCols = {} self.tblLbls = {} self.fitVals = {} @@ -7194,7 +7102,7 @@ def __init__(self, title='Refinement progress', message='All data Rw =', self.prevSeqHist = [] # previous sequential histograms that are still shown self.plotted = [] self.histOff = {} - + def _onClose(self,event): '''Respond to abort button or close of window ''' @@ -7209,7 +7117,7 @@ def Destroy(self): def _makeLabeledTable(self): '''Create two grid sizers, one with row labels and one scrolled. Use _xferLabeledTable to make the row heights match. - ''' + ''' lblSizer = wx.BoxSizer(wx.HORIZONTAL) self.Labels = wx.GridBagSizer(2,2) lblSizer.Add(self.Labels) @@ -7230,22 +7138,22 @@ def _makeLabeledTable(self): self.RwPanel.SetAutoLayout(1) self.RwPanel.SetupScrolling() return lblSizer - + def _xferLabeledTable(self): '''Matches the row sizes of the labels to the row heights in the table ''' for i,h in enumerate(self.gridSiz.GetRowHeights()): self.Labels.label[i].SetMinSize((-1,h)) self.Labels.Layout() - + def SetMaxCycle(self,value): - '''Set the maximum number of cycles or histograms (sequential fit). + '''Set the maximum number of cycles or histograms (sequential fit). Used to scale settings so the gauge bar completes close to 100%. Ignored for sequential refinements. ''' if self.SeqLen: return self.maxCycle = value - + def _AddTableColumn(self,label='',col=None): 'add a column to the Rfactor table' if col is None: @@ -7256,7 +7164,7 @@ def _AddTableColumn(self,label='',col=None): lbls.append(wx.StaticText(self.RwPanel,wx.ID_ANY,'',style=wx.ALIGN_CENTER)) self.gridSiz.Add(lbls[-1],(1+i,col)) return col,lbls - + def SetHistogram(self,nextHist,histLbl): '''Set this before beginning processing of each histogram ''' @@ -7274,7 +7182,7 @@ def SetHistogram(self,nextHist,histLbl): lbl = 'Overall' elif nextHist == -2: # Restraint Chi2 contribution goes in the last col lbl = 'Restraints' - + if nextHist not in self.tblCols: self.tblCols[nextHist],lbls = self._AddTableColumn(lbl,col) self.tblLbls[nextHist] = lbls @@ -7306,12 +7214,12 @@ def _SetSeqHistogram(self,nextHist,histLbl): if len(self.prevSeqHist) < self.seqShow: self.prevSeqHist.append(nextHist) else: - del self.fitVals[self.prevSeqHist[0]] + del self.fitVals[self.prevSeqHist[0]] del self.prevSeqHist[0] self.prevSeqHist.append(nextHist) self.fitVals[nextHist] = [] if -2 in self.fitVals: self.fitVals[-2] = [] - + if nextHist >= 0: self.msgLine1.SetLabel('Fitting '+histLbl) self.curHist = nextHist @@ -7335,7 +7243,7 @@ def _plotBar(self,h): self.plotaxis.bar(np.array(range(l))+self.histOff[h],self.fitVals[h], width=1/wid,label=lbl,color=sym) self.plotted.append(h) - + def _getPlotSetting(self,h): 'determines how a plot is drawn' if h == -1: @@ -7351,17 +7259,17 @@ def _getPlotSetting(self,h): return sym,lbl def _SetCycleRw(self,value): - '''Used to process an Rwp value from the :meth:`Update` method. + '''Used to process an Rwp value from the :meth:`Update` method. The value will be associated with the current histogram (as set in :meth:`SetHistogram`). If this is the 1st supplied value for - that histogram, the value is set and displayed as as the starting - Rwp. If :data`:self.trialRw` is False, the values are saved to a - list specific to the current histogram, and are displayed and - plotted. When :data`:self.trialRw` is True, the Rwp values are - considered trial values and are only saved and plotted when - :meth:`AdvanceCycle` is called. + that histogram, the value is set and displayed as as the starting + Rwp. If :data`:self.trialRw` is False, the values are saved to a + list specific to the current histogram, and are displayed and + plotted. When :data`:self.trialRw` is True, the Rwp values are + considered trial values and are only saved and plotted when + :meth:`AdvanceCycle` is called. ''' - if self.curHist in self.fitVals: + if self.curHist in self.fitVals: cycle = len(self.fitVals[self.curHist]) else: return @@ -7372,7 +7280,7 @@ def _SetCycleRw(self,value): self.tblLbls[self.curHist][1].SetLabel('{:8.3g}'.format(value)) self.RwPanel.SetupScrolling() self._xferLabeledTable() - if not self.trialRw: # show & plot current status here + if not self.trialRw: # show & plot current status here self.fitVals[self.curHist].append(value) self.tblLbls[self.curHist][2].SetLabel('{:8.3g}'.format(value)) self.plotaxis.clear() @@ -7403,8 +7311,8 @@ def _SetCycleRw(self,value): self.RwPanel.ScrollChildIntoView(self.tblLbls[self.curHist][1]) def AdvanceCycle(self,cycle=None): - '''Call this directly with Levenberg-Marquardt fitting after a - cycle completes. + '''Call this directly with Levenberg-Marquardt fitting after a + cycle completes. Plots the results. ''' self.plotaxis.clear() @@ -7422,100 +7330,18 @@ def AdvanceCycle(self,cycle=None): def Update(self, value=None, newmsg=""): '''designed to work with calls intended for wx.ProgressDialog.Update - the value is assumed to be the current wR value for the histogram - selected with SetHistogram and newmsg goes into the 2nd status line. + the value is assumed to be the current wR value for the histogram + selected with SetHistogram and newmsg goes into the 2nd status line. ''' if self.curHist is not None and value != 101.: self._SetCycleRw(value) if newmsg: - self.msgLine2.SetLabel(newmsg) + self.msgLine2.SetLabel(newmsg) m,s = divmod(time.time()-self.startTime,60) h,m = divmod(m,60) self.elapsed.SetLabel('{:0d}:{:02d}:{:04.1f}'.format(int(h), int(m), s)) wx.GetApp().Yield() return (not self.abortStatus, True) - -################################################################################ -class downdate(wx.Dialog): - '''Dialog to allow a user to select a version of GSAS-II to install - svn version - ''' - def __init__(self,parent=None): - style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER - wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Select Version', style=style) - pnl = wx.Panel(self) - sizer = wx.BoxSizer(wx.VERTICAL) - insver = GSASIIpath.svnGetRev(local=True) - curver = int(GSASIIpath.svnGetRev(local=False)) - label = wx.StaticText( - pnl, wx.ID_ANY, - 'Select a specific GSAS-II version to install' - ) - sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) - sizer1 = wx.BoxSizer(wx.HORIZONTAL) - sizer1.Add( - wx.StaticText(pnl, wx.ID_ANY, - 'Currently installed version: '+str(insver)), - 0, wx.ALIGN_CENTRE|wx.ALL, 5) - sizer.Add(sizer1) - sizer1 = wx.BoxSizer(wx.HORIZONTAL) - sizer1.Add( - wx.StaticText(pnl, wx.ID_ANY, - 'Select GSAS-II version to install: '), - 0, wx.ALIGN_CENTRE|wx.ALL, 5) - self.spin = wx.SpinCtrl(pnl, wx.ID_ANY,size=(150,-1)) - self.spin.SetRange(1, curver) - self.spin.SetValue(curver) - self.Bind(wx.EVT_SPINCTRL, self._onSpin, self.spin) - self.Bind(wx.EVT_KILL_FOCUS, self._onSpin, self.spin) - sizer1.Add(self.spin) - sizer.Add(sizer1) - - line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL) - sizer.Add(line, 0, wx.EXPAND|wx.ALL, 10) - - self.text = wx.StaticText(pnl, wx.ID_ANY, "") - sizer.Add(self.text, 0, wx.EXPAND|wx.ALL, 5) - - line = wx.StaticLine(pnl,-1, size=(-1,3), style=wx.LI_HORIZONTAL) - sizer.Add(line, 0, wx.EXPAND|wx.ALL, 10) - sizer.Add( - wx.StaticText( - pnl, wx.ID_ANY, - 'If "Install" is pressed, your project will be saved;\n' - 'GSAS-II will exit; The specified version will be loaded\n' - 'and GSAS-II will restart. Press "Cancel" to abort.'), - 0, wx.EXPAND|wx.ALL, 10) - btnsizer = wx.StdDialogButtonSizer() - btn = wx.Button(pnl, wx.ID_OK, "Install") - btn.SetDefault() - btnsizer.AddButton(btn) - btn = wx.Button(pnl, wx.ID_CANCEL) - btnsizer.AddButton(btn) - btnsizer.Realize() - sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) - pnl.SetSizer(sizer) - sizer.Fit(self) - self.topsizer=sizer - self.CenterOnParent() - self._onSpin(None) - - def _onSpin(self,event): - 'Called to load info about the selected version in the dialog' - if event: event.Skip() - ver = self.spin.GetValue() - d = GSASIIpath.svnGetLog(version=ver) - date = d.get('date','?').split('T')[0] - s = '(Version '+str(ver)+' created '+date - s += ' by '+d.get('author','?')+')' - msg = d.get('msg') - if msg: s += '\n\nComment: '+msg - self.text.SetLabel(s) - self.topsizer.Fit(self) - - def getVersion(self): - 'Get the version number in the dialog' - return self.spin.GetValue() ################################################################################ class gitVersionSelector(wx.Dialog): @@ -7533,7 +7359,7 @@ def __init__(self,parent=None): cutoff = datetime.datetime(2024,2,20,tzinfo=tz) # 20-feb-2024 self.githistory = [h for h in self.githistory if self.g2repo.commit(h).committed_datetime > cutoff] - # end patch + # end patch self.initial_commit = self.g2repo.commit('HEAD') self.initial_commit_info = self.docCommit(self.initial_commit) if parent is None: @@ -7557,7 +7383,7 @@ def __init__(self,parent=None): label.Wrap(400) sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add((-1,20)) - + sizer.Add(wx.StaticText(self, wx.ID_ANY, ' Currently installed version:')) sizer1 = wx.BoxSizer(wx.HORIZONTAL) @@ -7574,7 +7400,7 @@ def __init__(self,parent=None): initpnl.SetupScrolling() sizer1.Add(initpnl) sizer.Add(sizer1) - + sizer.Add((-1,20)) sizer1 = wx.BoxSizer(wx.HORIZONTAL) sizer1.Add( @@ -7594,7 +7420,7 @@ def __init__(self,parent=None): ' Selected version to install:')) sizer1 = wx.BoxSizer(wx.HORIZONTAL) sizer1.Add((50,-1)) - + self.spanel = wxscroll.ScrolledPanel(self, wx.ID_ANY,size=(450,90), style = wx.SUNKEN_BORDER) ssizer = wx.BoxSizer(wx.HORIZONTAL) @@ -7647,11 +7473,11 @@ def getVersion(self): :return: returns one of three values: - * 0: if the newest version is selected, so that the + * 0: if the newest version is selected, so that the installation should be updated rather than regressed * None: if the currently installed version is selected, so that nothing need be done - * A hexsha string: the regressed version that should be + * A hexsha string: the regressed version that should be selected. ''' if self.spin.GetValue() == 0: @@ -7660,7 +7486,7 @@ def getVersion(self): if self.g2repo.commit(commit) == self.initial_commit: return None return commit - + def docCommit(self,commit): '''Provides a string with information about a specific git commit. @@ -7675,16 +7501,16 @@ def docCommit(self,commit): msg += f"\ntags: {', '.join(tags)}" msg += '\ncomment: ' + commit.message return msg - + ################################################################################ class SortableLstCtrl(wx.Panel): - '''Creates a read-only table with sortable columns. Sorting is done by - clicking on a column label. A triangle facing up or down is added to + '''Creates a read-only table with sortable columns. Sorting is done by + clicking on a column label. A triangle facing up or down is added to indicate the column is sorted. - To use, the header is labeled using - :meth:`PopulateHeader`, then :meth:`PopulateLine` is called for every + To use, the header is labeled using + :meth:`PopulateHeader`, then :meth:`PopulateLine` is called for every row in table and finally :meth:`SetColWidth` is called to set the column widths. @@ -7703,7 +7529,7 @@ def PopulateHeader(self, header, justify): '''Defines the column labels :param list header: a list of strings with header labels - :param list justify: a list of int values where 0 causes left justification, + :param list justify: a list of int values where 0 causes left justification, 1 causes right justification, and -1 causes centering ''' info = wx.ListItem() @@ -7740,9 +7566,9 @@ def SetColWidth(self,col,width=None,auto=True,minwidth=0,maxwidth=None): :param int width: the column width in pixels :param bool auto: if True (default) and width is None (default) the width is set by the maximum width entry in the column - :param int minwidth: used when auto is True, sets a minimum + :param int minwidth: used when auto is True, sets a minimum column width - :param int maxwidth: used when auto is True, sets a maximum + :param int maxwidth: used when auto is True, sets a maximum column width. Do not use with minwidth ''' if width: @@ -7794,9 +7620,9 @@ class G2LstCtrl(wx.ListCtrl): ''' pass print('docs build kludge for G2LstCtrl') - + #### Display Help information ################################################################################ -# define some globals +# define some globals htmlPanel = None htmlFrame = None htmlFirstUse = True @@ -7814,7 +7640,7 @@ def ShowHelp(helpType,frame,helpMode=None): helplink = 'gsasII-phase.html#Phase-Data' elif helpType: helplink = 'gsasII.html#'+helpType.replace(')','').replace('(','_').replace(' ','_') - else: + else: helplink = 'gsasII.html' # determine if a web browser or the internal viewer should be used for help info if helpMode: @@ -7830,9 +7656,9 @@ def ShowHelp(helpType,frame,helpMode=None): import webbrowser # postpone this until now for quicker startup wb = webbrowser if sys.platform == "darwin": # on Mac, use a OSXscript so that file anchors work - # Get the default browser, this will fail in py2.7 and might fail, so + # Get the default browser, this will fail in py2.7 and might fail, so # use safari as a backup - appleScript = ''' + appleScript = ''' use framework "AppKit" use AppleScript version "2.4" use scripting additions @@ -7870,8 +7696,8 @@ def ShowWebPage(URL,frame,browser=False,internal=False): :param str URL: web page URL :param wx.Frame frame: parent window (or None) - :param bool browser: If True, forces the page to be opened in a web - browser, regardless of the ``Help_mode`` config setting. + :param bool browser: If True, forces the page to be opened in a web + browser, regardless of the ``Help_mode`` config setting. ''' global htmlFirstUse,htmlPanel,htmlFrame # determine if a web browser or the internal viewer should be used for help info @@ -7882,12 +7708,12 @@ def ShowWebPage(URL,frame,browser=False,internal=False): else: # elif browser: helpMode = 'browser' - + if helpMode == 'internal': viewWebPage(frame,URL) else: import webbrowser # postpone this until now for quicker startup - if URL.startswith('http'): + if URL.startswith('http'): pfx = '' elif sys.platform.lower().startswith('win'): pfx = '' @@ -7905,350 +7731,19 @@ def ShowWebPage(URL,frame,browser=False,internal=False): tutorialCatalog = [l for l in tutorialIndex if len(l) >= 3] # A catalog of GSAS-II tutorials generated from the table in :data:`tutorialIndex` def OpenTutorial(parent): - if GSASIIpath.HowIsG2Installed().startswith('git'): - return OpenGitTutorial(parent) - else: - return OpenSvnTutorial(parent) - -class OpenSvnTutorial(wx.Dialog): - '''Open a tutorial web page, optionally copying the web page, screen images and - data file(s) to the local disk. - ''' - - def __init__(self,parent): - style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER - wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Open Tutorial', style=style) - self.G2frame = self.frame = parent - pnl = wx.Panel(self) - sizer = wx.BoxSizer(wx.VERTICAL) - sizer1 = wx.BoxSizer(wx.HORIZONTAL) - label = wx.StaticText( - pnl, wx.ID_ANY, - 'Select the tutorial to be run and the mode of access' - ) - msg = '''GSAS-II tutorials and their sample data files - require a fair amount of storage space; few users will - use all of them. This dialog allows users to load selected - tutorials (along with their sample data) to their computer; - optionally all tutorials can be downloaded. - - Downloaded tutorials can be viewed and run without internet - access. Tutorials can also be viewed without download, but - users will need to download the sample data files manually. - - The location used to download tutorials is set using the - "Set download location" which is saved as the "Tutorial_location" - configuration option see File/Preference or the - config_example.py file. System managers can select to have - tutorial files installed at a shared location. - ''' - self.SetTutorialPath() - hlp = HelpButton(pnl,msg) - sizer1.Add((-1,-1),1, wx.EXPAND, 0) - sizer1.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 0) - sizer1.Add((-1,-1),1, wx.EXPAND, 0) - sizer1.Add(hlp) - sizer.Add(sizer1,0,wx.EXPAND|wx.ALL,0) - sizer.Add((10,10)) - sizer0 = wx.BoxSizer(wx.HORIZONTAL) - sizer1a = wx.BoxSizer(wx.VERTICAL) - sizer1b = wx.BoxSizer(wx.VERTICAL) - btn = wx.Button(pnl, wx.ID_ANY, "Download a tutorial and view") - btn.Bind(wx.EVT_BUTTON, self.SelectAndDownload) - sizer1a.Add(btn,0) - btn = wx.Button(pnl, wx.ID_ANY, "Select from downloaded tutorials") - btn.Bind(wx.EVT_BUTTON, self.onSelectDownloaded) - sizer1a.Add(btn,0) - btn = wx.Button(pnl, wx.ID_ANY, "Browse tutorial on web") - btn.Bind(wx.EVT_BUTTON, self.onWebBrowse) - sizer1a.Add(btn,0) - btn = wx.Button(pnl, wx.ID_ANY, "Update downloaded tutorials") - btn.Bind(wx.EVT_BUTTON, self.UpdateDownloaded) - sizer1b.Add(btn,0) - btn = wx.Button(pnl, wx.ID_ANY, "Download all tutorials") - btn.Bind(wx.EVT_BUTTON, self.DownloadAll) - sizer1b.Add(btn,0) - sizer0.Add(sizer1a,0,wx.EXPAND|wx.ALL,0) - sizer0.Add(sizer1b,0,wx.EXPAND|wx.ALL,0) - sizer.Add(sizer0,5,wx.EXPAND|wx.ALL,5) - - sizer.Add((10,10)) - sizer1 = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(pnl, wx.ID_ANY, "Set download location") - btn.Bind(wx.EVT_BUTTON, self.SelectDownloadLoc) - sizer1.Add(btn,0,WACV) - self.dataLoc = wx.StaticText(pnl, wx.ID_ANY,self.tutorialPath) - sizer1.Add(self.dataLoc,0,WACV) - sizer.Add(sizer1) - - btnsizer = wx.StdDialogButtonSizer() - btn = wx.Button(pnl, wx.ID_CANCEL,"Done") - btnsizer.AddButton(btn) - btnsizer.Realize() - sizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) - pnl.SetSizer(sizer) - sizer.Fit(self) - self.topsizer=sizer - self.CenterOnParent() - - def SetTutorialPath(self): - '''Get the tutorial location if set; if not pick a default - directory in a logical place - ''' - # has the user set a location and is it valid? - if GSASIIpath.GetConfigValue('Tutorial_location'): - tutorialPath = os.path.abspath(GSASIIpath.GetConfigValue('Tutorial_location')) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - try: - os.makedirs(tutorialPath) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - except: - print('Unable to use Tutorial_location config setting', - tutorialPath) - # try a system-specific location - if (sys.platform.lower().startswith('win')): - for p in ('Documents','My Documents',''): - if os.path.exists(os.path.abspath(os.path.expanduser( - os.path.join('~',p)))): - tutorialPath = os.path.abspath(os.path.expanduser( - os.path.join('~',p,'G2tutorials'))) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - try: - os.makedirs(tutorialPath) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - except: - pass - else: - tutorialPath = os.path.abspath(os.path.expanduser( - os.path.join('~','G2tutorials'))) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - try: - os.makedirs(tutorialPath) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - except: - pass - # no success so far, use current working directory - tutorialPath = os.path.abspath(os.path.join(os.getcwd(),'G2tutorials')) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - try: - os.makedirs(tutorialPath) - if os.path.exists(tutorialPath): - self.tutorialPath = tutorialPath - return - except: - pass - # nothing worked, set self.tutorialPath with os.getcwd() and hope for the best - print('Warning: Unable to set a TutorialPath, using',os.getcwd()) - tutorialPath = os.getcwd() +# how_install = GSASIIpath.HowIsG2Installed() +# if how_install.startswith('git'): +# return OpenGitTutorial(parent) +# else: + return OpenGitTutorial(parent) - def SelectAndDownload(self,event): - '''Make a list of all tutorials on web and allow user to choose one to - download and then view - ''' - indices = [j for j,i in enumerate(tutorialCatalog) - if not os.path.exists(os.path.join(self.tutorialPath,i[0],i[1]))] - if not indices: - G2MessageBox(self,'All tutorials are downloaded','None to download') - return -# choices = [tutorialCatalog[i][2] for i in indices] -# selected = self.ChooseTutorial(choices) - choices2 = [tutorialCatalog[i][2:4] for i in indices] - selected = self.ChooseTutorial2(choices2) - if selected is None: return - j = indices[selected] - fullpath = os.path.join(self.tutorialPath,tutorialCatalog[j][0],tutorialCatalog[j][1]) - fulldir = os.path.join(self.tutorialPath,tutorialCatalog[j][0]) - #URL = G2BaseURL+'/Tutorials/'+tutorialCatalog[j][0]+'/' - URL = G2TutURL + tutorialCatalog[j][0]+'/' - if GSASIIpath.svnInstallDir(URL,fulldir): - ShowWebPage(fullpath,self.frame) - else: - G2MessageBox(self,'Error downloading tutorial','Download error') - self.EndModal(wx.ID_OK) - self.G2frame.TutorialImportDir = os.path.join(self.tutorialPath,tutorialCatalog[j][0],'data') - - def onSelectDownloaded(self,event): - '''Select a previously downloaded tutorial - ''' - indices = [j for j,i in enumerate(tutorialCatalog) - if os.path.exists(os.path.join(self.tutorialPath,i[0],i[1]))] - if not indices: - G2MessageBox(self, - 'There are no downloaded tutorials in '+self.tutorialPath, - 'None downloaded') - return -# choices = [tutorialCatalog[i][2] for i in indices] -# selected = self.ChooseTutorial(choices) - choices2 = [tutorialCatalog[i][2:4] for i in indices] - selected = self.ChooseTutorial2(choices2) - if selected is None: return - j = indices[selected] - fullpath = os.path.join(self.tutorialPath,tutorialCatalog[j][0],tutorialCatalog[j][1]) - self.EndModal(wx.ID_OK) - ShowWebPage(fullpath,self.frame) - self.G2frame.TutorialImportDir = os.path.join(self.tutorialPath,tutorialCatalog[j][0],'data') - - def onWebBrowse(self,event): - '''Make a list of all tutorials on web and allow user to view one. - ''' -# choices = [i[2] for i in tutorialCatalog] -# selected = self.ChooseTutorial(choices) - choices2 = [i[2:4] for i in tutorialCatalog] - selected = self.ChooseTutorial2(choices2) - if selected is None: return - tutdir = tutorialCatalog[selected][0] - tutfil = tutorialCatalog[selected][1] - # open web page remotely, don't worry about data - #URL = G2BaseURL+'/Tutorials/'+tutdir+'/'+tutfil - URL = G2TutURL + tutdir + '/' +tutfil - self.EndModal(wx.ID_OK) - ShowWebPage(URL,self.frame) - - def ChooseTutorial2(self,choices): - '''Select tutorials from a two-column table, when possible - ''' - lbls = ('tutorial name (indent indicates previous is required)','description') - colWidths=[400,400] - dlg = MultiColumnSelection(self,'select tutorial',lbls,choices,colWidths) - selection = dlg.Selection - dlg.Destroy() - if selection is not None: # wx from EPD Python - if selection == -1: return - return selection - else: - return self.ChooseTutorial([i[0] for i in choices]) - - def ChooseTutorial(self,choices): - '''choose a tutorial from a list - (will eventually only be used with very old wxPython - ''' - def onDoubleClick(event): - 'double-click closes the dialog' - dlg.EndModal(wx.ID_OK) - dlg = wx.Dialog(self,wx.ID_ANY, - 'Select a tutorial to view. NB: indented ones require prerequisite', - style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) - pnl = wx.Panel(dlg) - sizer = wx.BoxSizer(wx.VERTICAL) - listbox = wx.ListBox(pnl, wx.ID_ANY, choices=choices,size=(450, 100),style=wx.LB_SINGLE) - sizer.Add(listbox,1,wx.EXPAND|wx.ALL,1) - listbox.Bind(wx.EVT_LISTBOX_DCLICK, onDoubleClick) - sizer.Add((10,10)) - btnsizer = wx.StdDialogButtonSizer() - btn = wx.Button(pnl, wx.ID_CANCEL) - btnsizer.AddButton(btn) - OKbtn = wx.Button(pnl, wx.ID_OK) - OKbtn.SetDefault() - btnsizer.AddButton(OKbtn) - btnsizer.Realize() - sizer.Add((-1,5)) - sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50) - - pnl.SetSizer(sizer) - sizer.Fit(dlg) - dlg.CenterOnParent() - if dlg.ShowModal() != wx.ID_OK: - dlg.Destroy() - return - selected = listbox.GetSelection() - dlg.Destroy() - wx.Yield() # close window right away so user sees something happen - if selected < 0: return - return selected - - def UpdateDownloaded(self,event): - 'Find the downloaded tutorials and run an svn update on them' - updated = 0 - wx.BeginBusyCursor() - for i in tutorialCatalog: - if not os.path.exists(os.path.join(self.tutorialPath,i[0],i[1])): continue - print('Updating '+i[0]) - GSASIIpath.svnUpdateDir(os.path.join(self.tutorialPath,i[0])) - updated += 1 - wx.EndBusyCursor() - if not updated: - G2MessageBox(self,'Warning, you have no downloaded tutorials','None Downloaded') - else: - G2MessageBox(self,'{} updates completed'.format(updated),'Updates done') - #self.EndModal(wx.ID_OK) - - def DownloadAll(self,event): - 'Download or update all tutorials' - fail = '' - for i in tutorialCatalog: - if os.path.exists(os.path.join(self.tutorialPath,i[0],i[1])): - print('Updating '+i[0]) - GSASIIpath.svnUpdateDir(os.path.join(self.tutorialPath,i[0])) - else: - fulldir = os.path.join(self.tutorialPath,i[0]) - #URL = G2BaseURL+'/Tutorials/'+i[0]+'/' - URL = G2TutURL + i[0] + '/' - if not GSASIIpath.svnInstallDir(URL,fulldir): - if fail: fail += ', ' - fail += i[0] - if fail: - G2MessageBox(self,'Error downloading tutorial(s)\n\t'+fail,'Download error') - self.EndModal(wx.ID_OK) - - def SelectDownloadLoc(self,event): - '''Select a download location, - Cancel resets to the default - ''' - dlg = wx.DirDialog(self, "Choose a directory for tutorial downloads:", - defaultPath=self.tutorialPath)#,style=wx.DD_DEFAULT_STYLE) - try: - if dlg.ShowModal() != wx.ID_OK: - return - pth = dlg.GetPath() - finally: - dlg.Destroy() - if not os.path.exists(pth): - try: - os.makedirs(pth) #failing for no obvious reason - except OSError: - msg = 'The selected directory is not valid.\n\t' - msg += pth - msg += '\n\nAn attempt to create the directory failed' - G2MessageBox(self.frame,msg) - return - if os.path.exists(os.path.join(pth,"help")) and os.path.exists(os.path.join(pth,"Exercises")): - print("Note that you may have old tutorial files in the following directories") - print('\t'+os.path.join(pth,"help")) - print('\t'+os.path.join(pth,"Exercises")) - print('Subdirectories in the above can be deleted to save space\n\n') - self.tutorialPath = pth - self.dataLoc.SetLabel(self.tutorialPath) - if GSASIIpath.GetConfigValue('Tutorial_location') == pth: return - vars = GetConfigValsDocs() - try: - vars['Tutorial_location'][1] = pth - if GSASIIpath.GetConfigValue('debug'): print('DBG_Saving Tutorial_location: '+pth) - GSASIIpath.SetConfigValue(vars) - SaveConfigVars(vars) - except KeyError: - pass - class OpenGitTutorial(wx.Dialog): - '''Open a tutorial web page from the git repository, - optionally copying the tutorial's exercise data file(s) to + '''Open a tutorial web page from the git repository, + optionally copying the tutorial's exercise data file(s) to the local disk. ''' - + def __init__(self,parent): style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER wx.Dialog.__init__(self, parent, wx.ID_ANY, 'Open Tutorial', style=style) @@ -8266,7 +7761,7 @@ def __init__(self,parent): msg = '''The data files needed to run the GSAS-II tutorials require a fair amount of storage space; few users will use all of them. This dialog allows you to open a - tutorial in a web browser and select if you want the data + tutorial in a web browser and select if you want the data needed to run that exercise to be downloaded to your computer. The location used to download tutorials is set using the @@ -8282,7 +7777,7 @@ def __init__(self,parent): sizer1.Add(hlp) sizer.Add(sizer1,0,wx.EXPAND|wx.ALL,0) sizer.Add((10,10)) - sizer0 = wx.BoxSizer(wx.HORIZONTAL) + sizer0 = wx.BoxSizer(wx.HORIZONTAL) sizer1 = wx.BoxSizer(wx.VERTICAL) btn = wx.Button(pnl, wx.ID_ANY, "Select a tutorial to view") btn.Bind(wx.EVT_BUTTON, self.onWebBrowse) @@ -8292,7 +7787,7 @@ def __init__(self,parent): sizer1.Add(btn,0,wx.TOP,5) sizer0.Add(sizer1,0,wx.EXPAND|wx.ALL,0) sizer.Add(sizer0,5,wx.EXPAND|wx.ALL,5) - + sizer.Add((10,10)) sizer1 = wx.BoxSizer(wx.HORIZONTAL) btn = wx.Button(pnl, wx.ID_ANY, "Set download location") @@ -8301,7 +7796,7 @@ def __init__(self,parent): self.dataLoc = wx.StaticText(pnl, wx.ID_ANY,self.tutorialPath) sizer1.Add(self.dataLoc,0,WACV) sizer.Add(sizer1) - + btnsizer = wx.StdDialogButtonSizer() btn = wx.Button(pnl, wx.ID_CANCEL,"Close") btnsizer.AddButton(btn) @@ -8392,7 +7887,7 @@ def onWebBrowse(self,event): ''' choices2 = [i[2:4] for i in tutorialCatalog] selected = self.ChooseTutorial2(choices2) - if selected is None: return + if selected is None: return tutdir = tutorialCatalog[selected][0] tutfil = tutorialCatalog[selected][1] # open web page remotely, don't worry about data @@ -8401,7 +7896,7 @@ def onWebBrowse(self,event): wx.CallAfter(self.EndModal,wx.ID_OK) ShowWebPage(URL,self.frame) return tutdir - + def ChooseTutorial2(self,choices): '''Select tutorials from a two-column table, when possible ''' @@ -8457,7 +7952,7 @@ def SelectDownloadLoc(self,event): AutoLoadWindow = None def AutoLoadFiles(G2frame,FileTyp='pwd'): - import GSASIIscriptable as G2sc + from . import GSASIIscriptable as G2sc def OnBrowse(event): '''Responds when the Browse button is pressed to load a file. The routine determines which button was pressed and gets the @@ -8495,7 +7990,7 @@ def OnBrowse(event): finally: d.Destroy() TestInput() - + def OnFileOfFiles(event): '''Read from a list of files and add those files in the order specified in that file. @@ -8631,12 +8126,12 @@ def RunTimerPWDR(event,filelist=None): else: G2fil.G2Print("{} block # {} read by Reader {}" .format(f,block,rd.formatName)) - block += 1 + block += 1 repeat = rd.repeat else: G2fil.G2Print("Warning: {} Reader failed to read {}" .format(rd.formatName,f)) - Iparm1, Iparm2 = G2sc.load_iprms(Settings['instfile'],rd) + Iparm1, Iparm2 = G2sc.load_iprms(Settings['instfile'],rd) if 'phoenix' in wx.version(): HistName = 'PWDR '+rd.idstring else: @@ -8671,8 +8166,8 @@ def RunTimerPWDR(event,filelist=None): rd.powderdata[3] = np.zeros_like(rd.powderdata[0]) rd.powderdata[4] = np.zeros_like(rd.powderdata[0]) rd.powderdata[5] = np.zeros_like(rd.powderdata[0]) - Ymin = np.min(rd.powderdata[1]) - Ymax = np.max(rd.powderdata[1]) + Ymin = np.min(rd.powderdata[1]) + Ymax = np.max(rd.powderdata[1]) valuesdict = { 'wtFactor':1.0, 'Dummy':False, @@ -8691,7 +8186,7 @@ def RunTimerPWDR(event,filelist=None): Tmin = min(rd.powderdata[0]) Tmax = max(rd.powderdata[0]) Tmin1 = Tmin - if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4: + if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4: Tmin1 = G2lat.Dsp2pos(Iparm1,0.4) G2frame.GPXtree.SetItemPyData( G2frame.GPXtree.AppendItem(Id,text='Limits'), @@ -8731,7 +8226,7 @@ def RunTimerPWDR(event,filelist=None): G2frame.GPXtree.Expand(Id) G2frame.GPXtree.SelectItem(Id) dlg.Raise() - + def RunTimerGR(event): if GSASIIpath.GetConfigValue('debug'): import datetime @@ -8775,7 +8270,7 @@ def RunTimerGR(event): else: G2fil.G2Print("{} block # {} read by Reader {}" .format(f,block,rd.formatName)) - block += 1 + block += 1 repeat = rd.repeat else: G2fil.G2Print("Warning: {} Reader failed to read {}" @@ -8788,8 +8283,8 @@ def RunTimerGR(event): Settings['ReadList'].append(HistName) # put into tree Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=HistName) - Ymin = np.min(rd.pdfdata[1]) - Ymax = np.max(rd.pdfdata[1]) + Ymin = np.min(rd.pdfdata[1]) + Ymax = np.max(rd.pdfdata[1]) valuesdict = { 'wtFactor':1.0,'Dummy':False,'ranId':ran.randint(0,sys.maxsize), 'Offset':[0.0,0.0],'delOffset':0.02*Ymax, @@ -8801,14 +8296,14 @@ def RunTimerGR(event): 'diffGRname':'','diffMult':1.0,'Rmax':Ymax,}) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='PDF Peaks'), {'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]}) - + # select and show last PWDR file to be read if Id: G2frame.EnablePlot = True G2frame.GPXtree.Expand(Id) G2frame.GPXtree.SelectItem(Id) - - global AutoLoadWindow + + global AutoLoadWindow Settings = {} if AutoLoadWindow: # make sure only one window is open at a time try: @@ -8825,7 +8320,7 @@ def RunTimerGR(event): fileReaders = [i for i in G2frame.ImportPDFReaderlist] # if i.scriptable] fmtchoices = [p.longFormatName for p in fileReaders] - Settings['fmt'] = 0 + Settings['fmt'] = 0 Settings['ext'] = 0 Settings['extStr'] = '' Settings['filter'] = '*.*' @@ -8846,24 +8341,24 @@ def RunTimerGR(event): fmtSel = G2ChoiceButton(mnpnl,fmtchoices,Settings,'fmt',onChoice=onSetFmtSelection) sizer.Add(fmtSel) sizer.Add((-1,-1),1,wx.EXPAND,1) - msg = '''This window serves two purposes. It can be used to read files -as they are added to a directory or it can be used to read files from an -externally-created file list. For either, set the file format and an + msg = '''This window serves two purposes. It can be used to read files +as they are added to a directory or it can be used to read files from an +externally-created file list. For either, set the file format and an instrument parameter file must be specified. %% * For automatic reading, the files must be found in the directory specified by -"Read from:" and the selected extension. The "File filter:" can be used to -limit the files to those matching a wildcard, (for example, if -"202408*pow*.*" is used as a filter, then files must begin with "202408" -and must also contain the string "pow".) +"Read from:" and the selected extension. The "File filter:" can be used to +limit the files to those matching a wildcard, (for example, if +"202408*pow*.*" is used as a filter, then files must begin with "202408" +and must also contain the string "pow".) %% -* For reading from a list of files, press the "Read from file with a list -of files" button. The input file must contain a list of files, one per line. -Lines beginning in '#' are ignored. If more than one column is used -(separated by commas or tabs), the file name should be the first column. -File names can be in quotes, but this is not required. The extension -is ignored, as is the "File filter". The "Read from" directory will be used -if the file name does not contain a full path and the file is not in the +* For reading from a list of files, press the "Read from file with a list +of files" button. The input file must contain a list of files, one per line. +Lines beginning in '#' are ignored. If more than one column is used +(separated by commas or tabs), the file name should be the first column. +File names can be in quotes, but this is not required. The extension +is ignored, as is the "File filter". The "Read from" directory will be used +if the file name does not contain a full path and the file is not in the current working directory. ''' sizer.Add(HelpButton(mnpnl,msg,wrap=400),0,wx.RIGHT,5) @@ -8887,7 +8382,7 @@ def RunTimerGR(event): btn3.Bind(wx.EVT_BUTTON, OnBrowse) sizer.Add(btn3,0,wx.ALIGN_CENTER_VERTICAL) mnsizer.Add(sizer,0,wx.EXPAND) - + if FileTyp == 'pwd': sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Instrument parameter file from: '),0,wx.ALIGN_CENTER_VERTICAL) @@ -8921,7 +8416,7 @@ def RunTimerGR(event): AutoLoadWindow = dlg # save window reference # Deal with Origin 1/2 ambiguities ################################################################################ -def ChooseOrigin(G2frame,rd): +def ChooseOrigin(G2frame,rd): G2elem.SetupGeneral(rd.Phase,G2frame.dirname) # make copy of Phase but shift atoms Origin 1->2 O2Phase = copy.deepcopy(rd.Phase) @@ -8962,19 +8457,19 @@ def ChooseOrigin(G2frame,rd): txt += '{}*{}'.format(cellContents[k],k) den,_ = G2mth.getDensity(phObj['General']) txt += "\n Density {:.2f} g/cc\n".format(den) - + DisAglData['OrigAtoms'] = DisAglData['TargAtoms'] = [ [i,]+atom[ct-1:ct+1]+atom[cx:cx+3] for i,atom in enumerate(phObj['Atoms'])] # lbl,dis,angle = G2stMn.RetDistAngle(DisAglCtls,DisAglData) # # get unique distances - # minDis = {} - # for i in dis: - # for j,o,s,d,e in dis[i]: - # key = '-'.join(sorted([lbl[i],lbl[j]])) - # if key not in minDis: - # minDis[key] = d - # elif d < minDis[key]: + # minDis = {} + # for i in dis: + # for j,o,s,d,e in dis[i]: + # key = '-'.join(sorted([lbl[i],lbl[j]])) + # if key not in minDis: + # minDis[key] = d + # elif d < minDis[key]: # minDis[key] = d # thirdShortest = sorted([minDis[k] for k in minDis])[:3][-1] # shortTxt = '' @@ -8990,7 +8485,7 @@ def ChooseOrigin(G2frame,rd): centro = False if '-x,-y,-z' in [i.replace(' ','').lower() for i in rd.SymOps['xyz']]: centro = True - + msg = 'Be careful here. This space group has two origin settings. GSAS-II requires the origin to be placed at a center of symmetry (Origin 2). You must choose the correct option below or all subsequent results will be *wrong*. For more info, press the help button (bottom right).\n' if centro: msg += '\nThere is an -x,-y,-z symmetry op in the file input, so this is likely already in Origin 2.\n' @@ -9045,8 +8540,8 @@ def ChooseOrigin(G2frame,rd): return None def makeContourSliders(G2frame,Ymax,PlotPatterns,newPlot,plottype): - '''Create a non-modal dialog for sliders to set contour plot - intensity thresholds. + '''Create a non-modal dialog for sliders to set contour plot + intensity thresholds. ''' def updatePlot(): 'updates plot after a change in values' @@ -9118,7 +8613,7 @@ def OnNewVal(*args,**kwargs): hbox = wx.BoxSizer(wx.HORIZONTAL) hbox.Add((-1,-1),1,wx.EXPAND,1) - btn = wx.Button(dlg, wx.ID_CLOSE) + btn = wx.Button(dlg, wx.ID_CLOSE) hbox.Add(btn,0,wx.ALL,0) btn.Bind(wx.EVT_BUTTON,lambda x: dlg.Destroy()) hbox.Add((-1,-1),1,wx.EXPAND,1) @@ -9150,9 +8645,9 @@ def skimGPX(fl): cnt += 1 note = None try: - data = G2IO.cPickleLoad(fp) + data = G2IO.pickleLoad(fp) except EOFError: - #print(cnt,'entries read') + #print(cnt,'entries read') break if cnt > 50: # don't spend too long on this file, if big result['PWDR'] += 3*[' .'] @@ -9164,7 +8659,7 @@ def skimGPX(fl): # datum[0]['Seq Data'] if 'LastSavedUsing' in datum[1]: result['last saved'] += ' (v' + datum[1]['LastSavedUsing'] +')' - + elif datum[0] == 'Covariance': d = datum[1].get('Rvals') if d: @@ -9195,7 +8690,7 @@ def skimGPX(fl): else: # print(datum[0]) # breakpoint() - pass + pass if note: if note not in result['other']: result['other'].append(note) @@ -9207,10 +8702,10 @@ def skimGPX(fl): class gpxFileSelector(wx.Dialog): '''Create a file selection widget for locating .gpx files as a modal - dialog. Displays status information on selected files. After creating + dialog. Displays status information on selected files. After creating this use dlg.ShowModal() to wait for selection of a file. If dlg.ShowModal() returns wx.ID_OK, use dlg.Selection (multiple=False) - to obtain the selected file or dlg.Selections (multiple=True) to + to obtain the selected file or dlg.Selections (multiple=True) to obtain a list of multiple files. :param wx.Frame parent: name of panel or frame that will be @@ -9232,17 +8727,17 @@ def __init__(self,parent,startdir='.',multiple=False,*args,**kwargs): if startdir == '.': self.startDir = os.getcwd() self.multiple = multiple - wx.Dialog.__init__(self, parent=parent, + wx.Dialog.__init__(self, parent=parent, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) self.CenterOnParent() - + topSizer = wx.BoxSizer(wx.VERTICAL) - self.dirBtn = wxfilebrowse.DirBrowseButton(self,wx.ID_ANY, size=(650, -1), + self.dirBtn = wxfilebrowse.DirBrowseButton(self,wx.ID_ANY, size=(650, -1), changeCallback = self.DirSelected, startDirectory = self.startDir ) topSizer.Add(self.dirBtn,0,wx.EXPAND,1) - + subSiz = wx.BoxSizer(wx.HORIZONTAL) self.opt = {'useBak':False, 'sort':0, 'filter':'*'} chk = G2CheckBoxFrontLbl(self,' Include .bakXX?',self.opt,'useBak', @@ -9256,14 +8751,14 @@ def __init__(self,parent,startdir='.',multiple=False,*args,**kwargs): subSiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL,0) subSiz.Add((10,-1),1,wx.EXPAND,1) subSiz.Add(wx.StaticText(self,wx.ID_ANY,'Name \nFilter: '),0,wx.ALIGN_CENTER_VERTICAL,1) - self.filterBox = ValidatedTxtCtrl(self, self.opt, 'filter', + self.filterBox = ValidatedTxtCtrl(self, self.opt, 'filter', size=(80,-1), style=wx.TE_PROCESS_ENTER, OnLeave=self.DirSelected, notBlank=False) self.filterBox.Bind(wx.EVT_TEXT,self._startUpdateTimer) self.filterBox.Bind(wx.EVT_TEXT_ENTER,self.DirSelected) subSiz.Add(self.filterBox) subSiz.Add((2,-1)) - + topSizer.Add(subSiz,0,wx.EXPAND,0) mainPanel = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_LIVE_UPDATE|wx.SP_3D) @@ -9282,7 +8777,7 @@ def __init__(self,parent,startdir='.',multiple=False,*args,**kwargs): wx.NO_BORDER|wx.richtext.RE_READONLY) mainPanel.SplitVertically(self.fileBox, self.rtc, 200) topSizer.Add(mainPanel,1,wx.EXPAND) - + subSiz = wx.BoxSizer(wx.HORIZONTAL) subSiz.Add((-1,-1),1,wx.EXPAND,1) self.OKbtn = wx.Button(self, wx.ID_OK, label='Open') @@ -9304,10 +8799,10 @@ def _startUpdateTimer(self,event): self.timer.Restart(self.delay) else: self.timer = wx.CallLater(self.delay,self.DirSelected) - + def DirSelected(self,event=None,*args,**kwargs): - '''Respond to a directory being selected. List files found in fileBox and - clear any selections. Also clear any reference to a timer. + '''Respond to a directory being selected. List files found in fileBox and + clear any selections. Also clear any reference to a timer. ''' import re try: @@ -9330,7 +8825,7 @@ def DirSelected(self,event=None,*args,**kwargs): if self.opt['useBak']: self.sl = [(os.path.split(i)[1],os.stat(i).st_mtime,i) for i in self.fl] else: - self.sl = [(os.path.split(i)[1],os.stat(i).st_mtime,i) for i in self.fl + self.sl = [(os.path.split(i)[1],os.stat(i).st_mtime,i) for i in self.fl if not re.match(r'.*\.bak\d+\.gpx.*',i)] if self.opt['sort'] == 0: self.sl.sort(key=lambda x: x[1],reverse=True) @@ -9339,10 +8834,10 @@ def DirSelected(self,event=None,*args,**kwargs): else: self.sl.sort(key=lambda x: x[0].lower()) items = [i[0]+' ('+self._fmtTimeStampDelta(i[1])+')' for i in self.sl] - if items: + if items: self.fileBox.InsertItems(items,0) - - def FileSelected(self,event): + + def FileSelected(self,event): '''Respond to a file being selected (or checked in multiple mode) ''' if self.multiple: # disable Open when nothing is selected @@ -9401,7 +8896,7 @@ def displayGPXrtc(self,result,fwp): self.rtc.WriteText(line+'\n') self.rtc.EndLeftIndent() self.rtc.EndParagraphSpacing() - + if 'PWDR' in result: self.rtc.BeginParagraphSpacing(0,0) self.rtc.BeginLeftIndent(0) @@ -9415,7 +8910,7 @@ def displayGPXrtc(self,result,fwp): self.rtc.WriteText(line+'\n') self.rtc.EndLeftIndent() self.rtc.EndParagraphSpacing() - + if 'error' in result: self.rtc.Newline() self.rtc.BeginBold() @@ -9457,7 +8952,7 @@ def onSetColour(event): array[key] = data.GetColour().Get() print('OK',array[key]) finally: - dlg.Destroy() + dlg.Destroy() if wx.__version__.startswith('4.1'): colorButton = wx.Button(parent,wx.ID_ANY,'Set') colorButton.Bind(wx.EVT_BUTTON, onSetColour) @@ -9466,32 +8961,94 @@ def onSetColour(event): colorButton.Bind(wcs.EVT_COLOURSELECT, OnColor) return colorButton +CitationDict = {} +def SaveCite(prog,text): + '''Save citation information as it is referenced so that all of it can be + displayed in the About GSAS-II window + ''' + global CitationDict + CitationDict[prog] = text + +def GetCite(key,wrap=None,indent=None): + '''Return citation information, optionally with text wrapping. + ''' + if GSASIIpath.GetConfigValue('debug') and key not in CitationDict: + print(f'Warning: GetCite citation ref {key!r} not defined.') + txt = CitationDict.get(key,'') + leftmargin = '' + if indent: + leftmargin=indent*' ' + if wrap: + import textwrap + txt = '\n'.join(textwrap.wrap(txt,wrap, + replace_whitespace=False, + break_long_words=False, + initial_indent=leftmargin, subsequent_indent=leftmargin)) + return txt +######################### +# Catalog citation refs # +######################### +# OK to call SaveCite anywhere in GSAS-II, but putting calls here allows control +# over their sequence. Calls anywhere else will show up after these. +SaveCite('small angle scattering', + 'R.B. Von Dreele, J. Appl. Cryst. 47, 1748-9 (2014)') +SaveCite('DIFFaX', + 'M.M.J. Treacy, J.M. Newsam & M.W. Deem, Proc. Roy. Soc. Lond. A 433, 499-520 (1991) doi: https://doi.org/10.1098/rspa.1991.0062') +SaveCite('NIST*LATTICE', +'''V. L. Karen and A. D. Mighell, NIST Technical Note 1290 (1991), https://nvlpubs.nist.gov/nistpubs/Legacy/TN/nbstechnicalnote1290.pdf; V. L. Karen & A. D. Mighell, U.S. Patent 5,235,523, https://patents.google.com/patent/US5235523A/en?oq=5235523''') +SaveCite('Parameter Impact', + 'Toby, B. H. (2024). "A simple solution to the Rietveld refinement recipe problem." J. Appl. Cryst. 57(1): 175-180.') +SaveCite('Fundamental parameter fitting', +'''MH Mendenhall, K Mullen && JP Cline (2015), J. Res. of NIST, 120, p223. DOI: 10.6028/jres.120.014; +For Incident Beam Mono model, also cite: MH Mendenhall, D Black && JP Cline (2019), J. Appl. Cryst., 52, p1087. DOI: 10.1107/S1600576719010951 +''') +SaveCite('RMCProfile', +'"RMCProfile: Reverse Monte Carlo for polycrystalline materials", M.G. Tucker, D.A. Keen, M.T. Dove, A.L. Goodwin and Q. Hui, Jour. Phys.: Cond. Matter 2007, 19, 335218. doi: https://doi.org/10.1088/0953-8984/19/33/335218') +SaveCite('PDFfit2', +'"PDFfit2 and PDFgui: computer programs for studying nanostructures in crystals", C.L. Farrow, P.Juhas, J.W. Liu, D. Bryndin, E.S. Bozin, J. Bloch, Th. Proffen and S.J.L. Billinge, J. Phys, Condens. Matter 19, 335219 (2007)., https://doi.org/10.1088/0953-8984/19/33/335219') +SaveCite('ISOTROPY, ISODISTORT, ISOCIF...', +'H. T. Stokes, D. M. Hatch, and B. J. Campbell, ISOTROPY Software Suite, iso.byu.edu.; B. J. Campbell, H. T. Stokes, D. E. Tanner, and D. M. Hatch, "ISODISPLACE: An Internet Tool for Exploring Structural Distortions." J. Appl. Cryst. 39, 607-614 (2006).') +SaveCite('ISODISPLACE', +'D. E. Tanner, and D. M. Hatch, "ISODISPLACE: An Internet Tool for Exploring Structural Distortions." J. Appl. Cryst. 39, 607-614 (2006).') +SaveCite('fullrmc', +'''"Atomic Stochastic Modeling & Optimization with fullrmc", B. Aoun, J. Appl. Cryst. 2022, 55(6) 1664-1676, DOI: 10.1107/S1600576722008536. +"Fullrmc, a Rigid Body Reverse Monte Carlo Modeling Package Enabled with Machine Learning and Artificial Intelligence", B. Aoun, Jour. Comp. Chem. 2016, 37, 1102-1111. DOI: 10.1002/jcc.24304''') +SaveCite('Bilbao: PSEUDO', +'''C. Capillas, E.S. Tasci, G. de la Flor, D. Orobengoa, J.M. Perez-Mato and M.I. Aroyo. "A new computer tool at the Bilbao Crystallographic Server to detect and characterize pseudosymmetry". Z. Krist. (2011), 226(2), 186-196 DOI:10.1524/zkri.2011.1321.''') +SaveCite('Bilbao: k-SUBGROUPSMAG', +'Symmetry-Based Computational Tools for Magnetic Crystallography, J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and M.I. Aroyo, Annu. Rev. Mater. Res. 2015. 45,217-48. doi: 10.1146/annurev-matsci-070214-021008') +SaveCite('Bilbao: PSEUDOLATTICE', +'Bilbao Crystallographic Server I: Databases and crystallographic computing programs, M. I. Aroyo, J. M. Perez-Mato, C. Capillas, E. Kroumova, S. Ivantchev, G. Madariaga, A. Kirov & H. Wondratschek, Z. Krist. 221, 1, 15-27 (2006). doi: https://doi.org/doi:10.1524/zkri.2006.221.1.15''') +SaveCite('Bilbao+GSAS-II magnetism', +'Determining magnetic structures in GSAS-II using the Bilbao Crystallographic Server tool k-SUBGROUPSMAG, R.B. Von Dreele & L. Elcoro, Acta Cryst. 2024, B80. doi: https://doi.org/10.1107/S2052520624008436') +SaveCite('SHAPES', +'A New Algorithm for the Reconstruction of Protein Molecular Envelopes from X-ray Solution Scattering Data, J. Badger, Jour. of Appl. Chrystallogr. 2019, 52, 937-944. doi: https://doi.org/10.1107/S1600576719009774') +SaveCite('Scikit-Learn', +'"Scikit-learn: Machine Learning in Python", Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J., Passos, A., Cournapeau, D., Brucher, M., Perrot, M. and Duchesnay, E., Journal of Machine Learning Research (2011) 12, 2825-2830.') +SaveCite('Dysnomia', +'Dysnomia, a computer program for maximum-entropy method (MEM) analysis and its performance in the MEM-based pattern fitting, K. Moma, T. Ikeda, A.A. Belik & F. Izumi, Powder Diffr. 2013, 28, 184-193. doi: https://doi.org/10.1017/S088571561300002X') + def NISTlatUse(msgonly=False): - msg = '''Performing cell symmetry search using NIST*LATTICE. Please cite: - V. L. Karen and A. D. Mighell, NIST Technical Note 1290 (1991), - https://nvlpubs.nist.gov/nistpubs/Legacy/TN/nbstechnicalnote1290.pdf - and - V. L. Karen & A. D. Mighell, U.S. Patent 5,235,523, - https://patents.google.com/patent/US5235523A/en?oq=5235523''' + msg = f'Performing cell symmetry search using NIST*LATTICE.\n\nPlease cite: {GetCite("NIST*LATTICE")}' print(msg) if msgonly: return msg wx.MessageBox(msg,caption='Using NIST*LATTICE',style=wx.ICON_INFORMATION) def Load2Cells(G2frame,phase): - '''Accept two unit cells and use NIST*LATTICE to search for a relationship - that relates them. + '''Accept two unit cells and use NIST*LATTICE to search for a relationship + that relates them. The first unit cell is initialized as the currently selected phase and - the second unit cell is set to the first different phase from the tree. - The user can initialize the cell parameters to select a different phase - for either cell or can type in the values themselves. + the second unit cell is set to the first different phase from the tree. + The user can initialize the cell parameters to select a different phase + for either cell or can type in the values themselves. :param wx.Frame G2frame: The main GSAS-II window :param dict phase: the currently selected frame ''' def setRatioMax(*arg,**kwarg): - '''Set the value for the max volume used in the search according - to the type of search selected. + '''Set the value for the max volume used in the search according + to the type of search selected. ''' if nistInput[2] == 'I': volRatW.Validator.xmax = 40 @@ -9502,7 +9059,7 @@ def setRatioMax(*arg,**kwarg): volRatW.SetValue(min(volRatW.Validator.xmax,nistInput[3])) def computeNISTlatCompare(event): 'run NIST*LATTICE after the compute button is pressed' - import nistlat + from . import nistlat out = nistlat.CompareCell(cellLen[0], cellCntr[0], cellLen[1], cellCntr[1], tolerance=3*[nistInput[0]]+3*[nistInput[1]], @@ -9518,7 +9075,7 @@ def computeNISTlatCompare(event): 'No transforms were found within supplied limits', 'No transforms found') def setCellFromPhase(event): - '''respond to "set from phase" button. A phase is selected and + '''respond to "set from phase" button. A phase is selected and the unit cell info is loaded from that phase into the appropriate cell widgets. ''' @@ -9548,8 +9105,8 @@ def setCellFromPhase(event): wid.SetValue(val) widgets[6].SetValue(cellCntr[cell]) dlg.Raise() # needed to bring modal dialog to front, at least on Mac - - # Load2Cells starts here + + # Load2Cells starts here msg = NISTlatUse(True) nistInput=[0.2,1.,'I',8] cellLen = [None,None] @@ -9639,16 +9196,16 @@ def setCellFromPhase(event): sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 5) dlg.SetSizer(sizer) sizer.Fit(dlg) - + if dlg.ShowModal() == wx.ID_OK: - dlg.Destroy() + dlg.Destroy() else: - dlg.Destroy() + dlg.Destroy() return class ScrolledStaticText(wx.StaticText): - '''Fits a long string into a small space by scrolling it. Inspired by - ActiveText.py from J Healey + '''Fits a long string into a small space by scrolling it. Inspired by + ActiveText.py from J Healey https://discuss.wxpython.org/t/activetext-rather-than-statictext/36370 Use examples:: @@ -9664,19 +9221,19 @@ class ScrolledStaticText(wx.StaticText): ms.Add(txt) ms.Add(G2G.ScrolledStaticText(frm,label=text,dots=False,delay=250,lbllen=20)) frm.SetSizer(ms) - + :param w.Frame parent: Frame or Panel where widget will be placed :param str label: string to be displayed :param int delay: time between updates in ms (default is 100) :param int lbllen: number of characters to show (default is 15) - :param bool dots: If True (default) ellipsis (...) are placed - at the beginning and end of the string when any characters - in the string are not shown. The displayed string length + :param bool dots: If True (default) ellipsis (...) are placed + at the beginning and end of the string when any characters + in the string are not shown. The displayed string length will thus be lbllen+6 most of the time :param (other): other optional keyword parameters for the wx.StaticText widget such as size or style may be specified. ''' - def __init__(self, parent, label='', delay=100, lbllen=15, + def __init__(self, parent, label='', delay=100, lbllen=15, dots=True, **kwargs): wx.StaticText.__init__(self, parent, wx.ID_ANY, '', **kwargs) self.fullmsg = label @@ -9703,120 +9260,6 @@ def onTimer(self,event): self.msgpos += 1 if self.msgpos >= len(self.fullmsg): self.msgpos = 0 -#=========================================================================== -# this has been moved to GSASIIfiles, since it does not need wx -# def openInNewTerm(project=None,g2script=None,pythonapp=sys.executable): -# '''Open a new and independent GSAS-II session in separate terminal -# or console window and as a separate process that will continue -# even if the calling process exits. -# Intended to work on all platforms. - -# This could be used to run other scripts inside python other than GSAS-II - -# :param str project: the name of an optional parameter to be -# passed to the script (usually a .gpx file to be opened in -# a new GSAS-II session) -# :param str g2script: the script to be run. If None (default) -# the GSASII.py file in the same directory as this file will -# be used. -# :param str pythonapp: the Python interpreter to be used. -# Defaults to sys.executable which is usually what is wanted. -# :param str terminal: a name for a preferred terminal emulator -# ''' -# import subprocess -# if g2script is None: -# g2script = os.path.join(os.path.dirname(__file__),'GSASII.py') - -# if sys.platform == "darwin": -# if project: -# script = f''' -# set python to "{pythonapp}" -# set appwithpath to "{g2script}" -# set filename to "{project}" -# set filename to the quoted form of the POSIX path of filename - -# tell application "Terminal" -# activate -# do script python & " " & appwithpath & " " & filename & "; exit" -# end tell -# ''' -# else: -# script = f''' -# set python to "{pythonapp}" -# set appwithpath to "{g2script}" - -# tell application "Terminal" -# activate -# do script python & " " & appwithpath & " " & "; exit" -# end tell -# ''' -# subprocess.Popen(["osascript","-e",script]) -# elif sys.platform.startswith("win"): -# cmds = [pythonapp, g2script] -# if project: cmds += [project] -# subprocess.Popen(cmds,creationflags=subprocess.CREATE_NEW_CONSOLE) -# else: -# import shutil -# script = '' -# # try a bunch of common terminal emulators in Linux -# # there does not appear to be a good way to way to specify this -# # perhaps this should be a GSAS-II config option -# for term in ("lxterminal", "gnome-terminal", 'konsole', "xterm", -# "terminator", "terminology", "tilix"): -# try: -# found = shutil.which(term) -# if not found: continue -# except AttributeError: -# print(f'shutil.which() failed (why?); assuming {term} present') -# found = True -# if term == "gnome-terminal": -# #terminal = 'gnome-terminal -t "GSAS-II console" --' -# cmds = [term,'--title','"GSAS-II console"','--'] -# script = "echo; echo Press Enter to close window; read line" -# break -# elif term == "lxterminal": -# #terminal = 'lxterminal -t "GSAS-II console" -e' -# cmds = [term,'-t','"GSAS-II console"','-e'] -# script = "echo;echo Press Enter to close window; read line" -# break -# elif term == "xterm": -# #terminal = 'xterm -title "GSAS-II console" -hold -e' -# cmds = [term,'-title','"GSAS-II console"','-hold','-e'] -# script = "echo; echo This window can now be closed" -# break -# elif term == "terminator": -# cmds = [term,'-T','"GSAS-II console"','-x'] -# script = "echo;echo Press Enter to close window; read line" -# break -# elif term == "konsole": -# cmds = [term,'-p','tabtitle="GSAS-II console"','--hold','-e'] -# script = "echo; echo This window can now be closed" -# break -# elif term == "tilix": -# cmds = [term,'-t','"GSAS-II console"','-e'] -# script = "echo;echo Press Enter to close window; read line" -# break -# elif term == "terminology": -# cmds = [term,'-T="GSAS-II console"','--hold','-e'] -# script = "echo; echo This window can now be closed" -# break -# else: -# print("No known terminal was found to use, Can't start {}") -# return - -# fil = '/tmp/GSAS2-launch.sh' -# cmds += ['/bin/sh',fil] -# fp = open(fil,'w') -# if project: -# fp.write(f"{pythonapp} {g2script} {project}\n") -# else: -# fp.write(f"{pythonapp} {g2script}\n") -# fp.write(f"rm {fil}\n") -# if script: -# fp.write(f"{script}\n") -# fp.close() -# subprocess.Popen(cmds,start_new_session=True) - #=========================================================================== def ExtractFileFromZip(filename, selection=None, confirmread=True, confirmoverwrite=True, parent=None, @@ -9841,7 +9284,7 @@ def ExtractFileFromZip(filename, selection=None, confirmread=True, :param str msg: a message explaining what is being read. Default is blank. - + :returns: the name of the file that has been created or a list of files (see multipleselect) @@ -9875,7 +9318,7 @@ def ExtractFileFromZip(filename, selection=None, confirmread=True, 'Is file '+str(zinfo[0].filename)+ ' what you want to extract from '+ str(os.path.split(filename)[1])+'?', - 'Confirm file', + 'Confirm file', wx.YES_NO | wx.ICON_QUESTION) try: result = dlg.ShowModal() @@ -9909,7 +9352,7 @@ def ExtractFileFromZip(filename, selection=None, confirmread=True, else: zlist = [-1] dlg.Destroy() - + outlist = [] for zindex in zlist: if zindex >= 0: @@ -9968,7 +9411,7 @@ def gitFetch(G2frame): count += 1 if count > 10: G2MessageBox(G2frame, - 'Background git update has not completed, try again later', + 'Background git update has not completed, try again later', title='Warning') return time.sleep(1) @@ -9976,6 +9419,7 @@ def gitFetch(G2frame): wx.GetApp().Yield() if not ok: return + if GSASIIpath.GetConfigValue('debug'): print('background update complete') # try update one more time just to make sure GSASIIpath.gitGetUpdate('immediate') @@ -9984,17 +9428,17 @@ def gitFetch(G2frame): finally: pdlg.Destroy() wx.EndBusyCursor() - + def gitCheckUpdates(G2frame): '''Used to update to the latest GSAS-II version, but checks for a variety - of repository conditions that could make this process more complex. If - there are uncommitted local changes, these changes must be cached or - deleted first. If there are local changes that have been committed or a new + of repository conditions that could make this process more complex. If + there are uncommitted local changes, these changes must be cached or + deleted first. If there are local changes that have been committed or a new branch has been created, the user (how obstensibly must know use of git) - will probably need to do this manually. If GSAS-II has previously been + will probably need to do this manually. If GSAS-II has previously been regressed (using :func:`gitSelectVersion`), then this is noted as well. - When all is done, function :func:`GSASIIpath.gitStartUpdate` is called to + When all is done, function :func:`GSASIIpath.gitStartUpdate` is called to actually perform the update. ''' try: @@ -10014,7 +9458,7 @@ def gitCheckUpdates(G2frame): localChanges = bool(status & 5) # If True need to select between # --git-stash or --git-reset # False: neither should be used - + if status&8: # not on local branch if localChanges: G2MessageBox(G2frame, @@ -10109,21 +9553,21 @@ def gitAskSave(G2frame,regressmsg,cmds): return True elif ans == wx.ID_YES: ans = G2frame.OnFileSave(None) - if ans: + if ans: cmds += [G2frame.GSASprojectfile] return False - + def gitSelectVersion(G2frame): - '''Used to regress to a previous GSAS-II version, checking first - for a variety of repository conditions that could make this process - more complex. If there are uncommitted local changes, these changes - must be cached or deleted before a different version can be installed. - If there are local changes that have been committed or a new + '''Used to regress to a previous GSAS-II version, checking first + for a variety of repository conditions that could make this process + more complex. If there are uncommitted local changes, these changes + must be cached or deleted before a different version can be installed. + If there are local changes that have been committed or a new branch has been created, the user (how obstensibly must know use of git) - will probably need to do this manually. If GSAS-II has previously been + will probably need to do this manually. If GSAS-II has previously been regressed (using :func:`gitSelectVersion`), then this is noted as well. - When all is done, function :func:`GSASIIpath.gitStartUpdate` is called to + When all is done, function :func:`GSASIIpath.gitStartUpdate` is called to actually perform the update. ''' # get updates from server @@ -10181,7 +9625,7 @@ def gitSelectVersion(G2frame): 'desired version manually') G2MessageBox(G2frame,msg,title='Do manual update') return - + # browse and select a version here dlg = gitVersionSelector() ans = dlg.ShowModal() @@ -10204,8 +9648,8 @@ def gitSelectVersion(G2frame): GSASIIpath.gitStartUpdate(cmds) def gitSelectBranch(event): - '''Pull in latest GSAS-II branches on origin server; Allow user to - select a branch; checkout that branch and restart GSAS-II. + '''Pull in latest GSAS-II branches on origin server; Allow user to + select a branch; checkout that branch and restart GSAS-II. Expected to be used by developers and by expert users only. ''' G2frame = wx.App.GetMainTopWindow() @@ -10215,7 +9659,7 @@ def gitSelectBranch(event): 'Unable to switch branches unless GSAS-II has been installed from GitHub; installed as: '+gitInst, 'Not a git install') return - if not os.path.exists(GSASIIpath.path2GSAS2): + if not os.path.exists(GSASIIpath.path2GSAS2): print(f'Warning: Directory {GSASIIpath.path2GSAS2} not found') return if os.path.exists(os.path.join(GSASIIpath.path2GSAS2,'..','.git')): @@ -10269,7 +9713,7 @@ def gitSelectBranch(event): dlg.Destroy() msg = f'''Confirm switching from git branch {g2repo.active_branch.name!r} to {b!r}. -If confirmed here, GSAS-II will restart. +If confirmed here, GSAS-II will restart. Do you want to save your project before restarting? Select "Yes" to save, "No" to skip the save, or "Cancel" @@ -10522,204 +9966,15 @@ def gitSwitchMaster2Main(): print(f'Unexpected error: file {g2script!r} not found') #=========================================================================== -def svnCheckUpdates(G2frame): - '''Check if the GSAS-II repository has an update for the current - source files and perform that update if requested. - ''' - wx.BeginBusyCursor() - local = GSASIIpath.svnGetRev() - if local is None: - wx.EndBusyCursor() - dlg = wx.MessageDialog(G2frame, - 'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?', - 'Subversion error', - wx.OK) - dlg.ShowModal() - dlg.Destroy() - return - print ('Installed GSAS-II version: '+local) - repos = GSASIIpath.svnGetRev(local=False) - wx.EndBusyCursor() - # has the current branch disappeared? If so, switch to the trunk -- not fully tested - if (repos is None and "not found" in GSASIIpath.svnLastError.lower() - and "path" in GSASIIpath.svnLastError.lower()): - print('Repository is gone, will switch to trunk') - GSASIIpath.svnSwitch2branch() - return - elif repos is None: - dlg = wx.MessageDialog(G2frame, - 'Unable to access the GSAS-II server. Is this computer on the internet?', - 'Server unavailable', - wx.OK) - dlg.ShowModal() - dlg.Destroy() - return - errmsg,warnmsg = G2gd.TestOldVersions() - if (errmsg or warnmsg): - msg = 'Based on the age of Python or an installed Python package (see below)' - msg += ' you are recommended to' - if GSASIIpath.condaTest(): - msg += ' either use conda to update (see https://bit.ly/G2pkgs for version recommendations) and then update GSAS-II. Or' - msg += ' reinstall GSAS-II (see https://bit.ly/G2install), which will update both.\n\n' - if errmsg: - opt = wx.YES_NO|wx.ICON_QUESTION|wx.CANCEL|wx.NO_DEFAULT - msg += 'Error(s):\n\t'+errmsg - else: - opt = wx.YES_NO|wx.ICON_QUESTION|wx.CANCEL|wx.YES_DEFAULT - if warnmsg: - if errmsg: msg += '\n\nWarning(s):\n\t' - msg += warnmsg - - msg += '\n\nContinue to update GSAS-II?' - dlg = wx.MessageDialog(G2frame, msg,'Confirm update',opt) - result = wx.ID_NO - try: - result = dlg.ShowModal() - finally: - dlg.Destroy() - if result != wx.ID_YES: return - print ('GSAS-II version on server: '+repos) - if local == repos: - up,mod,lock = GSASIIpath.svnGetFileStatus() - else: - up = 0 - mods = GSASIIpath.svnFindLocalChanges() - mod = len(mods) - if local == repos and up: - dlg = wx.MessageDialog(G2frame, - 'You have the current version '+ - ' of GSAS-II installed ('+repos+ - '). However, '+str(up)+ - ' file(s) still need to be updated.'+ - ' Most likely a previous update failed to finish.'+ - '\n\nPress OK to retry the update.'+ - '\n\nIf this problem continues contact toby@anl.gov', - 'Failed Update Likely', - wx.OK|wx.CANCEL) - if dlg.ShowModal() != wx.ID_OK: - dlg.Destroy() - return - else: - dlg.Destroy() - if lock: GSASIIpath.svnCleanup() - elif local == repos: - dlg = wx.MessageDialog(G2frame, - 'GSAS-II is up-to-date. Version '+local+' is already loaded.', - 'GSAS-II Up-to-date', - wx.OK) - dlg.ShowModal() - dlg.Destroy() - return - if mod: - dlg = wx.MessageDialog(G2frame, - 'You have version '+local+ - ' of GSAS-II installed, but the current version is '+repos+ - '. However, '+str(mod)+ - ' file(s) on your local computer have been modified.' - ' Updating will attempt to merge your local changes with ' - 'the latest GSAS-II version, but if ' - 'conflicts arise, local changes will be ' - 'discarded. It is also possible that the ' - 'merge may prevent GSAS-II from running. ' - '\n\nPress OK to start an update if this is acceptable:', - 'Local GSAS-II Mods', - wx.OK|wx.CANCEL) - if dlg.ShowModal() != wx.ID_OK: - dlg.Destroy() - return - else: - dlg.Destroy() - else: - dlg = wx.MessageDialog(G2frame, - 'You have version '+local+ - ' of GSAS-II installed, but the current version is '+repos+ - '. Press OK to start an update:', - 'GSAS-II Updates', - wx.OK|wx.CANCEL) - if dlg.ShowModal() != wx.ID_OK: - dlg.Destroy() - return - dlg.Destroy() - print ('start updates') - if G2frame.GPXtree.GetCount() > 1: - dlg = wx.MessageDialog(G2frame, - 'Your project will now be saved, GSAS-II will exit and an update '+ - 'will be performed and GSAS-II will restart. Press Cancel '+ - 'in next dialog to avoid saving the project.', - 'Starting update', - wx.OK) - dlg.ShowModal() - dlg.Destroy() - G2frame.OnFileSave(None) - GPX = G2frame.GSASprojectfile - GSASIIpath.svnUpdateProcess(projectfile=GPX) - else: - GSASIIpath.svnUpdateProcess() - return - -def svnSelectVersion(G2frame): - '''Allow the user to select a specific version of GSAS-II from the - APS svn server - ''' - local = GSASIIpath.svnGetRev() - if local is None: - dlg = wx.MessageDialog(G2frame, - 'Unable to run subversion on the GSAS-II current directory. Is GSAS-II installed correctly?', - 'Subversion error', - wx.OK) - dlg.ShowModal() - dlg.Destroy() - return - mods = GSASIIpath.svnFindLocalChanges() - if mods: - dlg = wx.MessageDialog(G2frame, - 'You have version '+local+ - ' of GSAS-II installed' - '. However, '+str(len(mods))+ - ' file(s) on your local computer have been modified.' - ' Downdating will attempt to merge your local changes with ' - 'the selected GSAS-II version. ' - 'Downdating is not encouraged because ' - 'if merging is not possible, your local changes will be ' - 'discarded. It is also possible that the ' - 'merge may prevent GSAS-II from running. ' - 'Press OK to continue anyway.', - 'Local GSAS-II Mods', - wx.OK|wx.CANCEL) - if dlg.ShowModal() != wx.ID_OK: - dlg.Destroy() - return - dlg.Destroy() - if GSASIIpath.svnGetRev(local=False) is None: - dlg = wx.MessageDialog(G2frame, - 'Error obtaining current GSAS-II version. Is internet access working correctly?', - 'Subversion error', - wx.OK) - dlg.ShowModal() - dlg.Destroy() - return - dlg = downdate(parent=G2frame) - if dlg.ShowModal() == wx.ID_OK: - ver = dlg.getVersion() - else: - dlg.Destroy() - return - dlg.Destroy() - print('start regress to '+str(ver)) - G2frame.OnFileSave(None) - GPX = G2frame.GSASprojectfile - GSASIIpath.svnUpdateProcess(projectfile=GPX,version=str(ver)) - return - # Importer GUI stuff def ImportMsg(parent,msgs): - '''Show a message with the warnings from importers that - could not be installed (due to uninstalled Python packages). Then + '''Show a message with the warnings from importers that + could not be installed (due to uninstalled Python packages). Then offer the chance to install GSAS-II packages using :func:`SelectPkgInstall` ''' - text = ('Message(s) from load of importers\n\n '+ - '\n\n'.join(msgs)+ - '\n\nNote: These errors only need to be addressed if you want to use the importers listed above') + text = ('Warnings message(s) from load of importers:\n\n * '+ + '\n\n * '.join(msgs)+ + '\n\n\tTo use any of these importers, press the "Install packages" button\n\tbelow. It is fine to ignore these warnings if you will not need to read\n\tthat file type.') ShowScrolledInfo(parent,text, header='Importer load problems', width=650, @@ -10728,7 +9983,7 @@ def ImportMsg(parent,msgs): def patch_condarc(): '''Comment out any references to "file:" locations in the .condarc - file. These should not be there and cause problems. + file. These should not be there and cause problems. ''' rc = os.path.normpath(os.path.join(GSASIIpath.path2GSAS2,'..','..','.condarc')) @@ -10740,29 +9995,37 @@ def patch_condarc(): print(f'Patching file {rc}') with open(rc,'w') as fp: fp.write(txt.replace('\n - /','\n# - /')) - + def SelectPkgInstall(event): - '''Offer the user a chance to install Python packages needed by one or - more importers. There might be times where something like this will be + '''Offer the user a chance to install Python packages needed by one or + more importers. There might be times where something like this will be useful for other GSAS-II actions. ''' dlg = event.GetEventObject().GetParent() - dlg.EndModal(wx.ID_OK) + if hasattr(dlg,'EndModal'): # present when called from a dialog + dlg.EndModal(wx.ID_OK) G2frame = wx.App.GetMainTopWindow() - choices = [] + + choices = {} + keylist = [] for key in G2fil.condaRequestList: for item in G2fil.condaRequestList[key]: - choices.append((item,key)) - msg = 'Select packages to install' + if item in choices: + choices[item] += f', {key}' + else: + choices[item] = key + keylist.append(item) + msg = 'Select package(s) to install' if GSASIIpath.condaTest(): msg += ' using conda' else: msg += ' using pip' sel = MultiColMultiSelDlg(G2frame, 'Install packages?', msg, - [('package',120,0),('needed by',300,0)], choices) + [('package',120,0),('needed by',300,0)], + [i for i in choices.items()]) if sel is None: return if not any(sel): return - pkgs = [choices[i][0] for i,f in enumerate(sel) if f] + pkgs = [keylist[i] for i,f in enumerate(sel) if f] wx.BeginBusyCursor() pdlg = wx.ProgressDialog('Updating','Installing Python packages; this can take a while', parent=G2frame) @@ -10837,7 +10100,7 @@ def SelectPkgInstall(event): #ShowWebPage('http://wxpython.org',G2frame,internal=True) #ShowHelp('hist/phase',G2frame,'internal') testAtoms = [''] - + nm = [' ','0','1','-1','2','-2','3','-3','4','5','6','7','8','9'] dm = ['1','2','3','4','5','6'] kfmt = ['choice','/','choice',', ','choice','/','choice',', ','choice','/','choice',' '] @@ -10846,7 +10109,7 @@ def strTest(text): return False elif text.strip() in [' ','0','1','-1','3/2']: # specials return True - elif '/' in text: #process fraction + elif '/' in text: #process fraction nums = text.split('/') return (0 < int(nums[1]) < 10) and (0 < abs(int(nums[0])) < int(nums[1])) return False @@ -10871,7 +10134,7 @@ def strTest(text): 'bool','choice','bool','choice','bool','%d',], header=msg) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetValues()) - + # if True: # title='title here' @@ -10889,7 +10152,7 @@ def strTest(text): sys.exit() app.MainLoop() - + # choices = [wx.ID_YES,wx.ID_NO] # warnmsg = '\nsome info\non a few lines\nand one more' # ans = ShowScrolledInfo(header='Constraint Warning', @@ -10899,7 +10162,7 @@ def strTest(text): # print(ans, choices) import sys; sys.exit() - + #====================================================================== # test Grid with GridFractionEditor #====================================================================== diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index e745a3544..040b57563 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- #GSASIIdataGUI - Main GUI routines ''' -Routines for main GUI wx.Frame follow. +Routines for main GUI wx.Frame follow. ''' from __future__ import division, print_function import platform @@ -11,6 +11,7 @@ import random as ran import copy import sys +import shutil import os import inspect import re @@ -42,30 +43,30 @@ def new_util_find_library( name ): import wx.lib.scrolledpanel as wxscroll except ImportError: pass -import GSASIIpath -import GSASIImath as G2mth -import GSASIImiscGUI as G2IO -import GSASIIfiles as G2fil -import GSASIIstrIO as G2stIO -import GSASIIlattice as G2lat -import GSASIIplot as G2plt -import GSASIIpwdplot as G2pwpl -import GSASIIpwdGUI as G2pdG -import GSASIIimgGUI as G2imG -import GSASIIphsGUI as G2phG -import GSASIIspc as G2spc -import GSASIImapvars as G2mv -import GSASIIconstrGUI as G2cnstG -import GSASIIrestrGUI as G2restG -import GSASIIobj as G2obj -import GSASIIctrlGUI as G2G -import GSASIIElem as G2elem -import GSASIIpwd as G2pwd -import GSASIIstrMain as G2stMn -import defaultIparms as dI -import GSASIIfpaGUI as G2fpa -import GSASIIseqGUI as G2seq -import GSASIIddataGUI as G2ddG +from . import GSASIIpath +from . import GSASIImath as G2mth +from . import GSASIImiscGUI as G2IO +from . import GSASIIfiles as G2fil +from . import GSASIIstrIO as G2stIO +from . import GSASIIlattice as G2lat +from . import GSASIIplot as G2plt +from . import GSASIIpwdplot as G2pwpl +from . import GSASIIpwdGUI as G2pdG +from . import GSASIIimgGUI as G2imG +from . import GSASIIphsGUI as G2phG +from . import GSASIIspc as G2spc +from . import GSASIImapvars as G2mv +from . import GSASIIconstrGUI as G2cnstG +from . import GSASIIrestrGUI as G2restG +from . import GSASIIobj as G2obj +from . import GSASIIctrlGUI as G2G +from . import GSASIIElem as G2elem +from . import GSASIIpwd as G2pwd +from . import GSASIIstrMain as G2stMn +from . import defaultIparms as dI +from . import GSASIIfpaGUI as G2fpa +from . import GSASIIseqGUI as G2seq +from . import GSASIIddataGUI as G2ddG try: wx.NewIdRef @@ -83,7 +84,7 @@ def new_util_find_library( name ): VERY_LIGHT_GREY = wx.Colour(240,240,240) DULL_YELLOW = (230,230,190) -# define Ids for wx menu items +# transformation matrices commonTrans = {'abc':np.eye(3),'a-cb':np.array([[1.,0.,0.],[0.,0.,-1.],[0.,1.,0.]]), 'ba-c':np.array([[0.,1.,0.],[1.,0.,0.],[0.,0.,-1.]]),'-cba':np.array([[0.,0.,-1.],[0.,1.,0.],[1.,0.,0.]]), 'bca':np.array([[0.,1.,0.],[0.,0.,1.],[1.,0.,0.]]),'cab':np.array([[0.,0.,1.],[1.,0.,0.],[0.,1.,0.]]), @@ -91,13 +92,13 @@ def new_util_find_library( name ): 'P->A':np.array([[-1.,0.,0.],[0.,-1.,1.],[0.,1.,1.]]),'R->O':np.array([[-1.,0.,0.],[0.,-1.,0.],[0.,0.,1.]]), 'P->B':np.array([[-1.,0.,1.],[0.,-1.,0.],[1.,0.,1.]]),'B->P':np.array([[-.5,0.,.5],[0.,-1.,0.],[.5,0.,.5]]), 'P->C':np.array([[1.,1.,0.],[1.,-1.,0.],[0.,0.,-1.]]),'C->P':np.array([[.5,.5,0.],[.5,-.5,0.],[0.,0.,-1.]]), - 'P->F':np.array([[-1.,1.,1.],[1.,-1.,1.],[1.,1.,-1.]]),'F->P':np.array([[0.,.5,.5],[.5,0.,.5],[.5,.5,0.]]), - 'P->I':np.array([[0.,1.,1.],[1.,0.,1.],[1.,1.,0.]]),'I->P':np.array([[-.5,.5,.5],[.5,-.5,.5],[.5,.5,-.5]]), - 'A->P':np.array([[-1.,0.,0.],[0.,-.5,.5],[0.,.5,.5]]),'O->R':np.array([[-1.,0.,0.],[0.,-1.,0.],[0.,0.,1.]]), + 'P->F':np.array([[-1.,1.,1.],[1.,-1.,1.],[1.,1.,-1.]]),'F->P':np.array([[0.,.5,.5],[.5,0.,.5],[.5,.5,0.]]), + 'P->I':np.array([[0.,1.,1.],[1.,0.,1.],[1.,1.,0.]]),'I->P':np.array([[-.5,.5,.5],[.5,-.5,.5],[.5,.5,-.5]]), + 'A->P':np.array([[-1.,0.,0.],[0.,-.5,.5],[0.,.5,.5]]),'O->R':np.array([[-1.,0.,0.],[0.,-1.,0.],[0.,0.,1.]]), 'abc*':np.eye(3), } commonNames = ['abc','bca','cab','a-cb','ba-c','-cba','P->A','A->P','P->B','B->P','P->C','C->P', 'P->I','I->P','P->F','F->P','H->R','R->H','R->O','O->R','abc*','setting 1->2'] #don't put any new ones after the setting one! - + def GetDisplay(pos): '''Gets display number (0=main display) for window position (pos). If pos outside all displays returns None @@ -109,17 +110,17 @@ def GetDisplay(pos): return ip return None -#### class definitions used for main GUI ###################################### +#### class definitions used for main GUI ###################################### class MergeDialog(wx.Dialog): ''' HKL transformation & merge dialog - + :param wx.Frame parent: reference to parent frame (or None) :param data: HKLF data - - ''' - + + ''' + def __init__(self,parent,data): - wx.Dialog.__init__(self,parent,wx.ID_ANY,'Setup HKLF merge', + wx.Dialog.__init__(self,parent,wx.ID_ANY,'Setup HKLF merge', pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.data = data @@ -133,32 +134,32 @@ def __init__(self,parent,data): self.Class = 'triclinic' self.Common = 'abc' self.Draw() - + def Draw(self): - + def OnCent(event): Obj = event.GetEventObject() self.Cent = Obj.GetValue() self.Laue = '' wx.CallAfter(self.Draw) - + def OnLaue(event): Obj = event.GetEventObject() self.Laue = Obj.GetValue() wx.CallAfter(self.Draw) - + def OnClass(event): Obj = event.GetEventObject() self.Class = Obj.GetValue() self.Laue = '' wx.CallAfter(self.Draw) - + def OnCommon(event): Obj = event.GetEventObject() self.Common = Obj.GetValue() self.Trans = commonTrans[self.Common] wx.CallAfter(self.Draw) - + self.panel.Destroy() self.panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -226,12 +227,12 @@ def OnCommon(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetSelection(self): return self.Trans,self.Cent,self.Laue @@ -244,7 +245,7 @@ def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() self.EndModal(wx.ID_CANCEL) - + def GUIpatches(): 'Misc fixes that only needs to be done when running a GUI' try: # patch for LANG environment var problem on occasional OSX machines @@ -256,13 +257,13 @@ def GUIpatches(): locale.getdefaultlocale() # PATCH: for Mavericks (OS X 10.9.x), wx produces an annoying warning about LucidaGrandeUI. # In case stderr has been suppressed there, redirect python error output to stdout. Nobody - # else should care much about this. + # else should care much about this. sys.stderr = sys.stdout def convVersion(version): - '''Convert a version string ("x", "x.y", "x.y.z") into a series of ints. + '''Convert a version string ("x", "x.y", "x.y.z") into a series of ints. - :returns: [i0, i1, i2] where None is used if a value is not specified + :returns: [i0, i1, i2] where None is used if a value is not specified and 0 is used if a field cannot be parsed. ''' vIntList = [None,None,None] @@ -281,7 +282,7 @@ def compareVersions(version1,version2): Note that '3.' matches '3.1', and '3.0' matches '3.0.1' but '3.0.0' does not match '3.0.1' - :returns: 0 if the versions match, -1 if version1 < version2, + :returns: 0 if the versions match, -1 if version1 < version2, or 1 if version1 > version2 ''' for v1,v2 in zip(convVersion(version1),convVersion(version2)): @@ -293,60 +294,60 @@ def compareVersions(version1,version2): # tabulate package versions that users should be warned about versionDict = {} -'''Variable versionDict is used to designate versions of packages that -should generate warnings or error messages. +'''Variable versionDict is used to designate versions of packages that +should generate warnings or error messages. -* ``versionDict['tooOld']`` is a dict with module versions that are too old and are +* ``versionDict['tooOld']`` is a dict with module versions that are too old and are known to cause serious errors -* ``versionDict['tooOldWarn']`` is a dict with module versions that are +* ``versionDict['tooOldWarn']`` is a dict with module versions that are significantly out of date and should be updated, but will probably function OK. -* ``versionDict['badVersionWarn']`` is a dict of with lists of package - versions that are known to have bugs. One should select an older or - newer version of the package. -* ``versionDict['tooNewUntested']`` is a dict with module versions that have +* ``versionDict['badVersionWarn']`` is a dict of with lists of package + versions that are known to have bugs. One should select an older or + newer version of the package. +* ``versionDict['tooNewUntested']`` is a dict with module versions that have not been tested but there is no reason to expect problems -* ``versionDict['tooNewWarn']`` is a dict with module versions that have not - been tested but there are concerns that problems may occur. +* ``versionDict['tooNewWarn']`` is a dict with module versions that have not + been tested but there are concerns that problems may occur. **Packages/versions to be avoided** * Python: - * We are no longer supporting Python <=2.7 and <=3.6. Jan. 2023: We will soon start - removing code that is specific to Python 2.7. - * A problem has been noted with wx4.0.7.post2 with Python 3.10 that we can't + * We are no longer supporting Python <=2.7 and <=3.6. Jan. 2023: We will soon start + removing code that is specific to Python 2.7. + * A problem has been noted with wx4.0.7.post2 with Python 3.10 that we can't yet duplicate (2/4/22). * We anticipate that Python 3.10+ will flag code that previously worked fine, - because it reports errors where we pass a floating point number to a + because it reports errors where we pass a floating point number to a wxpython routine that expects a int value. We are fixing these as we learn about them. * wxPython: - * <=2.x.x: while most of GSAS-II has been written to be - compatible with older versions of wxpython, we are now testing with + * <=2.x.x: while most of GSAS-II has been written to be + compatible with older versions of wxpython, we are now testing with version 4.0+ only. - * wxpython 3.0 is pretty similar to 4.0, but we did see - some issues with Python 3.x. - * wxpython 4.1 has some serious internal bugs with Python 3.10+ so we recommend + * wxpython 3.0 is pretty similar to 4.0, but we did see + some issues with Python 3.x. + * wxpython 4.1 has some serious internal bugs with Python 3.10+ so we recommend 4.2+ for compatibility with newer Python versions. * 4.2.0 has a problem on MacOS where buttons w/default size are not displayed properly. - (see https://github.com/wxWidgets/Phoenix/issues/2319). Worked around (mostly?) in our code. + (see https://github.com/wxWidgets/Phoenix/issues/2319). Worked around (mostly?) in our code. * Matplotlib: - * 1.x: there have been significant API changes since these versions and - significant graphics errors will occur. + * 1.x: there have been significant API changes since these versions and + significant graphics errors will occur. * 3.1.x and 3.2.x: these versions have a known bug for plotting - 3-D surfaces, such as microstrain vs crystal axes. The plots may appear + 3-D surfaces, such as microstrain vs crystal axes. The plots may appear distorted as the lengths of x, y & z will not be constrained as equal. Preferably use 3.0.x as 3.3.x is not fully tested. * between 3.3.x vs 3.6.x there seems to be a change in how 3d graphics - are handled; we seem to have this fixed, but not sure how <3.3 will work. + are handled; we seem to have this fixed, but not sure how <3.3 will work. Since 3.1 & 3.2 have problems; warn w/mpl <3.3.0 -* numpy: +* numpy: - * 1.16.0: produces .gpx files that are not compatible with older + * 1.16.0: produces .gpx files that are not compatible with older version numpy versions. This is a pretty outmoded version; upgrade. ''' @@ -359,7 +360,7 @@ def compareVersions(version1,version2): 'matplotlib': ['3.1','3.2'], 'wx':['4.1']} 'versions of modules that are known to have bugs' -versionDict['tooNewWarn'] = {} +versionDict['tooNewWarn'] = {} 'module versions newer than what we have tested & where problems are suspected' versionDict['tooNewUntested'] = {'Python':'3.14','wx': '4.2.3'} 'module versions newer than what we have tested but no problems are suspected' @@ -372,7 +373,6 @@ def ShowVersions(): import wx import matplotlib as mpl import OpenGL as ogl - import GSASIIpath pkgList = [('Python',None), ('wx',wx), ('matplotlib', mpl), ('numpy',np), ('scipy',sp), ('OpenGL',ogl)] if GSASIIpath.GetConfigValue('debug'): @@ -435,6 +435,7 @@ def ShowVersions(): pass if Image is None: print ("Image module not present; Note that PIL (Python Imaging Library) or pillow is needed for some image operations") + G2fil.NeededPackage({'Saving plot images':['pillow']}) else: # version # can be in various places, try standard dunderscore first for ver in '__version__','VERSION','PILLOW_VERSION': @@ -451,34 +452,44 @@ def ShowVersions(): print (" Max threads:%s"%mkl.get_max_threads()) except: pass - print(GSASIIpath.getG2VersionInfo()) - + # warn if problems with Git - try: - import git - except ImportError as msg: - if 'Failed to initialize' in msg.msg: - print('The gitpython package is unable to locate a git installation.') - print('See https://gsas-ii.readthedocs.io/en/latest/packages.html for more information.') - elif 'No module' in msg.msg: - print('Warning: Python gitpython module not installed') - else: - print(f'gitpython failed to import, but why? Error:\n{msg}') - print('Note: git and gitpython are required for GSAS-II to self-update') - except Exception as msg: - print(f'git import failed with unexpected error:\n{msg}') - print('Note: git and gitpython are required for GSAS-II to self-update') + if GSASIIpath.HowIsG2Installed().startswith('git'): + try: + import git + git + except ImportError as msg: + if 'Failed to initialize' in msg.msg: + print('The gitpython package is unable to locate a git installation.') + print('See https://gsas-ii.readthedocs.io/en/latest/packages.html for more information.') + elif 'No module' in msg.msg: + print('Warning: Python gitpython module not installed') + else: + print(f'gitpython failed to import, but why? Error:\n{msg}') + print('Note: git and gitpython are required for GSAS-II to self-update') + except Exception as msg: + print(f'git import failed with unexpected error:\n{msg}') + print('Note: git and gitpython are required for GSAS-II to self-update') # warn if problems with requests package try: import requests + requests except: print('Warning: Python requests package not installed (required for\n'+ ' GSAS-II to access web pages or self-install binary modules)') + G2fil.NeededPackage({'Accessing web resources':['requests']}) + try: + import pybaselines.whittaker + except: + G2fil.NeededPackage({'Auto background capability':['pybaselines']}) - if not GSASIIpath.TestSPG(GSASIIpath.binaryPath): + if not GSASIIpath.TestSPG(): path2repo = os.path.dirname(GSASIIpath.path2GSAS2) installLoc = os.path.join(path2repo,'GSASII-bin') + # TODO: note new code in gitstrap.py that avoids use of getGitBinaryLoc + # when the desired binary exists. Better to do lookup with + # getGitBinaryLoc only when an exact match is not available binarydir = GSASIIpath.getGitBinaryLoc(verbose=True) if not binarydir: versionDict['errors'] += 'GSAS-II does not have a binaries built for this version of Python. You will need to install a supported Python version or build binaries yourself. See https://gsas-ii.readthedocs.io/en/latest/packages.html#python-requirements\n\n' @@ -491,35 +502,42 @@ def ShowVersions(): result = dlg.ShowModal() finally: dlg.Destroy() - if result != wx.ID_NO: - try: - GSASIIpath.InstallGitBinary(binarydir, installLoc, + if result != wx.ID_NO: + try: + GSASIIpath.InstallGitBinary(binarydir, installLoc, nameByVersion=True) - msg = f'Binaries installed from {binarydir} to {installLoc}\n' - print(msg) - GSASIIpath.BinaryPathFailed = False - GSASIIpath.SetBinaryPath(True) - except: - print('Download failed, sorry') - if not GSASIIpath.TestSPG(GSASIIpath.binaryPath): - versionDict['errors'] += 'Error accessing GSAS-II binary files. Only limited functionality available.' + msg = f'Binaries installed from {binarydir} to {installLoc}\n' + print(msg) + GSASIIpath.BinaryPathFailed = False + GSASIIpath.SetBinaryPath(True) + except: + print('Download failed, sorry') + if not GSASIIpath.pathhack_TestSPG(GSASIIpath.binaryPath): + versionDict['errors'] += 'Error accessing GSAS-II binary files. Only limited functionality available.' else: - prog = 'convcell' - if sys.platform.startswith('win'): prog += '.exe' - if not os.path.exists(os.path.join(GSASIIpath.binaryPath,prog)): - versionDict['errors'] += 'Installed binary files need an update. If you built them, rerun scons' + # TODO: use GSASIIversion.txt (in binary directory) to see + # if version is recent enough rather than use presence of convcell[.exe] + if GSASIIpath.binaryPath: + prog = os.path.join(GSASIIpath.binaryPath,"convcell") + else: + prog = 'convcell' + if sys.platform.startswith('win'): + prog += '.exe' + if not shutil.which(prog): + versionDict['errors'] += 'NIST*LATTICE binaries not found. You may have old binaries or an installation problem. If you built these binaries, rerun scons or meson' if warn: print(70*'=') - print('''You are running GSAS-II in a Python environment with either untested -or known to be problematic packages, as noted above. If you are seeing -problems in running GSAS-II you are suggested to install an additional -copy of GSAS-II from one of the gsas2full installers (see -https://bit.ly/G2install). This will provide a working Python -environment as well as the latest GSAS-II version. - -For information on GSAS-II package requirements see + print('''You are running GSAS-II in a Python environment with either untested +or known to be problematic packages, as noted above. If you are seeing +problems in running GSAS-II you are suggested to install an additional +copy of GSAS-II from one of the gsas2full installers (see +https://GSASII.github.io/). This will provide a working Python +environment as well as the latest GSAS-II version. + +For information on GSAS-II package requirements see https://gsas-ii.readthedocs.io/en/latest/packages.html''') print(70*'=','\n') + print(GSASIIpath.getG2VersionInfo()) def TestOldVersions(): '''Test the versions of required Python packages, etc. @@ -557,13 +575,13 @@ def TestOldVersions(): errmsg += prefix + '{} version {} causes problems with GSAS-II.'.format(s,pkgver) break return errmsg,warnmsg - + #### GUI creation ############################################################# def GSASIImain(application): - '''Start up the GSAS-II GUI''' + '''Start up the GSAS-II GUI''' ShowVersions() GUIpatches() - + if versionDict['errors']: msg = ( '\n\nGSAS-II will attempt to start, but this problem needs '+ @@ -578,9 +596,9 @@ def GSASIImain(application): dlg.Destroy() msg = '' #sys.exit() - elif platform.python_version_tuple()[0] == '2' and int(platform.python_version_tuple()[1]) < 7: + elif platform.python_version_tuple()[0] == '2' and int(platform.python_version_tuple()[1]) < 7: msg = 'GSAS-II works best with Python version 3.7 or later.\nThis version is way too old: '+sys.version.split()[0] - elif platform.python_version_tuple()[0] == '3' and int(platform.python_version_tuple()[1]) < 6: + elif platform.python_version_tuple()[0] == '3' and int(platform.python_version_tuple()[1]) < 6: msg = 'GSAS-II works best with Python version 3.7 or later.\nThis version is too old: '+sys.version.split()[0] else: msg = '' @@ -591,15 +609,11 @@ def GSASIImain(application): finally: dlg.Destroy() sys.exit() - + application.main = GSASII(None) # application.main is the main wx.Frame (G2frame in most places) application.SetTopWindow(application.main) # save the current package versions application.main.PackageVersions = G2fil.get_python_versions([wx, mpl, np, sp, ogl]) - if GSASIIpath.GetConfigValue('wxInspector'): - import wx.lib.inspection as wxeye - wxeye.InspectionTool().Show() - try: application.SetAppDisplayName('GSAS-II') except: @@ -614,18 +628,18 @@ class GSASII(wx.Frame): :param parent: reference to parent application - ''' + ''' def _Add_FileMenuItems(self, parent): '''Add items to File menu ''' - item = parent.Append(wx.ID_ANY,'&Open project...\tCtrl+O','Open a GSAS-II project (.gpx) file') + item = parent.Append(wx.ID_ANY,'&Open project...\tCtrl+O','Open a GSAS-II project (.gpx) file') self.Bind(wx.EVT_MENU, self.OnFileOpen, id=item.GetId()) - # if sys.platform == "darwin": + # if sys.platform == "darwin": item = parent.Append(wx.ID_ANY,'&Open in new window...','Open a GSAS-II project (.gpx) file in a separate process') self.Bind(wx.EVT_MENU, self.OnNewGSASII, id=item.GetId()) item = parent.Append(wx.ID_ANY,'Reopen recent...\tCtrl+E','Reopen a previously used GSAS-II project (.gpx) file') self.Bind(wx.EVT_MENU, self.OnFileReopen, id=item.GetId()) - item = parent.Append(wx.ID_ANY,'&Open w/project browser\tCtrl+B','Use project browser to a GSAS-II project (.gpx) file') + item = parent.Append(wx.ID_ANY,'&Open w/project browser\tCtrl+B','Use project browser to a GSAS-II project (.gpx) file') self.Bind(wx.EVT_MENU, self.OnFileBrowse, id=item.GetId()) item = parent.Append(wx.ID_ANY,'&Save project\tCtrl+S','Save project under current name') self.Bind(wx.EVT_MENU, self.OnFileSave, id=item.GetId()) @@ -636,8 +650,9 @@ def _Add_FileMenuItems(self, parent): item = parent.Append(wx.ID_PREFERENCES,"&Preferences",'') self.Bind(wx.EVT_MENU, self.OnPreferences, item) if GSASIIpath.GetConfigValue('debug'): - try: + try: import IPython + IPython item = parent.Append(wx.ID_ANY,"IPython Console",'') self.Bind(wx.EVT_MENU, lambda event:GSASIIpath.IPyBreak(), @@ -651,15 +666,17 @@ def OnwxInspect(event): self.Bind(wx.EVT_MENU, OnwxInspect, item) item = parent.Append(wx.ID_ANY,'Reopen current\tCtrl+0','Reread the current GSAS-II project (.gpx) file') self.Bind(wx.EVT_MENU, self.OnFileReread, id=item.GetId()) - - item = parent.Append(wx.ID_ANY,"Install GSASIIscriptable shortcut",'') - self.Bind(wx.EVT_MENU, + + # if Git install assume GSAS-II was not installed into Python + if GSASIIpath.HowIsG2Installed().startswith('git'): + item = parent.Append(wx.ID_ANY,"Install GSASIIscriptable shortcut",'') + self.Bind(wx.EVT_MENU, lambda event: GSASIIpath.makeScriptShortcut(), item) item = parent.Append(wx.ID_EXIT,'Exit\tALT+F4','Exit from GSAS-II') self.Bind(wx.EVT_MENU, self.ExitMain, id=item.GetId()) - + def _Add_DataMenuItems(self,parent): '''Add items to Data menu ''' @@ -684,7 +701,7 @@ def _Add_DataMenuItems(self,parent): item = parent.Append(wx.ID_ANY,'Delete sequential result entries','') self.Bind(wx.EVT_MENU, self.OnDeleteSequential, id=item.GetId()) expandmenu = wx.Menu() - item = parent.AppendSubMenu(expandmenu,'Expand tree items', + item = parent.AppendSubMenu(expandmenu,'Expand tree items', 'Expand items of type in GSAS-II data tree') for s in 'all','IMG','PWDR','PDF','HKLF','SASD','REFD': if s == 'all': @@ -694,7 +711,7 @@ def _Add_DataMenuItems(self,parent): item = expandmenu.Append(wx.ID_ANY,s,help) self.Bind(wx.EVT_MENU,self.ExpandAll,id=item.GetId()) movemenu = wx.Menu() - item = parent.AppendSubMenu(movemenu,'Move tree items', + item = parent.AppendSubMenu(movemenu,'Move tree items', 'Move items of type items to end of GSAS-II data tree') for s in 'IMG','PWDR','PDF','HKLF','SASD','REFD','Phase': help = 'Move '+s+' type items to end of GSAS-II data tree' @@ -707,11 +724,11 @@ def _Add_CalculateMenuItems(self,parent): item = parent.Append(wx.ID_ANY,'Setup PDFs','Create PDF tree entries for selected powder patterns') self.MakePDF.append(item) self.Bind(wx.EVT_MENU, self.OnMakePDFs, id=item.GetId()) - + item = parent.Append(wx.ID_ANY,'&View LS parms\tCTRL+L','View least squares parameters') self.Bind(wx.EVT_MENU, self.OnShowLSParms, id=item.GetId()) - - item = parent.Append(wx.ID_ANY,'&Refine\tCTRL+R','Perform a refinement') + + item = parent.Append(wx.ID_ANY,'&Refine\tCTRL+R','Perform a refinement') if len(self.Refine): # extend state for new menus to match main state = self.Refine[0].IsEnabled() else: @@ -727,10 +744,10 @@ def _Add_CalculateMenuItems(self,parent): self.Bind(wx.EVT_MENU, self.OnDerivCalc, id=item.GetId()) item = parent.Append(wx.ID_ANY,'Evaluate expression and s.u.','Perform uncertainty analysis on an expression of GSAS-II parameters') self.Bind(wx.EVT_MENU, self.OnExpressionCalc, id=item.GetId()) - + item = parent.Append(wx.ID_ANY,'Setup Cluster Analysis','Setup Cluster Analysis') - self.Bind(wx.EVT_MENU, self.OnClusterAnalysis, id=item.GetId()) - + self.Bind(wx.EVT_MENU, self.OnClusterAnalysis, id=item.GetId()) + item = parent.Append(wx.ID_ANY,'&Run Fprime','X-ray resonant scattering') self.Bind(wx.EVT_MENU, self.OnRunFprime, id=item.GetId()) item = parent.Append(wx.ID_ANY,'&Run Absorb','x-ray absorption') @@ -744,7 +761,7 @@ def _Add_CalculateMenuItems(self,parent): # self.Bind(wx.EVT_MENU, self.TreeTest, id=item.GetId()) def _init_Imports(self): - '''import all the G2phase*.py & G2sfact*.py & G2pwd*.py files that + '''import all the G2phase*.py & G2sfact*.py & G2pwd*.py files that are found in the path ''' @@ -772,7 +789,7 @@ def testSeqRefineMode(self): seqSetting = controls.get('Seq Data',[]) else: seqSetting = None - + for item in self.Refine: if 'Le Bail' in item.GetItemLabel() or 'partials' in item.GetItemLabel() : item.Enable(not seqSetting) @@ -789,7 +806,7 @@ def testSeqRefineMode(self): for menu,Id in self.ExportNonSeq: menu.Enable(Id,not seqMode) return seqSetting - + def PreviewFile(self,filename): 'utility to confirm we have the right file' fp = open(filename,'r') @@ -813,7 +830,7 @@ def PreviewFile(self,filename): dlg.Destroy() if result == wx.ID_NO: return True return False - + def OnImportGeneric(self,reader,readerlist,label,multiple=False, usedRanIdList=[],Preview=True,load2Tree=False): '''Used for all imports, including Phases, datasets, images... @@ -852,7 +869,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, in the file dialog. False is default. At present True is used only for reading of powder data. - :param list usedRanIdList: an optional list of random Ids that + :param list usedRanIdList: an optional list of random Ids that have been used and should not be reused :param bool Preview: indicates if a preview of the file should @@ -866,7 +883,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, reader objects. :returns: a list of reader objects (rd_list) that were able - to read the specified file(s). This list may be empty. + to read the specified file(s). This list may be empty. ''' self.lastimport = '' self.zipfile = None @@ -908,7 +925,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, mode = wx.FD_OPEN|wx.FD_MULTIPLE else: mode = wx.FD_OPEN - if len(readerlist) > 1: + if len(readerlist) > 1: typ = ' (type to be guessed)' else: typ = ' (type '+readerlist[0].formatName+')' @@ -923,7 +940,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, extractedfiles = G2G.ExtractFileFromZip( filename,parent=self,msg=f'Reading {label} file(s)\n\n', multipleselect=True) - if extractedfiles is None: continue # error or Cancel + if extractedfiles is None: continue # error or Cancel if extractedfiles != filename: self.zipfile = filename # save zip name filelist1 += extractedfiles @@ -941,7 +958,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, # if os.path.splitext(filename)[1].lower() == '.zip': extractedfile = G2G.ExtractFileFromZip(filename,parent=self, msg=f'Reading a {label} file\n\n') - if extractedfile is None: continue # error or Cancel + if extractedfile is None: continue # error or Cancel if extractedfile != filename: filename,self.zipfile = extractedfile,filename # now use the file that was created print(f"Created temporary file {extractedfile}") @@ -951,7 +968,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, secondaryReaders = [] for rd in readerlist: flag = rd.ExtensionValidator(filename) - if flag is None: + if flag is None: secondaryReaders.append(rd) elif flag: primaryReaders.append(rd) @@ -982,7 +999,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, rd.errors = "" # clear out any old errors if not rd.ContentsValidator(filename): # rejected on cursory check errorReport += "\n "+rd.formatName + ' validator error' - if rd.errors: + if rd.errors: errorReport += ': '+rd.errors continue if len(rd.selections)>1 and Start: @@ -1034,7 +1051,7 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, G2IO.LoadImage2Tree(rd.readfilename,self,rd.Comments,rd.Data,rd.Npix,rd.Image) rd_list.append(True) # save a stub the result before it is written over del rd.Image - else: + else: rd_list.append(copy.deepcopy(rd)) # save the result before it is written over if rd.repeat: repeat = True @@ -1042,10 +1059,13 @@ def OnImportGeneric(self,reader,readerlist,label,multiple=False, errorReport += '\n'+rd.formatName + ' read error' if rd.errors: errorReport += ': '+rd.errors - if rd_list: # read succeeded, was there a warning or any errors? + if rd_list: # read succeeded, was there a warning or any errors? if rd.warnings: - self.ErrorDialog('Read Warning','The '+ rd.formatName+ - ' reader reported a warning message:\n\n'+rd.warnings) +# self.ErrorDialog('Read Warning','The '+ rd.formatName+ +# ' reader reported warning message(s):\n\n'+rd.warnings) + msg = f'The {rd.formatName} reader reported warning message(s):\n\n{rd.warnings}' + print(msg) + G2G.ShowScrolledInfo(self,msg,header='Read Warning') break # success in reading, try no further else: if singlereader: @@ -1079,7 +1099,7 @@ def _Add_ImportMenu_Phase(self,parent): self.Bind(wx.EVT_MENU, self.OnImportPhase, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import phase data, use file to try to determine format') self.Bind(wx.EVT_MENU, self.OnImportPhase, id=item.GetId()) - + def OnImportPhase(self,event): '''Called in response to an Import/Phase/... menu item to read phase information. @@ -1090,7 +1110,7 @@ def OnImportPhase(self,event): ''' # look up which format was requested reqrdr = self.ImportMenuId.get(event.GetId()) - + # make a list of phase names, ranId's and the histograms used in those phases phaseRIdList,usedHistograms = self.GetPhaseInfofromTree() phaseNameList = list(usedHistograms.keys()) # phase names in use @@ -1099,13 +1119,13 @@ def OnImportPhase(self,event): for h in usedHistograms[p]: if h.startswith('HKLF ') and h not in usedHKLFhists: usedHKLFhists.append(h) - + rdlist = self.OnImportGeneric(reqrdr,self.ImportPhaseReaderlist, 'phase',usedRanIdList=phaseRIdList) if len(rdlist) == 0: return # for now rdlist is only expected to have one element # but below will allow multiple phases to be imported - # if ever the import routines ever implement multiple phase reads. + # if ever the import routines ever implement multiple phase reads. self.CheckNotebook() newPhaseList = [] for rd in rdlist: @@ -1135,7 +1155,7 @@ def OnImportPhase(self,event): rd.Phase = choice elif not OK: # This user is in trouble - msg = '''Based on symmetry operations supplied in the input, it appears this structure uses a space group setting not compatible with GSAS-II. + msg = '''Based on symmetry operations supplied in the input, it appears this structure uses a space group setting not compatible with GSAS-II. If you continue from this point, it is quite likely that all intensity computations will be wrong. At a minimum view the structure and check site multiplicities to confirm they are correct. The Bilbao web site may help you convert this.''' wx.MessageBox(msg,caption='Symmetry problem likely',style=wx.ICON_EXCLAMATION) @@ -1163,16 +1183,16 @@ def OnImportPhase(self,event): except (AttributeError,TypeError): pass self.GPXtree.Expand(self.root) # make sure phases are seen - self.GPXtree.Expand(sub) + self.GPXtree.Expand(sub) self.GPXtree.Expand(psub) self.PickIdText = None - + # add constraints imported with phase to tree # at present, constraints are generated only in ISODISTORT_proc in the # CIF import if rd.Constraints: sub = GetGPXtreeItemId(self,self.root,'Constraints') # was created in CheckNotebook if needed - Constraints = self.GPXtree.GetItemPyData(sub) + Constraints = self.GPXtree.GetItemPyData(sub) for i in rd.Constraints: if type(i) is dict: if '_Explain' not in Constraints: Constraints['_Explain'] = {} @@ -1203,7 +1223,7 @@ def OnImportPhase(self,event): notOK = True while notOK: result = G2G.ItemSelector(TextList,self,header,header='Add histogram(s)',multiple=True) - if not result: + if not result: Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree() # reindex return # check that selected single crystal histograms are not already in use! @@ -1252,9 +1272,9 @@ def OnImportPhase(self,event): Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree() # reindex wx.EndBusyCursor() self.EnableRefineCommand() - + return # success - + def _Add_ImportMenu_Image(self,parent): '''configure the Import Image menus accord to the readers found in _init_Imports ''' @@ -1266,7 +1286,7 @@ def _Add_ImportMenu_Image(self,parent): self.Bind(wx.EVT_MENU, self.OnImportImage, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import image data, use file to try to determine format') self.Bind(wx.EVT_MENU, self.OnImportImage, id=item.GetId()) - + def OnImportImage(self,event): '''Called in response to an Import/Image/... menu item to read an image from a file. Like all the other imports, @@ -1275,23 +1295,23 @@ def OnImportImage(self,event): None for the last menu item, which is the "guess" option where all appropriate formats will be tried. - A reader object is filled each time an image is read. + A reader object is filled each time an image is read. ''' self.CheckNotebook() # look up which format was requested reqrdr = self.ImportMenuId.get(event.GetId()) rdlist = self.OnImportGeneric(reqrdr,self.ImportImageReaderlist, 'image',multiple=True,Preview=False,load2Tree=True) - if rdlist: + if rdlist: self.GPXtree.SelectItem(GetGPXtreeItemId(self,self.Image,'Image Controls')) #show last image to have beeen read - + def _Add_ImportMenu_Sfact(self,parent): '''configure the Import Structure Factor menus accord to the readers found in _init_Imports ''' submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Structure Factor','Import Structure Factor data') for reader in self.ImportSfactReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportSfact, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import Structure Factor, use file to try to determine format') @@ -1299,7 +1319,7 @@ def _Add_ImportMenu_Sfact(self,parent): def OnImportSfact(self,event): '''Called in response to an Import/Structure Factor/... menu item - to read single crystal datasets. + to read single crystal datasets. dict self.ImportMenuId is used to look up the specific reader item associated with the menu item, which will be None for the last menu item, which is the "guess" option @@ -1323,7 +1343,7 @@ def OnImportSfact(self,event): newHistList = [] for rd in rdlist: HistName = rd.objname - if len(rdlist) <= 2: + if len(rdlist) <= 2: dlg = wx.TextEntryDialog( # allow editing of Structure Factor name self, 'Enter the name for the new Structure Factor', 'Edit Structure Factor name', HistName, @@ -1361,7 +1381,7 @@ def OnImportSfact(self,event): self.GPXtree.SetItemPyData( self.GPXtree.AppendItem(Id,text='Reflection List'),{}) #dummy entry for GUI use newHistList.append(HistName) - + self.GPXtree.SelectItem(Id) self.GPXtree.Expand(Id) self.Sngl = True @@ -1403,7 +1423,7 @@ def _Add_ImportMenu_powder(self,parent): ''' def OnAutoImport(event): G2G.AutoLoadFiles(self,FileTyp='pwd') - + submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Powder Data','Import Powder data') for reader in self.ImportPowderReaderlist: @@ -1417,10 +1437,10 @@ def OnAutoImport(event): self.Bind(wx.EVT_MENU, self.OnDummyPowder, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'Auto Import','Import data files as found') self.Bind(wx.EVT_MENU, OnAutoImport, id=item.GetId()) - + item = submenu.Append(wx.ID_ANY,'Fit instr. profile from fundamental parms...','') self.Bind(wx.EVT_MENU, self.OnPowderFPA, id=item.GetId()) - + def OpenPowderInstprm(self,instfile): '''Read a GSAS-II (new) instrument parameter file @@ -1430,27 +1450,27 @@ def OpenPowderInstprm(self,instfile): File = open(instfile,'r') lines = File.readlines() File.close() - return lines - + return lines + def ReadPowderInstprm(self, instLines, bank, rd): '''Read contents of a GSAS-II (new) .instprm instrument parameter file similar to G2pwdGUI.OnLoad. Uses :func:`GSASIIfiles.ReadInstprm` - to actually read the file. + to actually read the file. If instprm file has multiple banks (where each has header #Bank n: ..., - and bank is supplied as None here, this routine (in GUI) uses a - dialog for selection. Note that multibank .instprm files are made by - a "Save all profile" command in Instrument Parameters. + and bank is supplied as None here, this routine (in GUI) uses a + dialog for selection. Note that multibank .instprm files are made by + a "Save all profile" command in Instrument Parameters. - :param list instLines: contents of GSAS-II parameter file as a + :param list instLines: contents of GSAS-II parameter file as a list of str; N.B. lines can be concatenated with ';' - :param int bank: bank number to use when instprm file has values - for multiple banks (noted by headers of '#BANK n:...'.). This - is ignored for instprm files without those headers. - If bank is None with multiple banks, a selection window is shown. - :param GSASIIobj.ImportPowder rd: The reader object that - will be read from. Sample parameters are placed here. + :param int bank: bank number to use when instprm file has values + for multiple banks (noted by headers of '#BANK n:...'.). This + is ignored for instprm files without those headers. + If bank is None with multiple banks, a selection window is shown. + :param GSASIIobj.ImportPowder rd: The reader object that + will be read from. Sample parameters are placed here. :returns: Either an instrument parameter dict if OK, or - an Error message (str) if read failed + an Error message (str) if read failed ''' if 'GSAS-II' not in instLines[0]: # not a valid file return 'Not a valid GSAS-II instprm file' @@ -1476,7 +1496,7 @@ def ReadPowderIparm(self,instfile,bank,databanks,rd): :param int databanks: the number of banks in the raw data file. If the number of banks in the data and instrument parameter files agree, then the sets of banks are assumed to match up and bank - is used to select the instrument parameter file. If not and not TOF, + is used to select the instrument parameter file. If not and not TOF, the user is asked to make a selection. :param obj rd: the raw data (histogram) data object. This sets rd.instbank. @@ -1494,7 +1514,7 @@ def ReadPowderIparm(self,instfile,bank,databanks,rd): Iparm[S[:12]] = S[12:-1] except IOError: print(u'Error reading file: {}'.format(instfile)) - if fp: + if fp: fp.close() ibanks = int(Iparm.get('INS BANK ','1').strip()) @@ -1525,7 +1545,7 @@ def ReadPowderIparm(self,instfile,bank,databanks,rd): IparmS[key[:4]+' 1'+key[6:]] = Iparm[key] rd.instbank = bank return IparmS - + def GetPowderIparm(self,rd, prevIparm, lastIparmfile, lastdatafile): '''Open and read an instrument parameter file for a data file Returns the list of parameters used in the data tree @@ -1535,26 +1555,26 @@ def GetPowderIparm(self,rd, prevIparm, lastIparmfile, lastdatafile): :param str prevIparm: not used :param str lastIparmfile: Name of last instrument parameter - file that was read, or a empty string. + file that was read, or a empty string. :param str lastdatafile: Name of last data file that was read. :returns: a list of two dicts, the first containing instrument parameters and the second used for TOF lookup tables for profile coeff. ''' - + def GetDefaultParms(self,rd): '''Solicits from user a default set of parameters & returns Inst parm dict param: self: refers to the GSASII main class param: rd: importer data structure returns: dict: Instrument parameter dictionary - ''' + ''' sind = lambda x: math.sin(x*math.pi/180.) tand = lambda x: math.tan(x*math.pi/180.) while True: # loop until we get a choice choices = [] head = 'Select from default instrument parameters for '+rd.idstring - + for l in dI.defaultIparm_lbl: choices.append('Defaults for '+l) res = G2G.BlockSelector(choices,ParentFrame=self,title=head, @@ -1598,42 +1618,43 @@ def GetDefaultParms(self,rd): #1st priority: is there an instrument parameter file matching the current file # with extension .instprm, .prm, .inst, or .ins? If so read it basename = os.path.splitext(filename)[0] - #-- look for an instrument file matching the name of the data file ------------- - print('looking for default instrument parameter file named\n\t', - os.path.split(basename)[1], - 'with extensions .prm, .inst, .ins or .instprm') - for ext in '.prm','.inst','.ins','.instprm': - if self.zipfile: - instfile = G2G.ExtractFileFromZip(self.zipfile, - selection=os.path.split(basename + ext)[1],parent=self) - if instfile == None: - continue - print(f'created {instfile} from {self.zipfile}') - self.cleanupList.append(instfile) - else: - instfile = basename + ext - if not os.path.exists(instfile): - continue - if 'instprm' in instfile: - Lines = self.OpenPowderInstprm(instfile) - instParmList = self.ReadPowderInstprm(Lines,bank,rd) #this is [Inst1,Inst2] a pair of dicts - if 'list' in str(type(instParmList)): - rd.instfile = instfile - rd.instmsg = 'GSAS-II file '+instfile - return instParmList + if basename: + #-- look for an instrument file matching the name of the data file ------------- + print('looking for default instrument parameter file named\n\t', + os.path.split(basename)[1], + 'with extensions .prm, .inst, .ins or .instprm') + for ext in '.prm','.inst','.ins','.instprm': + if self.zipfile: + instfile = G2G.ExtractFileFromZip(self.zipfile, + selection=os.path.split(basename + ext)[1],parent=self) + if instfile == None: + continue + print(f'created {instfile} from {self.zipfile}') + self.cleanupList.append(instfile) else: - #print 'debug: open/read failed',instfile - pass # fail silently - else: - Iparm = self.ReadPowderIparm(instfile,bank,numbanks,rd) - if Iparm: - #print 'debug: success' - rd.instfile = instfile - rd.instmsg = instfile + ' bank ' + str(rd.instbank) - return G2fil.SetPowderInstParms(Iparm,rd) + instfile = basename + ext + if not os.path.exists(instfile): + continue + if 'instprm' in instfile: + Lines = self.OpenPowderInstprm(instfile) + instParmList = self.ReadPowderInstprm(Lines,bank,rd) #this is [Inst1,Inst2] a pair of dicts + if 'list' in str(type(instParmList)): + rd.instfile = instfile + rd.instmsg = 'GSAS-II file '+instfile + return instParmList + else: + #print 'debug: open/read failed',instfile + pass # fail silently else: - #print 'debug: open/read failed',instfile - pass # fail silently + Iparm = self.ReadPowderIparm(instfile,bank,numbanks,rd) + if Iparm: + #print 'debug: success' + rd.instfile = instfile + rd.instmsg = instfile + ' bank ' + str(rd.instbank) + return G2fil.SetPowderInstParms(Iparm,rd) + else: + #print 'debug: open/read failed',instfile + pass # fail silently #-- look for an instrument file matching the name of the data file ------------- # 2nd choice: is there an instrument parameter file defined in the @@ -1685,7 +1706,7 @@ def GetDefaultParms(self,rd): extOrd = [0,1] if GSASIIpath.GetConfigValue('Instprm_default',False): extOrd = [1,0] - extList = ['GSAS iparm file (*.prm,*.inst,*.ins)|*.prm;*.inst;*.ins|','GSAS-II iparm file (*.instprm)|*.instprm|'] + extList = ['GSAS iparm file (*.prm,*.inst,*.ins)|*.prm;*.inst;*.ins;*.PRM|','GSAS-II iparm file (*.instprm)|*.instprm|'] dlg = wx.FileDialog(self, u'Choose inst. param file for "'+rd.idstring+u'" (or Cancel for default)', pth, '',extList[extOrd[0]]+extList[extOrd[1]]+'All files (*.*)|*.*', wx.FD_OPEN) @@ -1694,7 +1715,7 @@ def GetDefaultParms(self,rd): if dlg.ShowModal() == wx.ID_OK: instfile = dlg.GetPath() dlg.Destroy() - if not instfile: + if not instfile: return GetDefaultParms(self,rd) #on Cancel/break if 'instprm' in instfile: Lines = self.OpenPowderInstprm(instfile) @@ -1719,14 +1740,14 @@ def GetDefaultParms(self,rd): u'Error opening/reading file {}'.format(instfile)) def EnableRefineCommand(self): - '''Check that phases are connected to histograms - if so then + '''Check that phases are connected to histograms - if so then Data/Remove Histogram is enabled ''' if callable(self.dataWindow.DataGeneral): # will fail w/o Phase menus self.dataWindow.DataGeneral() haveData = False sub = GetGPXtreeItemId(self,self.root,'Phases') - if sub: + if sub: item, cookie = self.GPXtree.GetFirstChild(sub) while item: # loop over phases data = self.GPXtree.GetItemPyData(item) @@ -1741,10 +1762,10 @@ def EnableRefineCommand(self): else: self.dataWindow.DataMenu.Enable(G2G.wxID_DATADELETE,False) for item in self.Refine: item.Enable(False) - + def OnImportPowder(self,event): '''Called in response to an Import/Powder Data/... menu item - to read a powder diffraction data set. + to read a powder diffraction data set. dict self.ImportMenuId is used to look up the specific reader item associated with the menu item, which will be None for the last menu item, which is the "guess" option @@ -1762,7 +1783,7 @@ def OnImportPowder(self,event): PWDRlist.append(name) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) # look up which format was requested - reqrdr = self.ImportMenuId.get(event.GetId()) + reqrdr = self.ImportMenuId.get(event.GetId()) rdlist = self.OnImportGeneric( reqrdr,self.ImportPowderReaderlist,'Powder Data',multiple=True) if len(rdlist) == 0: return @@ -1790,13 +1811,13 @@ def OnImportPowder(self,event): Id = 0 continue Iparm1,Iparm2 = Iparms - if rd.repeat_instparm: + if rd.repeat_instparm: lastIparmfile = rd.instfile else: Iparms = {} # lastVals = (rd.powderdata[0].min(),rd.powderdata[0].max(),len(rd.powderdata[0])) # override any keys in read instrument parameters with ones set in import - for key in Iparm1: + for key in Iparm1: if key in rd.instdict: Iparm1[key] = rd.instdict[key] lastdatafile = rd.powderentry[0] @@ -1825,12 +1846,12 @@ def OnImportPowder(self,event): # make new histogram names unique HistName = G2obj.MakeUniqueLabel(HistName,PWDRlist) try: - print('Read powder data '+HistName+ + print('Read powder data '+HistName+ ' from file '+G2obj.StripUnicode(rd.readfilename) + - ' (format: '+ rd.formatName + + ' (format: '+ rd.formatName + '). Inst parameters from '+G2obj.StripUnicode(rd.instmsg)) except: - print('Read powder data') + print('Read powder data') # data are read, now store them in the tree Id = self.GPXtree.AppendItem(parent=self.root,text=HistName) if 'T' in Iparm1['Type'][0]: @@ -1890,7 +1911,7 @@ def OnImportPowder(self,event): Tmin = min(rd.powderdata[0]) Tmax = max(rd.powderdata[0]) Tmin1 = Tmin - if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4: + if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4: Tmin1 = G2lat.Dsp2pos(Iparm1,0.4) if 'PXE' in Iparm1['Type'][0]: Iparm1.update(rd.Inst) @@ -1973,7 +1994,7 @@ def OnImportPowder(self,event): def OnDummyPowder(self,event): '''Called in response to Import/Powder Data/Simulate menu item - to create a Dummy powder diffraction data set. + to create a Dummy powder diffraction data set. Reads an instrument parameter file and then gets input from the user ''' @@ -2006,7 +2027,7 @@ def OnDummyPowder(self,event): return Iparm1, Iparm2 = Iparm if 'T' in Iparm1['Type'][0]: - rd.idstring = ' TOF neutron simulation' + rd.idstring = 'TOF neutron simulation' simType = 'TOF' else: # need to get name, 2theta start, end, step @@ -2027,42 +2048,43 @@ def OnDummyPowder(self,event): N = 0 while (N < 3): # insist on a dataset with a few points if 'TOF' in rd.idstring: - names = ('dataset name', 'start TOF(ms)', 'end TOF(ms)', 'DT/T') + names = ('dataset name', 'T start (ms)', 'T end (ms)', 'DT/T') inp = [rd.idstring, 10.,80.,0.0005] # see names for what's what - dlg = G2G.ScrolledMultiEditor( - self,[inp] * len(inp),range(len(inp)),names, - header='Enter simulation name and range', - minvals=(None,.5,1.0,0.0001), - maxvals=(None,500.,500.,.01), - sizevals=((225,-1),) - ) + minvals = (None,.5,1.0,0.0001) + maxvals = (None,500.,500.,.01) else: names = ('dataset name', 'start angle', 'end angle', 'step size') if not wave or wave < 1.0: inp = [rd.idstring, 10.,40.,0.005] # see names for what's what else: inp = [rd.idstring, 10.,80.,0.01] # see names for what's what - dlg = G2G.ScrolledMultiEditor( - self,[inp] * len(inp),range(len(inp)),names, - header='Enter simulation name and range', - minvals=(None,0.001,0.001,0.0001), - maxvals=(None,180.,180.,.1), - sizevals=((225,-1),) - ) + minvals=(None,0.001,0.001,0.0001), + maxvals=(None,180.,180.,.1), + dlg = G2G.ScrolledMultiEditor( + self,[inp] * len(inp),range(len(inp)),names, + header='Enter ramnge for simulation and histogram name', + minvals=minvals, + maxvals=maxvals, + sizevals=((250,-1),None,None,None), + size=(400,150)) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: if inp[1] > inp[2]: end,start,step = inp[1:] - else: + else: start,end,step = inp[1:] step = abs(step) else: return False + # TODO: compute if the range and see if the widths are all + # positive here. If OK continue, otherwise warn and reject the + # limits (as per issue #170) if 'TOF' in rd.idstring: N = (np.log(end)-np.log(start))/step x = np.exp((np.arange(0,N))*step+np.log(start*1000.)) N = len(x) - else: + rd.Sample['Scale'][0] = 5000. # default (1) is way too low for "counts" + else: N = int((end-start)/step)+1 x = np.linspace(start,end,N,True) N = len(x) @@ -2255,7 +2277,7 @@ def AddSimulatedPowder(self,ttArr,intArr,HistName,Lam1,Lam2): self.GPXtree.SelectItem(Id) print(u'Added simulation powder data {}'.format(HistName)) return Id - + def OnPreferences(self,event): 'Edit the GSAS-II configuration variables' dlg = G2G.SelectConfigSetting(self) @@ -2309,22 +2331,22 @@ def _Add_ImportMenu_smallangle(self,parent): def OnImportSmallAngle(self,event): '''Called in response to an Import/Small Angle Data/... menu item - to read a small angle diffraction data set. + to read a small angle diffraction data set. dict self.ImportMenuId is used to look up the specific reader item associated with the menu item, which will be None for the last menu item, which is the "guess" option where all appropriate formats will be tried. Small angle data is presumed to be as QIE form for either x-rays or neutrons ''' - + def GetSASDIparm(reader): ''' Setup instrument parameters for small ang scattering data ''' parm = reader.instdict Iparm = {'Type':[parm['type'],parm['type'],0],'Lam':[parm['wave'], - parm['wave'],0],'Azimuth':[0.,0.,0]} + parm['wave'],0],'Azimuth':[0.,0.,0]} return Iparm,{} - + # get a list of existing histograms SASDlist = [] if self.GPXtree.GetCount(): @@ -2335,7 +2357,7 @@ def GetSASDIparm(reader): SASDlist.append(name) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) # look up which format was requested - reqrdr = self.ImportMenuId.get(event.GetId()) + reqrdr = self.ImportMenuId.get(event.GetId()) rdlist = self.OnImportGeneric( reqrdr,self.ImportSmallAngleReaderlist,'Small Angle Data',multiple=True) if len(rdlist) == 0: return @@ -2382,10 +2404,10 @@ def GetSASDIparm(reader): self.EnablePlot = True self.GPXtree.Expand(Id) self.GPXtree.SelectItem(Id) - + if not newHistList: return # somehow, no new histograms return # success - + def _Add_ImportMenu_reflectometry(self,parent): '''configure the reflectometry Data menus accord to the readers found in _init_Imports ''' @@ -2399,25 +2421,25 @@ def _Add_ImportMenu_reflectometry(self,parent): # help='Import reflectometry data, use file to try to determine format', # kind=wx.ITEM_NORMAL,text='guess format from file') # self.Bind(wx.EVT_MENU, self.OnImportReflectometry, id=item.GetId()) - + def OnImportReflectometry(self,event): '''Called in response to an Import/Reflectometry Data/... menu item - to read a reflectometry data set. + to read a reflectometry data set. dict self.ImportMenuId is used to look up the specific reader item associated with the menu item, which will be None for the last menu item, which is the "guess" option where all appropriate formats will be tried. Reflectometry data is presumed to be in QIE form for x-rays of neutrons ''' - + def GetREFDIparm(reader): ''' Setup reflectometry data instrument parameters ''' parm = reader.instdict Iparm = {'Type':[parm['type'],parm['type'],0],'Lam':[parm['wave'], - parm['wave'],0],'Azimuth':[0.,0.,0]} + parm['wave'],0],'Azimuth':[0.,0.,0]} return Iparm,{} - + # get a list of existing histograms REFDlist = [] if self.GPXtree.GetCount(): @@ -2428,7 +2450,7 @@ def GetREFDIparm(reader): REFDlist.append(name) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) # look up which format was requested - reqrdr = self.ImportMenuId.get(event.GetId()) + reqrdr = self.ImportMenuId.get(event.GetId()) rdlist = self.OnImportGeneric( reqrdr,self.ImportReflectometryReaderlist,'Reflectometry Data',multiple=True) if len(rdlist) == 0: return @@ -2477,7 +2499,7 @@ def GetREFDIparm(reader): self.EnablePlot = True self.GPXtree.Expand(Id) self.GPXtree.SelectItem(Id) - + if not newHistList: return # somehow, no new histograms return # success @@ -2499,10 +2521,10 @@ def OnAutoImport(event): # help='Import reflectometry data, use file to try to determine format', # kind=wx.ITEM_NORMAL,text='guess format from file') # self.Bind(wx.EVT_MENU, self.OnImportReflectometry, id=item.GetId()) - + def OnImportPDF(self,event): '''Called in response to an Import/PDF G(R) Data/... menu item - to read a PDF G(R) data set. + to read a PDF G(R) data set. dict self.ImportMenuId is used to look up the specific reader item associated with the menu item, which will be None for the last menu item, which is the "guess" option @@ -2518,7 +2540,7 @@ def OnImportPDF(self,event): PDFlist.append(name) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) # look up which format was requested - reqrdr = self.ImportMenuId.get(event.GetId()) + reqrdr = self.ImportMenuId.get(event.GetId()) rdlist = self.OnImportGeneric( reqrdr,self.ImportPDFReaderlist,'PDF G(R) Data',multiple=True) if len(rdlist) == 0: return @@ -2534,10 +2556,10 @@ def OnImportPDF(self,event): ' from file '+self.lastimport) # data are read, now store them in the tree Id = self.GPXtree.AppendItem(self.root,text=HistName) - Ymin = np.min(rd.pdfdata[1]) + Ymin = np.min(rd.pdfdata[1]) Ymax = np.max(rd.pdfdata[1]) - Rmin = np.min(rd.pdfdata[0]) - Rmax = np.max(rd.pdfdata[0]) + Rmin = np.min(rd.pdfdata[0]) + Rmax = np.max(rd.pdfdata[0]) valuesdict = { 'wtFactor':1.0,'Dummy':False,'ranId':ran.randint(0,sys.maxsize), 'Offset':[0.0,0.0],'delOffset':0.02*Ymax, @@ -2553,16 +2575,16 @@ def OnImportPDF(self,event): self.EnablePlot = True self.GPXtree.Expand(Id) self.GPXtree.SelectItem(Id) - + if not newHistList: return # somehow, no new histograms return # success - + def AddToNotebook(self,text,typ=None, TimeStamp=True): '''Add entry to Notebook tree item ''' Id = GetGPXtreeItemId(self,self.root,'Notebook') data = self.GPXtree.GetItemPyData(Id) - if TimeStamp: + if TimeStamp: data.append(f'[TS] Notebook entry @ {time.ctime()}') if typ: data.append(f'[{typ}] {text}') @@ -2576,7 +2598,7 @@ def _init_Exports(self,menu): projectmenu = wx.Menu() item = menu.AppendSubMenu(projectmenu,'Entire project as','Export entire project') self.ExportNonSeq.append([menu,item.Id]) - + phasemenu = wx.Menu() item = menu.AppendSubMenu(phasemenu,'Phase as','Export phase or sometimes phases') self.ExportNonSeq.append([menu,item.Id]) @@ -2584,7 +2606,7 @@ def _init_Exports(self,menu): powdermenu = wx.Menu() item = menu.AppendSubMenu(powdermenu,'Powder data as','Export powder diffraction histogram(s)') self.ExportNonSeq.append([menu,item.Id]) - + sasdmenu = wx.Menu() item = menu.AppendSubMenu(sasdmenu,'Small angle data as','Export small angle histogram(s)') @@ -2611,11 +2633,11 @@ def _init_Exports(self,menu): seqHistmenu = wx.Menu() item = menu.AppendSubMenu(seqHistmenu,'Sequential histograms','Export histograms from sequential fit') self.ExportSeq.append([menu,item.Id]) - + # find all the exporter files if not self.exporterlist: # this only needs to be done once self.exporterlist = G2fil.LoadExportRoutines(self) - + # Add submenu item(s) for each Exporter by its self-declared type (can be more than one) for obj in self.exporterlist: #print 'exporter',obj @@ -2668,7 +2690,7 @@ def seqMenuItemEventHandler(event,obj=obj,typ=lbl): if 'mode' in inspect.getfullargspec(obj.Writer)[0]: item = submenu.Append(wx.ID_ANY,obj.formatName,obj.longFormatName) self.Bind(wx.EVT_MENU, seqMenuItemEventHandler, item) - + item = imagemenu.Append(wx.ID_ANY,'Multiple image controls and masks', 'Export image controls and masks for multiple images') self.Bind(wx.EVT_MENU, self.OnSaveMultipleImg, id=item.GetId()) @@ -2704,7 +2726,7 @@ def _Add_ExportMenuItems(self,parent): item = parent.Append(wx.ID_ANY,'Export HKLs...','') self.ExportHKL.append(item) self.Bind(wx.EVT_MENU, self.OnExportHKL, id=item.GetId()) - + item = parent.Append(wx.ID_ANY,'Export MTZ file...','') self.ExportMTZ.append(item) self.Bind(wx.EVT_MENU, self.OnExportMTZ, id=item.GetId()) @@ -2713,7 +2735,7 @@ def _Add_ExportMenuItems(self,parent): self.ExportPDF.append(item) item.Enable(False) self.Bind(wx.EVT_MENU, self.OnExportPDF, id=item.GetId()) - + def FillMainMenu(self,menubar,addhelp=True): '''Define contents of the main GSAS-II menu for the (main) data tree window. For the mac, this is also called for the data item windows as well so that @@ -2725,10 +2747,10 @@ def FillMainMenu(self,menubar,addhelp=True): Data = wx.Menu(title='') menubar.Append(menu=Data, title='Data') self._Add_DataMenuItems(Data) - Calculate = wx.Menu(title='') + Calculate = wx.Menu(title='') menubar.Append(menu=Calculate, title='&Calculate') self._Add_CalculateMenuItems(Calculate) - Import = wx.Menu(title='') + Import = wx.Menu(title='') menubar.Append(menu=Import, title='Import') self._Add_ImportMenu_Image(Import) self._Add_ImportMenu_Phase(Import) @@ -2737,7 +2759,7 @@ def FillMainMenu(self,menubar,addhelp=True): self._Add_ImportMenu_smallangle(Import) self._Add_ImportMenu_reflectometry(Import) self._Add_ImportMenu_PDF(Import) - + item = Import.Append(wx.ID_ANY,'Column metadata test','Test Column (.par) metadata import') self.Bind(wx.EVT_MENU, self.OnColMetaTest, id=item.GetId()) msgs = G2fil.ImportErrorMsg() @@ -2785,15 +2807,11 @@ def FillMainMenu(self,menubar,addhelp=True): menubar.Append(menu=self.ExportMenu, title='Export') self._init_Exports(self.ExportMenu) self._Add_ExportMenuItems(self.ExportMenu) - if GSASIIpath.GetConfigValue('Enable_logging'): - self.MacroMenu = wx.Menu(title='') - menubar.Append(menu=self.MacroMenu, title='Macro') - self._init_Macro() if addhelp: HelpMenu=G2G.MyHelp(self,includeTree=True, morehelpitems=[('&Tutorials\tCtrl+T','Tutorials'),]) menubar.Append(menu=HelpMenu,title='&Help') - + def _init_ctrls(self, parent): try: size = GSASIIpath.GetConfigValue('Main_Size') @@ -2812,7 +2830,7 @@ def _init_ctrls(self, parent): f = wx.Font(self.GetFont()) f.SetPointSize(f.PointSize+fontIncr) self.SetFont(f) - + self._init_Imports() #initialize Menu item objects (these contain lists of menu items that are enabled or disabled) self.MakePDF = [] @@ -2838,14 +2856,14 @@ def _init_ctrls(self, parent): self.mainPanel.SetMinimumPaneSize(100) self.treePanel = wx.Panel(self.mainPanel, wx.ID_ANY, style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER) - + self.dataWindow = G2DataWindow(self.mainPanel) dataSizer = wx.BoxSizer(wx.VERTICAL) self.dataWindow.SetSizer(dataSizer) self.mainPanel.SplitVertically(self.treePanel, self.dataWindow.outer, 400) self.Status.SetStatusWidths([200,-1]) # make these match? - + G2G.wxID_GPXTREE = wx.NewId() treeSizer = wx.BoxSizer(wx.VERTICAL) self.treePanel.SetSizer(treeSizer) @@ -2863,9 +2881,9 @@ def _init_ctrls(self, parent): self.GPXtree.Bind(wx.EVT_TREE_KEY_DOWN, self.OnGPXtreeKeyDown, id=G2G.wxID_GPXTREE) self.GPXtree.Bind(wx.EVT_TREE_BEGIN_RDRAG, - self.OnGPXtreeBeginRDrag, id=G2G.wxID_GPXTREE) + self.OnGPXtreeBeginRDrag, id=G2G.wxID_GPXTREE) self.GPXtree.Bind(wx.EVT_TREE_END_DRAG, - self.OnGPXtreeEndDrag, id=G2G.wxID_GPXTREE) + self.OnGPXtreeEndDrag, id=G2G.wxID_GPXTREE) self.root = self.GPXtree.root try: @@ -2877,7 +2895,7 @@ def _init_ctrls(self, parent): else: raise Exception except: - size = wx.Size(700,600) + size = wx.Size(700,600) self.plotFrame = wx.Frame(None,-1,'GSASII Plots',size=size, style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX) self.G2plotNB = G2plt.G2PlotNoteBook(self.plotFrame,G2frame=self) @@ -2897,7 +2915,7 @@ def _init_ctrls(self, parent): class dummymenu(object): def Enable(*args, **kwargs): pass self.CancelSetLimitsMode = dummymenu() - + #### init_vars ################################################################ def init_vars(self): ''' initialize default values for GSAS-II "global" variables (saved in main Frame, G2frame) @@ -2969,7 +2987,7 @@ def init_vars(self): self.RMCchoice = 'RMCProfile' self.ifSetLimitsMode = 0 - + def __init__(self, parent): self.ExportLookup = {} self.exporterlist = [] @@ -2995,11 +3013,11 @@ def __init__(self, parent): self.LastExportDir = None # the last directory used for exports, if any. self.dataDisplay = None self.init_vars() - + if GSASIIpath.GetConfigValue('Starting_directory'): try: pth = GSASIIpath.GetConfigValue('Starting_directory') - pth = os.path.expanduser(pth) + pth = os.path.expanduser(pth) os.chdir(pth) self.LastGPXdir = pth except: @@ -3026,7 +3044,7 @@ def __init__(self, parent): print (traceback.format_exc()) elif any('SPYDER' in name for name in os.environ): self.OnFileReopen(None) - + def GetTreeItemsList(self,item): ''' returns a list of all GSAS-II tree items ''' @@ -3041,7 +3059,7 @@ def GetTreeItemsList(self,item): # self.mainPanel.SetSize(wx.Size(w,h)) # self.GPXtree.SetSize(wx.Size(w,h)) # self.dataWindow.SetSize(self.dataPanel.GetClientSize()) - + def SetDataSize(self): '''this routine is a placeholder until all G2frame.SetDataSize calls are replaced by G2frame.dataWindow.SetDataSize @@ -3050,7 +3068,7 @@ def SetDataSize(self): print ('G2frame.SetDataSize called rather than dataWindow.SetDataSize') G2obj.HowDidIgetHere(True) self.dataWindow.SetDataSize() - + def OnDataTreeSelChanged(self, event): '''Called when a data tree item is selected. May be called on item deletion as well. ''' @@ -3067,7 +3085,7 @@ def OnDataTreeSelChanged(self, event): wx.CallAfter(SelectDataTreeItem,self,item,self.oldFocus) #if self.oldFocus: # now done via last parameter on SelectDataTreeItem # wx.CallAfter(self.oldFocus.SetFocus) - + def OnGPXtreeItemCollapsed(self, event): 'Called when a tree item is collapsed - all children will be collapsed' self.GPXtree.CollapseAllChildren(event.GetItem()) @@ -3075,7 +3093,7 @@ def OnGPXtreeItemCollapsed(self, event): def OnGPXtreeItemExpanded(self, event): 'Called when a tree item is expanded' event.Skip() - + def OnGPXtreeItemDelete(self, event): 'Called when a tree item is deleted, inhibit the next tree item selection action' self.TreeItemDelete = True @@ -3083,7 +3101,7 @@ def OnGPXtreeItemDelete(self, event): def OnGPXtreeItemActivated(self, event): 'Called when a tree item is activated' event.Skip() - + def OnGPXtreeBeginRDrag(self,event): event.Allow() self.BeginDragId = event.GetItem() @@ -3094,8 +3112,8 @@ def OnGPXtreeBeginRDrag(self,event): while item: #G2 data tree has no sub children under a child of a tree item name = self.GPXtree.GetItemText(item) self.DragData.append([name,self.GPXtree.GetItemPyData(item)]) - item, cookie = self.GPXtree.GetNextChild(self.BeginDragId, cookie) - + item, cookie = self.GPXtree.GetNextChild(self.BeginDragId, cookie) + def OnGPXtreeEndDrag(self,event): event.Allow() self.EndDragId = event.GetItem() @@ -3115,7 +3133,7 @@ def OnGPXtreeEndDrag(self,event): self.GPXtree.SetItemPyData(Id,item) self.GPXtree.Delete(self.BeginDragId) SelectDataTreeItem(self,NewId) - + def OnGPXtreeKeyDown(self,event): #doesn't exactly work right with Shift key down 'Allows stepping through the tree with the up/down arrow keys' self.oldFocus = wx.Window.FindFocus() @@ -3137,7 +3155,7 @@ def OnGPXtreeKeyDown(self,event): #doesn't exactly work right with Shift key dow wx.CallAfter(self.GPXtree.SelectItem,NewId) else: wx.CallAfter(self.GPXtree.SelectItem,item) - elif sys.platform == "win32": + elif sys.platform == "win32": self.GPXtree.GetPrevSibling(item) self.GPXtree.SelectItem(item) else: @@ -3154,17 +3172,17 @@ def OnGPXtreeKeyDown(self,event): #doesn't exactly work right with Shift key dow wx.CallAfter(self.GPXtree.SelectItem,NewId) else: wx.CallAfter(self.GPXtree.SelectItem,item) - elif sys.platform == "win32": + elif sys.platform == "win32": self.GPXtree.GetNextSibling(item) self.GPXtree.SelectItem(item) - else: + else: item = self.GPXtree.GetNextSibling(item) if item.IsOk(): self.GPXtree.SelectItem(item) - + def OnColMetaTest(self,event): 'Test the .par/.*lbls pair for contents' G2imG.testColumnMetadata(self) - + def OnPowderFPA(self,event): 'Perform FPA simulation/peak fitting' # if GSASIIpath.GetConfigValue('debug'): @@ -3172,13 +3190,13 @@ def OnPowderFPA(self,event): # import imp # imp.reload(G2fpa) G2fpa.GetFPAInput(self) - + def OnReadPowderPeaks(self,event): 'Bound to menu Data/Read Powder Peaks' self.CheckNotebook() pth = G2G.GetImportPath(self) if not pth: pth = '.' - dlg = wx.FileDialog(self, 'Choose file with peak list', pth, '', + dlg = wx.FileDialog(self, 'Choose file with peak list', pth, '', 'peak files (*.txt)|*.txt|All files (*.*)|*.*',wx.FD_MULTIPLE) try: if dlg.ShowModal() == wx.ID_OK: @@ -3189,20 +3207,20 @@ def OnReadPowderPeaks(self,event): comments,peaks,limits,wave = G2IO.GetPowderPeaks(self.powderfile) Id = self.GPXtree.AppendItem(parent=self.root,text='PKS '+os.path.basename(self.powderfile)) data = ['PKS',wave,0.0] - names = ['Type','Lam','Zero'] + names = ['Type','Lam','Zero'] codes = [0,0,0] inst = [G2fil.makeInstDict(names,data,codes),{}] self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Instrument Parameters'),inst) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Comments'),comments) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Limits'),[tuple(limits),limits]) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Index Peak List'),[peaks,[]]) - self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Unit Cells List'),[]) + self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Unit Cells List'),[]) self.GPXtree.Expand(Id) self.GPXtree.SelectItem(Id) os.chdir(dlg.GetDirectory()) # to get Mac/Linux to change directory! finally: dlg.Destroy() - + def CheckNotebook(self): '''Make sure the data tree has the minimally expected controls. ''' @@ -3235,12 +3253,12 @@ def CheckNotebook(self): 'Spin':{},'RBIds':{'Vector':[],'Residue':[],'Spin':[]}}) if new: self.GPXtree.Expand(self.GPXtree.root) - + class CopyDialog(wx.Dialog): '''Creates a dialog for copying control settings between data tree items''' def __init__(self,parent,title,text,data): - wx.Dialog.__init__(self,parent,-1,title, + wx.Dialog.__init__(self,parent,-1,title, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.data = data panel = wx.Panel(self) @@ -3253,7 +3271,7 @@ def __init__(self,parent,title,text,data): dataGridSizer = wx.FlexGridSizer(cols=ncols,hgap=2,vgap=2) for Id,item in enumerate(self.data): ckbox = wx.CheckBox(panel,Id,item[1]) - ckbox.Bind(wx.EVT_CHECKBOX,self.OnCopyChange) + ckbox.Bind(wx.EVT_CHECKBOX,self.OnCopyChange) dataGridSizer.Add(ckbox,0,wx.LEFT,10) mainSizer.Add(dataGridSizer,0,wx.EXPAND) OkBtn = wx.Button(panel,-1,"Ok") @@ -3266,31 +3284,31 @@ def __init__(self,parent,title,text,data): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) panel.SetSizer(mainSizer) panel.Fit() self.Fit() - + def OnCopyChange(self,event): Id = event.GetId() - self.data[Id][0] = self.FindWindowById(Id).GetValue() - + self.data[Id][0] = self.FindWindowById(Id).GetValue() + def OnOk(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_CANCEL) - + self.EndModal(wx.ID_CANCEL) + def GetData(self): return self.data - + class SumDialog(wx.Dialog): - '''Allows user to supply scale factor(s) when summing data + '''Allows user to supply scale factor(s) when summing data ''' def __init__(self,parent,title,text,dataType,data,dataList,Limits=None): wx.Dialog.__init__(self,parent,-1,title,size=(400,250), @@ -3310,8 +3328,8 @@ def __init__(self,parent,title,text,dataType,data,dataList,Limits=None): self.filterVal = '' self.panel = None self.Draw() - - def Draw(self): + + def Draw(self): if self.panel: self.panel.DestroyChildren() #safe: wx.Panel self.panel.Destroy() @@ -3368,7 +3386,7 @@ def Draw(self): btnSizer.Add(self.OkBtn) btnSizer.Add(cancelBtn) btnSizer.Add((5,5)) - + self.panel.SetSizer(mainSizer) self.panel.SetAutoLayout(1) self.panel.SetupScrolling() @@ -3377,7 +3395,7 @@ def Draw(self): self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def OnAve(self,event): self.average = self.Avg.GetValue() @@ -3401,13 +3419,13 @@ def OnFilter(self,event): # self.selectData = copy.copy(self.data[:-1]) self.selectVals = len(self.data)*[0.0,] wx.CallAfter(self.Draw) - + def GetData(self): if self.dataType == 'PWDR': return self.selectData+[self.data[-1],],self.result else: return self.selectData+[self.data[-1],],self.selectVals - + def onChar(self,event): 'Respond to keyboard events in the Filter box' self.filterVal = self.filterBox.GetValue() @@ -3429,7 +3447,7 @@ def OnAllScale(self,event): for Id,item in enumerate(self.selectData): self.selectVals[Id] = val wx.CallAfter(self.Draw) - + def OnTest(self,event): lenX = 0 Xminmax = [0,0] @@ -3500,19 +3518,19 @@ def OnTest(self,event): G2plt.PlotXY(self,XY,lines=True,Title='Sum:'+self.data[-1],labelY='Intensity',) self.plotFrame.Show() return True - + def OnOk(self,event): if self.dataType == 'PWDR': if not self.OnTest(event): return parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_OK) - + self.EndModal(wx.ID_OK) + def OnCancel(self,event): parent = self.GetParent() if parent is not None: parent.Raise() - self.EndModal(wx.ID_CANCEL) - + self.EndModal(wx.ID_CANCEL) + def OnPwdrSum(self,event): 'Sum or Average together powder data(?)' TextList = [] @@ -3537,7 +3555,7 @@ def OnPwdrSum(self,event): if len(TextList) < 2: self.ErrorDialog('Not enough data to sum/average','There must be more than one "PWDR" pattern') return - TextList.append('default_ave_name') + TextList.append('default_ave_name') dlg = self.SumDialog(self,'Sum/Average data',''' Enter scale for each pattern to be summed/averaged Limits for first pattern used sets range for the sum @@ -3573,7 +3591,7 @@ def OnPwdrSum(self,event): } self.GPXtree.SetItemPyData(Id,[valuesdict,[np.array(Xsum),np.array(Ysum),np.array(Wsum), np.array(YCsum),np.array(YBsum),np.array(YDsum)]]) - self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Comments'),Comments) + self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Comments'),Comments) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Limits'),[tuple(Xminmax),Xminmax]) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Background'),[['chebyschev-1',True,3,1.0,0.0,0.0], {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[],'background PWDR':['',1.0,False]}]) @@ -3581,8 +3599,8 @@ def OnPwdrSum(self,event): self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Peak List'),{'peaks':[],'sigDict':{}}) self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Index Peak List'),[[],[]]) - self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Unit Cells List'),[]) - self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Reflection Lists'),{}) + self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Unit Cells List'),[]) + self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Reflection Lists'),{}) self.GPXtree.SelectItem(Id) self.GPXtree.Expand(Id) finally: @@ -3594,6 +3612,7 @@ def OnImageSum(self,event): DataList = [] IdList = [] Names = [] + Items = [] Comments = ['Sum equals: \n'] if self.GPXtree.GetCount(): item, cookie = self.GPXtree.GetFirstChild(self.root) @@ -3601,6 +3620,7 @@ def OnImageSum(self,event): name = self.GPXtree.GetItemText(item) Names.append(name) if 'IMG' in name: + Items.append(item) TextList.append(name) DataList.append(self.GPXtree.GetImageLoc(item)) #Size,Image,Tag IdList.append(item) @@ -3609,18 +3629,32 @@ def OnImageSum(self,event): if len(TextList) < 2: self.ErrorDialog('Not enough data to sum','There must be more than one "IMG" pattern') return - TextList.append('default_sum_name') + TextList.append('default_sum_name') dlg = self.SumDialog(self,'Sum data',' Enter scale for each image to be summed','IMG', TextList,DataList) + chkItems = ['pixelSize','wavelength','distance','center','size','tilt','rotation'] try: if dlg.ShowModal() == wx.ID_OK: imSize = 0 result,scales = dlg.GetData() First = True Found = False - for name,scale in zip(result,scales): + for item,name,scale in zip(Items,result,scales): if scale: - Found = True + if not Found: + Data = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,item,'Image Controls')) + chkData = {Id:Data[Id] for Id in chkItems} + else: + data = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,item,'Image Controls')) + chkdata = {Id:data[Id] for Id in chkItems} + if str(chkdata) != str(chkData): + self.ErrorDialog('Image Controls error','Images to be summed must have same Image Controls - see Console for list') + chkDiff = [[iD,chkData[iD],chkdata[iD]] for iD in chkData if str(chkData[iD]) != str(chkdata[iD])] + print('Differences: ') + for diff in chkDiff: + print('%s: %s %s'%(diff[0],str(diff[1]),str(diff[2]))) + return + Found = True Comments.append("%10.3f %s" % (scale,' * '+name)) i = TextList.index(name) Npix,imagefile,imagetag = DataList[i] @@ -3643,8 +3677,8 @@ def OnImageSum(self,event): if not Found: self.ErrorDialog('Image sum error','No nonzero image multipliers found') return - - newImage = np.array(newImage,dtype=np.int32) + + newImage = np.array(newImage,dtype=np.int32) outname = 'IMG '+result[-1] Id = 0 if outname in Names: @@ -3659,8 +3693,8 @@ def OnImageSum(self,event): if Id: pth = os.path.split(os.path.abspath(imagefile))[0] # pth = G2G.GetExportPath(self) - dlg = wx.FileDialog(self, 'Choose sum image filename', pth,outname.split('IMG ')[1], - 'G2img files (*.G2img)|*.G2img', + dlg = wx.FileDialog(self, 'Choose sum image filename', pth,outname.split('IMG ')[1], + 'G2img files (*.G2img)|*.G2img', wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: newimagefile = dlg.GetPath() @@ -3683,7 +3717,7 @@ def OnImageSum(self,event): Data['ellipses'] = [] Data['calibrant'] = '' Data['range'] = [(Imin,Imax),[Imin,Imax]] - self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Image Controls'),Data) + self.GPXtree.SetItemPyData(self.GPXtree.AppendItem(Id,text='Image Controls'),Data) Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[], 'Frames':[],'Thresholds':[(Imin,Imax),[Imin,Imax]], 'SpotMask':{'esdMul':2.,'spotMask':None}} @@ -3696,7 +3730,7 @@ def OnImageSum(self,event): self.Image = self.PickId finally: dlg.Destroy() - + def OnAddPhase(self,event): 'Add a new, empty phase to the tree. Called by Data/Add Phase menu' PhaseName = '' @@ -3722,19 +3756,19 @@ def OnAddPhase(self,event): self.GPXtree.SetItemPyData(newphase,G2obj.SetNewPhase(Name=PhaseName,SGData=SGData)) self.GPXtree.Expand(sub) SelectDataTreeItem(self,newphase) #bring up new phase General tab - + def OnDeletePhase(self,event): '''Delete one or more phases from the tree. Called by Data/Delete Phase menu. - Also delete this phase from Reflection Lists for each PWDR histogram; - removes the phase from restraints and deletes any constraints + Also delete this phase from Reflection Lists for each PWDR histogram; + removes the phase from restraints and deletes any constraints with variables from the phase. If any deleted phase is marked as Used in a histogram, a more rigorous - "deep clean" is done and histogram refinement results are cleared, as well as + "deep clean" is done and histogram refinement results are cleared, as well as the covariance information and all plots are deleted ''' selItem = self.GPXtree.GetSelection() if self.dataWindow: - self.dataWindow.ClearData() + self.dataWindow.ClearData() TextList = [] DelList = [] DelItemList = [] @@ -3753,11 +3787,11 @@ def OnDeletePhase(self,event): constr = self.GPXtree.GetItemPyData(id) else: constr = {} - + item, cookie = self.GPXtree.GetFirstChild(sub) while item: TextList.append(self.GPXtree.GetItemText(item)) - item, cookie = self.GPXtree.GetNextChild(sub, cookie) + item, cookie = self.GPXtree.GetNextChild(sub, cookie) dlg = wx.MultiChoiceDialog(self, 'Which phase(s) to delete?', 'Delete phase', TextList, wx.CHOICEDLG_STYLE) try: if dlg.ShowModal() == wx.ID_OK: @@ -3808,7 +3842,7 @@ def OnDeletePhase(self,event): # could wipe out computed & difference patterns, but does not work #data[1][3] = np.zeros_like(data[1][3]) #data[1][5] = np.zeros_like(data[1][5]) - # always get rid of reflection lists + # always get rid of reflection lists Id = GetGPXtreeItemId(self,item, 'Reflection Lists') refList = self.GPXtree.GetItemPyData(Id) if len(refList): @@ -3850,12 +3884,12 @@ def OnDeletePhase(self,event): wx.CallAfter(self.GPXtree.SelectItem,selItem) if consDeleted: print('\n',consDeleted,'constraints were deleted') - + def OnRenameData(self,event): '''Renames an existing histogram. Called by Data/Rename Phase menu. Must be used before a histogram is used in a phase. ''' - name = self.GPXtree.GetItemText(self.PickId) + name = self.GPXtree.GetItemText(self.PickId) Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree() if name in Histograms: G2G.G2MessageBox(self, @@ -3881,14 +3915,14 @@ def OnRenameData(self,event): if 'PWDR' in name: self.GPXtree.GetItemPyData(self.PickId)[2] = name dlg.Destroy() - - def GetFileList(self,fileType,skip=None): + + def GetFileList(self,fileType,skip=None): ''' Get list of file names containing a particular string; can skip one of known GSAS-II id param: fileType str: any string within a file name param: skip int:default=None, a GSAS-II assigned id of a data item to skip in collecting the names returns: list of file names from GSAS-II tree returns: str name of file optionally skipped - Appears unused, but potentially useful. + Appears unused, but potentially useful. Note routine of same name in GSASIIpwdGUI; it does not have the skip option ''' fileList = [] @@ -3906,7 +3940,7 @@ def GetFileList(self,fileType,skip=None): return fileList,Source else: return fileList - + def OnDataDelete(self, event): '''Delete one or more histograms from data tree. Called by the Data/DeleteData menu @@ -3920,7 +3954,7 @@ def OnDataDelete(self, event): Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree() if not self.GPXtree.GetCount(): G2G.G2MessageBox(self,'No tree items to be deleted','Nothing to delete') - return + return item, cookie = self.GPXtree.GetFirstChild(self.root) used = False seqUse = False @@ -4007,9 +4041,9 @@ def OnDataDelete(self, event): SelectDataTreeItem(self,selItem) try: # fails if previously selected item is deleted self.GPXtree.UpdateSelection() - except: + except: self.GPXtree.SelectItem(self.root) - + def OnPlotDelete(self,event): '''Delete one or more plots from plot window. Called by the Data/DeletePlots menu @@ -4021,11 +4055,11 @@ def OnPlotDelete(self,event): if dlg.ShowModal() == wx.ID_OK: result = dlg.GetSelections() result.sort(reverse=True) - for i in result: + for i in result: self.G2plotNB.Delete(plotNames[i]) finally: dlg.Destroy() - + def OnDeleteSequential(self,event): ''' Delete any sequential results table. Called by the Data/Delete sequential results menu ''' @@ -4097,8 +4131,8 @@ def OnFileReopen(self, event): self.OnFileOpen(event, filename=f) self.LastGPXdir = dirname else: - print('file not found',f) - + print('file not found',f) + def OnFileReread(self, event): '''reread the current GPX file; no questions asked -- no save for development purposes. @@ -4122,9 +4156,9 @@ def _setStart(nameParent,name): else: print('file not found',f) wx.CallLater(100,_setStart,nameParent,name) - + def _SaveOld(self,askSave=True): - '''See if we should save current project and continue + '''See if we should save current project and continue to read another. returns True if the project load should continue ''' @@ -4155,19 +4189,19 @@ def _SaveOld(self,askSave=True): if self.G2plotNB.plotList: self.G2plotNB.clear() return True - + def OnFileOpen(self, event, filename=None, askSave=True): '''Gets a GSAS-II .gpx project file in response to the File/Open Project menu button ''' - + def GetGPX(): if self.LastGPXdir: pth = self.LastGPXdir else: pth = '.' #if GSASIIpath.GetConfigValue('debug'): print('debug: open from '+pth) - dlg = wx.FileDialog(self, 'Choose GSAS-II project file', pth, + dlg = wx.FileDialog(self, 'Choose GSAS-II project file', pth, wildcard='GSAS-II project file (*.gpx)|*.gpx',style=wx.FD_OPEN) try: if dlg.ShowModal() != wx.ID_OK: return @@ -4176,7 +4210,7 @@ def GetGPX(): self.LastGPXdir = dlg.GetDirectory() finally: dlg.Destroy() - + self.EnablePlot = False if self.GPXtree.GetChildrenCount(self.root,False): if not self._SaveOld(askSave=askSave): return @@ -4202,9 +4236,9 @@ def GetGPX(): print (traceback.format_exc()) def OnFileBrowse(self, event): - '''Gets a GSAS-II .gpx project using the GPX browser, in response - to the File/"Open Project browser" menu button - ''' + '''Gets a GSAS-II .gpx project using the GPX browser, in response + to the File/"Open Project browser" menu button + ''' self.EnablePlot = False if self.LastGPXdir: pth = self.LastGPXdir @@ -4219,7 +4253,7 @@ def OnFileBrowse(self, event): return finally: dlg.Destroy() - + if self.GPXtree.GetChildrenCount(self.root,False): if not self._SaveOld(): return # cancel was entered; nothing changed self.GSASprojectfile = filename @@ -4233,9 +4267,9 @@ def OnFileBrowse(self, event): import traceback print (traceback.format_exc()) - + def StartProject(self): - '''Opens a GSAS-II project file & selects the 1st available data set to + '''Opens a GSAS-II project file & selects the 1st available data set to display (PWDR, HKLF, REFD or SASD) ''' @@ -4335,15 +4369,15 @@ def OnFileSave(self, event): '''Save the current project in response to the File/Save Project menu button ''' - - if self.GSASprojectfile: + + if self.GSASprojectfile: self.GPXtree.SetItemText(self.root,'Project: '+self.GSASprojectfile) self.CheckNotebook() G2IO.ProjFileSave(self) return True else: return self.OnFileSaveas(event) - + def OnNewGSASII(self, event): '''Gets a GSAS-II .gpx project file in response to the File/Open new window menu button. Runs only on Mac. @@ -4353,10 +4387,10 @@ def OnNewGSASII(self, event): else: pth = '.' GSASprojectfile = '' - dlg = wx.FileDialog(self, 'Choose GSAS-II project file', pth, + dlg = wx.FileDialog(self, 'Choose GSAS-II project file', pth, wildcard='GSAS-II project file (*.gpx)|*.gpx',style=wx.FD_OPEN) try: - if dlg.ShowModal() == wx.ID_OK: + if dlg.ShowModal() == wx.ID_OK: GSASprojectfile = dlg.GetPath() if not os.path.exists(GSASprojectfile): print(f'File not found {GSASprojectfile}') @@ -4365,9 +4399,8 @@ def OnNewGSASII(self, event): self.LastGPXdir = dlg.GetDirectory() finally: dlg.Destroy() - #G2script = os.path.join(os.path.split(__file__)[0],'GSASII.py') G2fil.openInNewTerm(GSASprojectfile) - + def SetTitleByGPX(self): '''Set the title for the two window frames ''' @@ -4386,12 +4419,12 @@ def OnFileSaveas(self, event): ''' if GSASIIpath.GetConfigValue('Starting_directory'): pth = GSASIIpath.GetConfigValue('Starting_directory') - pth = os.path.expanduser(pth) + pth = os.path.expanduser(pth) elif self.LastGPXdir: pth = self.LastGPXdir else: pth = '.' - dlg = wx.FileDialog(self, 'Choose GSAS-II project file name', pth, self.newGPXfile, + dlg = wx.FileDialog(self, 'Choose GSAS-II project file name', pth, self.newGPXfile, 'GSAS-II project file (*.gpx)|*.gpx',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: #TODO: what about Cancel? @@ -4402,7 +4435,7 @@ def OnFileSaveas(self, event): G2IO.ProjFileSave(self) self.SetTitleByGPX() self.LastGPXdir = dlg.GetDirectory() - os.chdir(self.LastGPXdir) + os.chdir(self.LastGPXdir) config = G2G.GetConfigValsDocs() GSASIIpath.addPrevGPX(self.GSASprojectfile,config) # add new name G2G.SaveConfigVars(config) @@ -4411,7 +4444,7 @@ def OnFileSaveas(self, event): return False finally: dlg.Destroy() - + def ExpandAll(self,event): '''Expand all tree items or those of a single type ''' @@ -4444,7 +4477,7 @@ def MoveTreeItems(self,event): if self.GPXtree.GetItemText(item).startswith(txt+' '): copyList.append(item) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) - + self.ExpandingAll = True try: for item in copyList: @@ -4461,7 +4494,7 @@ def MoveTreeItems(self,event): finally: self.ExpandingAll = False SelectDataTreeItem(self,self.root) - + def ExitMain(self, event): '''Called if exit selected or the main window is closed rescord last position of data & plot windows; saved to config.py file @@ -4497,14 +4530,14 @@ def ExitMain(self, event): print('Config save failed') if self.G2plotNB: self.G2plotNB.Destroy() - if self.undofile and os.path.exists(self.undofile): + if self.undofile and os.path.exists(self.undofile): os.remove(self.undofile) sys.exit() - + def OnExportMTZ(self,event): ''' exports MTZ file from macromoleculat Reflection Lists in multiple histograms ''' - + Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree() MTZok = False for phase in Phases: @@ -4540,7 +4573,7 @@ def OnExportMTZ(self,event): IoRange = [[10000.0,0.0] for i in range(nDif)] IcRange = [[10000.0,0.0] for i in range(nDif)] SigRange = [[10000.0,0.0] for i in range(nDif)] - + for ih,hist in enumerate(useHist): Refset = Histograms[hist]['Reflection Lists'][General['Name']]['RefList'] for ref in Refset: @@ -4623,14 +4656,14 @@ def OnExportMTZ(self,event): Header += ('END'.ljust(80)) Header += ('Created by GSAS-II on %s'%time.ctime()).ljust(80) Header += ('MTZENDOFHEADER'.ljust(80)) - + if useFo: fName = General['Name'].replace(' ','_')+'F.mtz' else: fName = General['Name'].replace(' ','_')+'F2.mtz' MTZ = open(fName,'wb') MTZ.write(st.pack('4sl2s70x',b'MTZ ',21+nCol*nRef,b'DA')) - + for ref in refDict: rec = [float(i) for i in ref.split()] refData = refDict[ref] @@ -4646,18 +4679,18 @@ def OnExportMTZ(self,event): MTZ.write(Header.encode()) MTZ.close() print('MTZ file %s written'%fName) - + def OnExportPeakList(self,event): '''Exports a PWDR peak list as a text file ''' pth = G2G.GetExportPath(self) - dlg = wx.FileDialog(self, 'Choose output peak list file name', pth, '', + dlg = wx.FileDialog(self, 'Choose output peak list file name', pth, '', '(*.*)|*.*',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: self.peaklistfile = dlg.GetPath() self.peaklistfile = G2IO.FileDlgFixExt(dlg,self.peaklistfile) - file = open(self.peaklistfile,'w') + file = open(self.peaklistfile,'w') item, cookie = self.GPXtree.GetFirstChild(self.root) while item: name = self.GPXtree.GetItemText(item) @@ -4675,12 +4708,12 @@ def OnExportPeakList(self,event): pkdata = self.GPXtree.GetItemPyData(item2) peaks = pkdata['peaks'] sigDict = pkdata['sigDict'] - item2, cookie2 = self.GPXtree.GetNextChild(item, cookie2) + item2, cookie2 = self.GPXtree.GetNextChild(item, cookie2) file.write("#%s \n" % (name+' Peak List')) if wave: file.write('#wavelength = %10.6f\n'%(wave)) if 'T' in Type: - file.write('#%9s %10s %10s %12s %10s %10s %10s %10s %10s %10s\n'%('pos','dsp','esd','int','esd','alp','bet','sig','gam','FWHM')) + file.write('#%9s %10s %10s %12s %10s %10s %10s %10s %10s %10s\n'%('pos','dsp','esd','int','esd','alp','bet','sig','gam','FWHM')) else: file.write('#%9s %10s %10s %12s %10s %10s %10s %10s %10s %10s\n'%('pos','dsp','esd','int','esd','sig','esd','gam','esd','FWHM')) for ip,peak in enumerate(peaks): @@ -4706,22 +4739,22 @@ def OnExportPeakList(self,event): FWHM = G2pwd.getgamFW(gam,sig) #to get delta-2-theta in deg. from Gam(peak) file.write("%10.4f %10.5f %10.5f %12.2f %10.2f %10.5f %10.5f %10.5f %10.5f %10.5f \n" % \ (peak[0],dsp,esddsp,peak[2],esds['int'],np.sqrt(max(0.0001,peak[4]))/100.,esds['sig']/100.,peak[6]/100.,esds['gam']/100,FWHM/100.)) #convert to deg - item, cookie = self.GPXtree.GetNextChild(self.root, cookie) + item, cookie = self.GPXtree.GetNextChild(self.root, cookie) file.close() finally: dlg.Destroy() - + def OnExportHKL(self,event): '''Exports a PWDR reflection list as a text file ''' pth = G2G.GetExportPath(self) - dlg = wx.FileDialog(self, 'Choose output reflection list file name', pth, '', + dlg = wx.FileDialog(self, 'Choose output reflection list file name', pth, '', '(*.*)|*.*',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: self.peaklistfile = dlg.GetPath() self.peaklistfile = G2IO.FileDlgFixExt(dlg,self.peaklistfile) - file = open(self.peaklistfile,'w') + file = open(self.peaklistfile,'w') item, cookie = self.GPXtree.GetFirstChild(self.root) while item: name = self.GPXtree.GetItemText(item) @@ -4741,7 +4774,7 @@ def OnExportHKL(self,event): file.write("%s %s %s \n" % (name,phase,' Reflection List')) if 'T' in peaks.get('Type','PXC'): file.write('%s \n'%(' h k l m d-space TOF wid Fo**2 Fc**2 Icorr Prfo Trans ExtP I100')) - else: + else: file.write('%s \n'%(' h k l m d-space 2-theta wid Fo**2 Fc**2 Icorr Prfo Trans ExtP I100')) for ipk,peak in enumerate(peaks['RefList']): if 'T' in peaks.get('Type','PXC'): @@ -4758,12 +4791,12 @@ def OnExportHKL(self,event): file.write(" %3d %3d %3d %3d%10.5f%10.5f%10.5f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f\n" % \ (int(peak[0]),int(peak[1]),int(peak[2]),int(peak[3]),peak[4],peak[5],FWHM/100., peak[8],peak[9],peak[11],peak[12],peak[13],peak[14],I100[ipk])) - item2, cookie2 = self.GPXtree.GetNextChild(item, cookie2) - item, cookie = self.GPXtree.GetNextChild(self.root, cookie) + item2, cookie2 = self.GPXtree.GetNextChild(item, cookie2) + item, cookie = self.GPXtree.GetNextChild(self.root, cookie) file.close() finally: dlg.Destroy() - + def OnExportPDF(self,event): 'Save S(Q), G(R),... as selected by user' def PDFSave(G2frame,exports,PDFsaves): @@ -4793,7 +4826,7 @@ def PDFSave(G2frame,exports,PDFsaves): GetGPXtreeItemId(G2frame, pId,'Limits')) G2fil.PDFWrite(export,os.path.join(dirname,filename), PDFsaves,PDFControls,Inst,Limits) - + names = G2pdG.GetFileList(self,'PDF') exports = [] if names: @@ -4810,7 +4843,7 @@ def PDFSave(G2frame,exports,PDFsaves): if exports: PDFsaves = [od['value_1'],od['value_2'],od['value_3'],od['value_4'],od['value_5'],od['value_6']] PDFSave(self,exports,PDFsaves) - + def OnMakePDFs(self,event): '''Sets up PDF data structure filled with defaults; if found chemical formula is inserted so a default PDF can be made. @@ -4857,7 +4890,7 @@ def OnMakePDFs(self,event): ElData['FormulaNo'] = float(num) sumnum += float(num) ElList[elem] = ElData - + except ValueError: G2G.G2MessageBox(self,'Carbon-based (and wrong) PDF will be generated','Missing chemical formula') ElData = copy.deepcopy(G2elem.GetElInfo('C',Parms)) @@ -4878,7 +4911,7 @@ def OnMakePDFs(self,event): for item in self.ExportPDF: item.Enable(True) finally: dlg.Destroy() - + def GetPWDRdatafromTree(self,PWDRname): ''' Returns powder data from GSASII tree @@ -4887,7 +4920,7 @@ def GetPWDRdatafromTree(self,PWDRname): :returns: PWDRdata = powder data dictionary with Powder data arrays, Limits, Instrument Parameters, - Sample Parameters + Sample Parameters ''' PWDRdata = {} try: @@ -4925,9 +4958,9 @@ def GetHKLFdatafromTree(self,HKLFname): HKLFdata['Data'] = self.GPXtree.GetItemPyData(HKLFname)[1] HKLFdata['Instrument Parameters'] = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,HKLFname,'Instrument Parameters')) return HKLFdata - + def GetPhaseData(self): - '''Returns a dict with defined phases. + '''Returns a dict with defined phases. Note routine :func:`GSASIIstrIO.GetPhaseData` also exists to get same kind of info from GPX file & put in a list of 9 items. ''' @@ -4943,22 +4976,22 @@ def GetPhaseData(self): phaseName = self.GPXtree.GetItemText(item) phaseData[phaseName] = self.GPXtree.GetItemPyData(item) if 'ranId' not in phaseData[phaseName]: - phaseData[phaseName]['ranId'] = ran.randint(0,sys.maxsize) + phaseData[phaseName]['ranId'] = ran.randint(0,sys.maxsize) item, cookie = self.GPXtree.GetNextChild(sub, cookie) return phaseData def GetPhaseInfofromTree(self, Used=False): '''Get the phase names and their rId values, - also the histograms referenced in each phase. + also the histograms referenced in each phase. :param bool Used: if Used is True, only histograms that are referenced in the histogram are returned - :returns: (phaseRIdList, usedHistograms) where + :returns: (phaseRIdList, usedHistograms) where * phaseRIdList is a list of random Id values for each phase * usedHistograms is a dict where the keys are the phase names and the values for each key are a list of the histogram names - used in each phase. + used in each phase. ''' phaseRIdList = [] usedHistograms = {} @@ -4979,7 +5012,7 @@ def GetPhaseInfofromTree(self, Used=False): return phaseRIdList,usedHistograms def GetPhaseNames(self): - '''Returns a list of defined phases. + '''Returns a list of defined phases. Note routine :func:`GSASIIstrIO.GetPhaseNames` also exists to get same info from GPX file. ''' @@ -4996,12 +5029,12 @@ def GetPhaseNames(self): phaseNames.append(phase) item, cookie = self.GPXtree.GetNextChild(sub, cookie) return phaseNames - + def GetHistogramTypes(self): """ Returns a list of histogram types found in the GSASII data tree - + :return: list of histogram types - + """ HistogramTypes = [] if self.GPXtree.GetCount(): @@ -5013,15 +5046,15 @@ def GetHistogramTypes(self): HistogramTypes.append(Inst[0]['Type'][0]) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) return HistogramTypes - + def GetHistogramNames(self,hType): """ Returns a list of histogram names found in the GSASII data tree Note routine :func:`GSASIIstrIO.GetHistogramNames` also exists to get same info from GPX file. - + :param str hType: list of histogram types :return: list of histogram names - + """ HistogramNames = [] if self.GPXtree.GetCount(): @@ -5029,21 +5062,21 @@ def GetHistogramNames(self,hType): while item: name = self.GPXtree.GetItemText(item) if name[:4] in hType: - HistogramNames.append(name) + HistogramNames.append(name) item, cookie = self.GPXtree.GetNextChild(self.root, cookie) return HistogramNames - + def GetHistogramNamesID(self,hType): """ Returns a list of histogram names found in the GSASII data tree and a lookup table of their Id values. Should replace GetHistogramNames since that will not be much faster (and there may be real speed gains from - caching the Ids rather than keep searching for them). + caching the Ids rather than keep searching for them). N.B routine :func:`GSASIIstrIO.GetHistogramNames` also exists to get same info, but from GPX file. - + :param str hType: list of histogram types - :return: list of histogram names and a dict of histogram Ids + :return: list of histogram names and a dict of histogram Ids keyed by histogram name. """ HistogramNames = [] @@ -5053,16 +5086,16 @@ def GetHistogramNamesID(self,hType): while item: name = self.GPXtree.GetItemText(item) if name[:4] in hType: - HistogramNames.append(name) + HistogramNames.append(name) HistogramIds[name] = item item, cookie = self.GPXtree.GetNextChild(self.root, cookie) return HistogramNames,HistogramIds - + def GetUsedHistogramsAndPhasesfromTree(self): ''' Returns all histograms that are found in any phase and any phase that uses a histogram. This also assigns numbers to used phases and histograms by the - order they appear in the file. + order they appear in the file. Note routine :func:`GSASIIstrIO.GetUsedHistogramsAndPhases` also exists to get same info from GPX file. @@ -5096,7 +5129,7 @@ def GetUsedHistogramsAndPhasesfromTree(self): continue item = histIdList[hist] if item: - if 'PWDR' in hist[:4]: + if 'PWDR' in hist[:4]: Histograms[hist] = self.GetPWDRdatafromTree(item) elif 'HKLF' in hist[:4]: Histograms[hist] = self.GetHKLFdatafromTree(item) @@ -5108,7 +5141,7 @@ def GetUsedHistogramsAndPhasesfromTree(self): if badnum > 1: print(' ...hist not in histIdList error occured {} times'.format(badnum)) G2obj.IndexAllIds(Histograms=Histograms,Phases=phaseData) return Histograms,Phases - + def MakeLSParmDict(self,seqHist=None): '''Load all parameters used for computation from the tree into a dict of paired values [value, refine flag]. Note that this is @@ -5116,7 +5149,7 @@ def MakeLSParmDict(self,seqHist=None): values. Note that similar things are done in - :meth:`GSASIIfiles.ExportBaseclass.loadParmDict` (from the tree) and + :meth:`GSASIIfiles.ExportBaseclass.loadParmDict` (from the tree) and :func:`GSASIIstrMain.Refine` and :func:`GSASIIstrMain.SeqRefine` (from a GPX file). @@ -5144,7 +5177,7 @@ def MakeLSParmDict(self,seqHist=None): rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False) rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[],'Spin':[]}) Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,EFtable,ORBtables,BLtable,MFtable,maxSSwave = \ - G2stIO.GetPhaseData(Phases,RestraintDict=None,rbIds=rbIds,Print=False) + G2stIO.GetPhaseData(Phases,RestraintDict=None,rbIds=rbIds,Print=False) hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,histDict,Print=False,resetRefList=False) histVary,histDict,controlDict = G2stIO.GetHistogramData(histDict,Print=False) varyList = rbVary+phaseVary+hapVary+histVary @@ -5173,7 +5206,7 @@ def OnShowLSParms(self,event): Called from the Calculate/View LS Parms menu. This could potentially be sped up by loading only the histogram that is needed - for a sequential fit. + for a sequential fit. ''' if G2cnstG.CheckAllScalePhaseFractions(self,refine=False): return try: @@ -5184,16 +5217,16 @@ def OnShowLSParms(self,event): parmValDict = {} for i in parmDict: parmValDict[i] = parmDict[i][0] - + reqVaryList = copy.copy(varyList) # save requested variables wx.BeginBusyCursor() - try: + try: # process constraints Histograms,Phases = self.GetUsedHistogramsAndPhasesfromTree() if not len(Phases) or not len(Histograms): print('No constraints processing without phases and histograms defined') raise G2mv.ConstraintException - sub = GetGPXtreeItemId(self,self.root,'Constraints') + sub = GetGPXtreeItemId(self,self.root,'Constraints') Constraints = self.GPXtree.GetItemPyData(sub) errmsg,warnmsg = G2cnstG.CheckConstraints(self,Phases,Histograms,Constraints,[],reqVaryList) except G2mv.ConstraintException: @@ -5207,7 +5240,7 @@ def OnShowLSParms(self,event): # # check for limits on dependent vars # consVars = [i for i in reqVaryList if i not in varyList] # impossible = set( - # [str(i) for i in Controls['parmMinDict'] if i in consVars] + + # [str(i) for i in Controls['parmMinDict'] if i in consVars] + # [str(i) for i in Controls['parmMaxDict'] if i in consVars]) # if impossible: # msg = '' @@ -5223,14 +5256,18 @@ def OnShowLSParms(self,event): # print('reloading',G2G) # import imp # imp.reload(G2G) - # end debug stuff + # end debug stuff dlg = G2G.ShowLSParms(self,'Least Squares Parameters',parmValDict,G2mv.saveVaryList,reqVaryList,Controls) dlg.CenterOnParent() dlg.ShowModal() dlg.Destroy() - + def OnDerivCalc(self,event): - Controls = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,self.root, 'Controls')) + controlId = GetGPXtreeItemId(self,self.root, 'Controls') + if not controlId: + self.ErrorDialog('Computation error','No refinement information present') + return + Controls = self.GPXtree.GetItemPyData(controlId) self._cleanPartials(Controls) # set phase partials as invalid self.OnFileSave(event) errmsg, warnmsg = G2stIO.ReadCheckConstraints(self.GSASprojectfile) # check constraints are OK @@ -5261,23 +5298,23 @@ def OnDerivCalc(self,event): txt = txt.replace('Pwd=','Histogram: ') tbl.append([x,(' T' if x in varyList else ' '),derivCalcs[x][1],txt]) G2G.G2ScrolledGrid(self,'Parameter Impact Results','Impact Results',tbl,colLbls,colTypes, - maxSize=(700,400),comment=' Cite: Toby, B. H. (2024). "A simple solution to the Rietveld refinement recipe problem." J. Appl. Cryst. 57(1): 175-180.') + maxSize=(700,400),comment=f' Cite: {G2G.GetCite("Parameter Impact")}') def OnExpressionCalc(self,event): - '''Compute an arbitrary expression (supplied by user) as well as the + '''Compute an arbitrary expression (supplied by user) as well as the (statistical) standard uncertainty on that expression. - Uses the :class:`GSASIIexprGUI.ExpressionDialog` to obtain - an expression which is evaluated using the + Uses the :class:`GSASIIexprGUI.ExpressionDialog` to obtain + an expression which is evaluated using the :class:`GSASIIobj.ExpressionObj` capability. Then the derivative of the expression is computed numerically for every parameter in the covariance matrix. Finally the derivative list is used to find - the s.u. on the expression using Ted Prince's method. + the s.u. on the expression using Ted Prince's method. ''' - import GSASIIexprGUI as G2exG - import GSASIIstrMath as G2stMth + from . import GSASIIexprGUI as G2exG + from . import GSASIIstrMath as G2stMth def extendChanges(): - '''Propagate changes due to constraint and rigid bodies + '''Propagate changes due to constraint and rigid bodies from varied parameters to dependent parameters ''' # apply constraints @@ -5314,7 +5351,7 @@ def striphist(var,insChar=''): item = GetGPXtreeItemId(self,self.root,'Covariance') covData = self.GPXtree.GetItemPyData(item) covData['covMatrix'] = covData.get('covMatrix',[]) - parmValDict = {i:parmDict[i][0] for i in parmDict} # dict w/parm values only + parmValDict = {i:parmDict[i][0] for i in parmDict} # dict w/parm values only G2mv.Map2Dict(parmValDict,G2mv.saveVaryList) rigidbodyDict = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,self.root,'Rigid bodies')) @@ -5330,7 +5367,7 @@ def striphist(var,insChar=''): if ks != k: parmValDict[ks] = parmValDict[k] del parmValDict[k] - + dlg = G2exG.ExpressionDialog(self,parmValDict, header="Evaluate an expression of GSAS-II parameters", VarLabel = "Expression", @@ -5397,7 +5434,7 @@ def OnRefine(self,event): if self.testSeqRefineMode(): self.OnSeqRefine(event) return - + if G2cnstG.CheckAllScalePhaseFractions(self): return # can be slow for sequential fits, skip self.OnFileSave(event) @@ -5409,18 +5446,23 @@ def OnRefine(self,event): print ('\nError message(s):\n',errmsg) self.ErrorDialog('Error in constraints',errmsg) return - Controls = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,self.root, 'Controls')) if Controls.get('newLeBail',False): dlgtxt = '''Reset Le Bail structure factors? - - Yes: all structure factors are reset to start at unity; Le Bail-only fitting will be used before any least-squares cycles.\n - No: least-squares starts with previously set structure factors.''' - dlgb = wx.MessageDialog(self,dlgtxt,'Le Bail Mode',style=wx.YES_NO) - result = wx.ID_NO - try: - result = dlgb.ShowModal() - finally: - dlgb.Destroy() + +Yes: all structure factors are reset to start at unity; Le Bail-only fitting will be applied before any least-squares cycles.\n +No: least-squares fitting starts with previously fit structure factors.''' + #dlgb = wx.MessageDialog(self,dlgtxt,'Le Bail Mode',style=wx.YES_NO) + #result = wx.ID_NO + # try: + # result = dlgb.ShowModal() + # finally: + # dlgb.Destroy() + result = G2G.ShowScrolledInfo(self,dlgtxt,header='Le Bail Mode', + width=400,height=200, + buttonlist=[ + ('Yes', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_YES)), + ('No', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_NO)) + ]) if result == wx.ID_YES: res = self.OnLeBail(event) if res: return @@ -5439,7 +5481,7 @@ def OnRefine(self,event): else: refPlotUpdate = None try: - OK,Rvals = G2stMn.Refine(self.GSASprojectfile,dlg,refPlotUpdate=refPlotUpdate) + OK,Rvals = G2stMn.Refine(self.GSASprojectfile,dlg,refPlotUpdate=refPlotUpdate,newLeBail=Controls.get('newLeBail',False)) finally: dlg.Update(101.) # forces the Auto_Hide; needed after move w/Win & wx3.0 dlg.Destroy() @@ -5491,9 +5533,6 @@ def OnRefine(self,event): dlg.Destroy() else: self.ErrorDialog('Refinement error',Rvals['msg']) - # a fit has been done, no need to reset intensities again - Controls = self.GPXtree.GetItemPyData(GetGPXtreeItemId(self,self.root, 'Controls')) - Controls['newLeBail'] = False def OnLeBail(self,event): '''Do a 1 cycle LeBail refinement with no other variables; usually done upon initialization of a LeBail refinement @@ -5508,8 +5547,8 @@ def OnLeBail(self,event): rChi2initial = 'GOF: {:.3f}'.format(covData['Rvals']['GOF']**2) except: rChi2initial = '?' - - if GSASIIpath.GetConfigValue('G2RefinementWindow'): + + if GSASIIpath.GetConfigValue('G2RefinementWindow'): if (self.testSeqRefineMode()): l = len(self.testSeqRefineMode()) else: @@ -5563,10 +5602,10 @@ def OnLeBail(self,event): self.ErrorDialog('Le Bail error',Rvals['msg']) return True return False - + def _cleanPartials(self,Controls): '''Delete any partials created with :meth:`OnRefinePartials`; used - in GUI-based refinements, as the partials are no longer correct after + in GUI-based refinements, as the partials are no longer correct after any fit. Also clears the PhasePartials name from Controls ''' @@ -5587,9 +5626,9 @@ def LoadPartial(self,target_hId): hId = pickle.load(fp) # get histogram number if hId == target_hId: # found the target, read until we get a None or EOF - x = pickle.load(fp) + x = pickle.load(fp) yb = pickle.load(fp) - while True: + while True: phase = pickle.load(fp) if phase is None: fp.close() @@ -5603,14 +5642,14 @@ def LoadPartial(self,target_hId): fp.close() return x, yb, yDict # did not find the right histogram -- unexpected! - return None,None,[] - + return None,None,[] + def OnRefinePartials(self,event): - '''Computes and saves the intensities from each phase for each powder - histogram. Do a 0 cycle fit with no variables to pickle intensities for each + '''Computes and saves the intensities from each phase for each powder + histogram. Do a 0 cycle fit with no variables to pickle intensities for each phase into a file. Not for sequential fits. Sets Controls['PhasePartials'] to a file name to trigger save of - info in :meth:`GSASIIstrMath.getPowderProfile` and then clear that. + info in :meth:`GSASIIstrMath.getPowderProfile` and then clear that. ''' if self.testSeqRefineMode(): # should not happen, as should not be enabled G2G.G2MessageBox(self, @@ -5625,7 +5664,7 @@ def OnRefinePartials(self,event): dlg = G2G.RefinementProgress(parent=self) self.SaveTreeSetting() # save the current tree selection self.GPXtree.SaveExposedItems() # save the exposed/hidden tree items - + try: OK,Rvals = G2stMn.Refine(self.GSASprojectfile,dlg,refPlotUpdate=None) except Exception as msg: @@ -5640,11 +5679,11 @@ def OnRefinePartials(self,event): dlg.Destroy() if OK: G2G.G2MessageBox(self,'Phase partials computed; if necessary, press "P" on multiphase PWDR plot to view', - 'Partials complete') + 'Partials complete') Controls['deriv type'] = saveDervtype Controls['max cyc'] = savCyc self.OnFileSave(event) - + def OnSavePartials(self,event): ''' Saves partials as a csv file ''' @@ -5664,9 +5703,9 @@ def OnSavePartials(self,event): filename = None defpath,defnam = os.path.split(os.path.abspath( os.path.splitext(self.GSASprojectfile)[0]+'_part_N.csv')) - + dlg = wx.FileDialog(self, - 'Choose a file prefix to save the partials', defpath, defnam, + 'Choose a file prefix to save the partials', defpath, defnam, 'spreadsheet input (*.csv)|*.csv',wx.FD_SAVE) dlg.CenterOnParent() try: @@ -5733,14 +5772,15 @@ def OnSavePartials(self,event): print('File',phPartialFile,'written') fp.close() print('Saving partials as csv files finished') - + def OnClusterAnalysis(self,event): ''' Setsup cluster analysis & make tree entry ''' - + try: SKLearn = False import sklearn.cluster + sklearn.cluster SKLearn = True except: res = GSASIIpath.condaInstall('scikit-learn') @@ -5751,19 +5791,19 @@ def OnClusterAnalysis(self,event): SKLearn = True Id = GetGPXtreeItemId(self,self.root,'Cluster Analysis') if not Id: - + Id = self.GPXtree.AppendItem(self.root,text='Cluster Analysis') ClustDict = {'Files':[],'Method':'correlation','Limits':[0.,100.],'DataMatrix':[],'plots':'All', 'LinkMethod':'average','Opt Order':False,'ConDistMat':[],'NumClust':2,'codes':None,'Scikit':'K-Means'} self.GPXtree.SetItemPyData(Id,ClustDict) else: ClustDict = self.GPXtree.GetItemPyData(Id) - print('Cluster Analysis exists - nothing done') + print('Cluster Analysis exists - nothing done') ClustDict['SKLearn'] = SKLearn self.GPXtree.SelectItem(Id) - + def reloadFromGPX(self,rtext=None,Rvals={}): - '''Deletes current data tree & reloads it from GPX file (after a + '''Deletes current data tree & reloads it from GPX file (after a refinement.) Done after events are completed to avoid crashes. :param rtext str: string info from caller to be put in Notebook after reload ''' @@ -5779,22 +5819,22 @@ def reloadFromGPX(self,rtext=None,Rvals={}): ['RSTR','restrSumm'],['RB','RBsumm']): if entry in Rvals and Rvals[entry]: self.AddToNotebook(Rvals[entry],tag,TimeStamp=False) - + def SaveTreeSetting(self): 'Save the current selected tree item by name (since the id will change)' oldId = self.GPXtree.GetSelection() #retain current selection oldPath = self.GetTreeItemsList(oldId) self.lastTreeSetting = oldPath - + def ResetPlots(self): - '''This reloads the current tree item, often drawing a plot. It - also refreshes any plots that have registered a refresh routine - (see G2plotNB.RegisterRedrawRoutine) and deletes all plots that + '''This reloads the current tree item, often drawing a plot. It + also refreshes any plots that have registered a refresh routine + (see G2plotNB.RegisterRedrawRoutine) and deletes all plots that have not been refreshed and require one (see G2plotNB.SetNoDelete). ''' for lbl,win in zip(self.G2plotNB.plotList,self.G2plotNB.panelList): win.plotInvalid = True # mark all current plots as invalid so we can tell what has been updated - + oldPath = self.lastTreeSetting # reload last selected tree item, triggers window and possibly plot redraw Id = self.root for txt in oldPath: @@ -5843,7 +5883,7 @@ def ResetPlots(self): if self.G2plotNB.nb.GetPageText(i) == pltText: self.G2plotNB.nb.SetSelection(i) break - + def OnSeqRefine(self,event): '''Perform a sequential refinement. Called from self.OnRefine (Which is called from the Calculate/Refine menu) @@ -5853,7 +5893,7 @@ def OnSeqRefine(self,event): Id = GetGPXtreeItemId(self,self.root,'Sequential results') if not Id: Id = self.GPXtree.AppendItem(self.root,text='Sequential results') - self.GPXtree.SetItemPyData(Id,{}) + self.GPXtree.SetItemPyData(Id,{}) self.G2plotNB.Delete('Sequential refinement') #clear away probably invalid plot Controls['ShowCell'] = True for key in ('parmMinDict','parmMaxDict','parmFrozen'): @@ -5877,10 +5917,10 @@ def OnSeqRefine(self,event): # # Check if a phase lattice parameter refinement flag is set, if so transfer it to the Dij terms cellFit = 0 - for key in Phases: + for key in Phases: if Phases[key]['General']['Cell'][0]: cellFit += 1 - if cellFit: - msg = f'''You are refining the unit cell parameter for {cellFit} phase(s). + if cellFit: + msg = f'''You are refining the unit cell parameter for {cellFit} phase(s). In sequential fits the Dij (hydrostatic strain) terms, which provide offsets to the reciprocal cell tensor, must be refined rather than the cell parameters. @@ -5895,7 +5935,7 @@ def OnSeqRefine(self,event): dlg.Destroy() if result == wx.ID_NO: return - for p in Phases: + for p in Phases: count = 0 if Phases[p]['General']['Cell'][0]: for h in Phases[p]['Histograms']: @@ -5905,7 +5945,7 @@ def OnSeqRefine(self,event): count += 1 if count > 0: print(f'{count} hydrostratic strain refinement flags were set for phase {p}') Phases[p]['General']['Cell'][0] = False - + # save Tree to file and from here forward, work from the .gpx file not from the data tree self.OnFileSave(event) Histograms,Phases = G2stIO.GetUsedHistogramsAndPhases(self.GSASprojectfile) @@ -5946,9 +5986,9 @@ def OnSeqRefine(self,event): finally: dlg.Destroy() if result == wx.ID_NO: return - self.GPXtree.SaveExposedItems() + self.GPXtree.SaveExposedItems() # find 1st histogram to be refined - if 'Seq Data' in Controls: + if 'Seq Data' in Controls: histNames = Controls['Seq Data'] else: # patch from before Controls['Seq Data'] was implemented histNames = G2stIO.GetHistogramNames(self.GSASprojectfile,['PWDR',]) @@ -5956,10 +5996,10 @@ def OnSeqRefine(self,event): histNames.reverse() if Controls.get('newLeBail',False): dlgtxt = '''Do Le Bail refinement of intensities first? - - If Yes, resets starting structure factors; recommended after major parameter changes. + + If Yes, resets starting structure factors; recommended after major parameter changes. If No, then previous structure factors are used.''' - dlgb = wx.MessageDialog(self,dlgtxt,'Le Bail Refinement',style=wx.YES_NO) + dlgb = wx.MessageDialog(self,dlgtxt,'Le Bail Refinement',style=wx.YES_NO) result = wx.ID_NO try: result = dlgb.ShowModal() @@ -5996,7 +6036,7 @@ def OnSeqRefine(self,event): avg = np.average(Msg['maxshift/sigma']) mx = np.max(Msg['maxshift/sigma']) text += '\nBiggest Max shft/sig was {:.3f} (average across histograms {:.3f})\n'.format(mx,avg) - text += '\nLoad new result?' + text += '\nLoad new result?' dlg = wx.MessageDialog(self,text,'Refinement results',wx.OK|wx.CANCEL) dlg.CenterOnParent() try: @@ -6021,28 +6061,28 @@ def OnSeqRefine(self,event): refPlotUpdate = None finally: dlg.Destroy() - + else: self.ErrorDialog('Sequential refinement error',Msg) - + def OnRunFprime(self,event): '''Run Fprime''' - import fprime + from . import fprime self.fprime = fprime.Fprime(self) self.fprime.Show() - + def OnRunAbsorb(self,event): '''Run Absorb''' - import Absorb + from . import Absorb self.absorb = Absorb.Absorb(self) self.absorb.Show() - + def OnRunPlotXNFF(self,evnt): '''Run PlotXNFF''' - import PlotXNFF + from . import PlotXNFF self.plotXNFF = PlotXNFF.PlotXNFF(self) self.plotXNFF.Show() - + def ErrorDialog(self,title,message,parent=None, wtype=wx.OK): 'Display an error message' result = None @@ -6056,7 +6096,7 @@ def ErrorDialog(self,title,message,parent=None, wtype=wx.OK): finally: dlg.Destroy() return result - + def OnSaveMultipleImg(self,event): '''Select and save multiple image parameter and mask files ''' @@ -6077,57 +6117,57 @@ def DoSequentialProjExport(self,event): #### Data window side of main GUI; menu definitions here ######################### class G2DataWindow(wx.ScrolledWindow): #wxscroll.ScrolledPanel): - '''Create the GSAS-II data window as well as sets up the menus for each - window. There will be one instance of this in the GSAS-II app named as + '''Create the GSAS-II data window as well as sets up the menus for each + window. There will be one instance of this in the GSAS-II app named as ``G2frame.dataWindow``. - This creates two panels, where the inner one is the data object in - this class, which is scrolled. The outer one + This creates two panels, where the inner one is the data object in + this class, which is scrolled. The outer one (:data:`G2frame.dataWindow.outer`) uses all space in the appropriate part of the window, but defines a sizer at the top and bottom of the window that can be used to place information that will not be scrolled. The inner one is the ``G2frame.dataWindow`` - object. + object. Note that before any items are to be placed in either of these panels, one should call:: G2frame.dataWindow.ClearData() - This deletes the contents of the three main sizers used in the - panels. Do not delete the sizers for the unscrolled regions at the - top and bottom of the outer panel, as they cannot be [easily?] - regenerated if deleted. + This deletes the contents of the three main sizers used in the + panels. Do not delete the sizers for the unscrolled regions at the + top and bottom of the outer panel, as they cannot be [easily?] + regenerated if deleted. - The sizer for the scrolled panel should be not be reused, - though some earlier code may do that. + The sizer for the scrolled panel should be not be reused, + though some earlier code may do that. - After the contents of the data window have been created, + After the contents of the data window have been created, a call is made to:: G2frame.dataWindow.SetDataSize() - this ensures that the window's scroll bars are placed properly. - Initial GUI creation for the contents of dataWindow is done in - :func:`SelectDataTreeItem`(), which is invoked when a selection + this ensures that the window's scroll bars are placed properly. + Initial GUI creation for the contents of dataWindow is done in + :func:`SelectDataTreeItem`, which is invoked when a selection is made in the data tree selection. This may places items into - the dataWindow, but more commonly calls other routeins tht call - that. + the dataWindow, but more commonly calls other routines tht call + that. - Routines that are called multiple times to redraw the contents - of the data window should call :meth:`ClearData()` and - :meth:`SetDataSize` at the beginning and end of the GUI code, - respectively, to clear contents and complete the layout. + Routines that are called multiple times to redraw the contents + of the data window should call :meth:`ClearData()` and + :meth:`SetDataSize` at the beginning and end of the GUI code, + respectively, to clear contents and complete the layout. When placing a widget in the sizer that has its own scrolling - e.g. :class:`GSASIIctrlGUI.GSNoteBook` (anything else?) that + e.g. :class:`GSASIIctrlGUI.GSNoteBook` (anything else?) that one widget should be placed in the scrolledpanel sizer using:: mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) mainSizer.Add(G2frame.,1,wx.EXPAND) - so that it consumes the full size of the panel and so that + so that it consumes the full size of the panel and so that the NoteBook widget does the scrolling. For other uses, one will likely place a bunch of widgets and (other @@ -6138,13 +6178,13 @@ class G2DataWindow(wx.ScrolledWindow): #wxscroll.ScrolledPanel): Sizer.Fit(dataWindow) will do bad things, though a call to SubSizer.Fit(dataWindow.subpanel) could make sense. - Use of the unscrolled top sizer: - :data:`G2DataWindow.topBox` provides access to a Horizontal + Use of the unscrolled top sizer: + :data:`G2DataWindow.topBox` provides access to a Horizontal wx.BoxSizer, where GUI objects can be placed. The parent for these objects should be :data:`G2DataWindow.topPanel`. For the unscrolled - bottom region of the window, use :data:`G2DataWindow.bottomBox` + bottom region of the window, use :data:`G2DataWindow.bottomBox` and :data:`G2DataWindow.bottomPanel` as parent. - Sample code:: + Sample code:: topSizer = G2frame.dataWindow.topBox parent = G2frame.dataWindow.topPanel @@ -6152,34 +6192,34 @@ class G2DataWindow(wx.ScrolledWindow): #wxscroll.ScrolledPanel): topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) - Menus: The same core menu items are used in all menu bars (defined in - :meth:`PrefillDataMenu` and :meth:`PostfillDataMenu, but - different items may be added, depending on what data tree item and - in some cases (phases +?) window tab. Menu definitions are - performed in :meth:`_initMenus`. Menus that are needed at all + Menus: The same core menu items are used in all menu bars (defined in + :meth:`PrefillDataMenu` and :meth:`PostfillDataMenu, but + different items may be added, depending on what data tree item and + in some cases (phases +?) window tab. Menu definitions are + performed in :meth:`_initMenus`. Menus that are needed at all times in GSAS-II are created there with a call sich as:: self.ConstraintMenu = wx.MenuBar() - but to reduce the time needed to start GSAS-II initially, most menus - are created "on demand". This is done by defining a routine (named + but to reduce the time needed to start GSAS-II initially, most menus + are created "on demand". This is done by defining a routine (named here as :func:`_makemenu`) and the above definition is replaced with:: self.ConstraintMenu = _makemenu - The code that causes a menubar to be displayed (:func:`SetDataMenuBar`) - checks to see if the menubar has been already been created, if so it - is displayed, if not the function (the appropriate one of many - :func:`_makemenu` routines) is called. This creates and displays the - menu. + The code that causes a menubar to be displayed (:func:`SetDataMenuBar`) + checks to see if the menubar has been already been created, if so it + is displayed, if not the function (the appropriate one of many + :func:`_makemenu` routines) is called. This creates and displays the + menu. - Note, if there is a problem, a call like + Note, if there is a problem, a call like wx.CallAfter(G2frame.phaseDisplay.SendSizeEvent) might be needed. There are some calls to G2frame.dataWindow.SendSizeEvent() or G2frame.dataWindow.outer.SendSizeEvent() - that may be doing the same thing. + that may be doing the same thing. ''' def __init__(self,parent): @@ -6306,13 +6346,13 @@ def _onResize(event): def PrefillDataMenu(self,menu,empty=False): '''Create the "standard" part of data frame menus & add the dataWindow menu headings This menu duplicates the tree menu, but adds an extra help command for the current - data item and a separator. + data item and a separator. ''' self.datamenu = menu self.GetTopLevelParent().FillMainMenu(menu,addhelp=False) # add the data tree menu items to the main menu if not empty: menu.Append(wx.Menu(title=''),title='|') # add a separator - + def PostfillDataMenu(self,empty=False): '''Add the help menu to the menus associated with data tree items. ''' @@ -6326,11 +6366,11 @@ def PostfillDataMenu(self,empty=False): #### Menu definitions here def _initMenus(self): - '''define all GSAS-II data window menus. + '''define all GSAS-II data window menus. NB: argument order conforms to both classic & phoenix variants for wx. Do not use argument= for these as the argument names are different for classic & phoenix ''' - + #### GSAS-II Menu items # Main menu G2frame = self.GetTopLevelParent() @@ -6340,12 +6380,12 @@ def _initMenus(self): G2frame.SetMenuBar(G2frame.GSASIIMenu) # Controls - self.ControlsMenu = G2frame.GSASIIMenu + self.ControlsMenu = G2frame.GSASIIMenu # Notebook self.DataNotebookMenu = G2frame.GSASIIMenu # Comments self.DataCommentsMenu = G2frame.GSASIIMenu - + # Constraints G2G.Define_wxId('wxID_CONSTRAINTADD', 'wxID_EQUIVADD', 'wxID_HOLDADD', 'wxID_FUNCTADD', 'wxID_ADDRIDING', 'wxID_CONSPHASE', 'wxID_CONSHIST', 'wxID_CONSHAP', @@ -6389,14 +6429,14 @@ def _makemenu(): # routine to create menu when first used # Rigid bodies G2G.Define_wxId('wxID_RIGIDBODYADD', 'wxID_RIGIDBODYIMPORT', 'wxID_RESIDUETORSSEQ', 'wxID_VECTORBODYADD', 'wxID_RIGIDBODYSAVE','wxID_RIGIDBODYIMP','wxID_RESBODYSAV','wxID_RESBODYRD', - 'wxID_VECTORBODYIMP','wxID_VECTORBODYSAV','wxID_VECTORBODYRD','wxID_VECTORBODYEXTD','wxID_SPINBODYADD') + 'wxID_VECTORBODYIMP','wxID_VECTORBODYSAV','wxID_VECTORBODYRD','wxID_VECTORBODYEXTD','wxID_SPINBODYADD') def _makemenu(): # routine to create menu when first used self.RigidBodyMenu = wx.MenuBar() self.PrefillDataMenu(self.RigidBodyMenu) self.ResidueRBMenu = wx.Menu(title='') self.ResidueRBMenu.Append(G2G.wxID_RIGIDBODYIMPORT,'Import XYZ','Import rigid body XYZ from file') self.ResidueRBMenu.Append(G2G.wxID_RIGIDBODYIMP,'Extract from file','Extract rigid body from phase file') - self.ResidueRBMenu.Append(G2G.wxID_RIGIDBODYSAVE,'Save as PDB','Save rigid body to PDB file') + self.ResidueRBMenu.Append(G2G.wxID_RIGIDBODYSAVE,'Save as PDB','Save rigid body to PDB file') self.ResidueRBMenu.Append(G2G.wxID_RESIDUETORSSEQ,'Define torsion','Define torsion sequence') self.ResidueRBMenu.Append(G2G.wxID_RIGIDBODYADD,'Import residues','Import residue rigid bodies from macro file') self.ResidueRBMenu.Append(G2G.wxID_RESBODYSAV,'Save rigid body','Write a rigid body to a file') @@ -6458,15 +6498,15 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.RestraintMenu) self.RestraintMenu = _makemenu - + # Sequential results G2G.Define_wxId('wxID_RENAMESEQSEL', 'wxID_SAVESEQSEL', 'wxID_SAVESEQCSV', 'wxID_SAVESEQSELCSV', 'wxID_PLOTSEQSEL', 'wxID_ADDSEQVAR', 'wxID_DELSEQVAR', 'wxID_EDITSEQVAR', 'wxID_COPYPARFIT', 'wxID_AVESEQSEL','wxID_SELECTUSE', 'wxID_ADDPARFIT', 'wxID_DELPARFIT', 'wxID_EDITPARFIT', 'wxID_DOPARFIT', 'wxID_ADDSEQDIST', 'wxID_ADDSEQANGLE', 'wxID_ORGSEQINC',) G2G.Define_wxId('wxID_UPDATESEQSEL') G2G.Define_wxId('wxID_EDITSEQSELPHASE') - G2G.Define_wxId('wxID_XPORTSEQFCIF') - G2G.Define_wxId('wxID_XPORTSEQCSV') + G2G.Define_wxId('wxID_XPORTSEQFCIF') + G2G.Define_wxId('wxID_XPORTSEQCSV') def _makemenu(): # routine to create menu when first used self.SequentialMenu = wx.MenuBar() self.PrefillDataMenu(self.SequentialMenu) @@ -6488,10 +6528,10 @@ def _makemenu(): # routine to create menu when first used self.SequentialFile.Append(G2G.wxID_SAVESEQSELCSV,'Save selected as CSV', 'Save selected sequential refinement columns as a CSV spreadsheet file') self.SequentialFile.Append(G2G.wxID_AVESEQSEL,'Compute average', - 'Compute average for selected parameter') + 'Compute average for selected parameter') self.SequentialFile.Append(G2G.wxID_ORGSEQINC,'Hide columns...', 'Select columns to remove from displayed table') - self.SequentialFile.AppendSeparator() + self.SequentialFile.AppendSeparator() self.SequentialFile.Append(G2G.wxID_SAVESEQCSV,'Save all as CSV', 'Save all sequential refinement results as a CSV spreadsheet file') self.SequentialPvars = wx.Menu(title='') @@ -6552,16 +6592,16 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.SequentialMenu) self.SequentialMenu = _makemenu - + #Cluster analysis - what do I need here? self.ClusterAnalysisMenu = G2frame.GSASIIMenu - # self.ClusterAnalysisMenu = wx.MenuBar() + # self.ClusterAnalysisMenu = wx.MenuBar() # self.PrefillDataMenu(self.ClusterAnalysisMenu,empty=True) # self.PostfillDataMenu(empty=True) - + # PWDR & SASD G2G.Define_wxId('wxID_PWDANALYSIS','wxID_PWDCOPY','wxID_PLOTCTRLCOPY', - 'wxID_PWDHKLPLOT', 'wxID_PWD3DHKLPLOT','wxID_1DHKLSTICKPLOT') + 'wxID_PWDHKLPLOT', 'wxID_PWD3DHKLPLOT','wxID_1DHKLSTICKPLOT') G2G.Define_wxId('wxID_CHHKLLBLS') G2G.Define_wxId('wxID_CHPHPARTIAL') G2G.Define_wxId('wxID_PHPARTIALCSV') @@ -6587,7 +6627,7 @@ def _makemenu(): # routine to create menu when first used self.PWDRMenu = _makemenu # HKLF - many wxIDs defined in PWDR & SASD above - G2G.Define_wxId('wxID_3DALLHKLPLOT','wxID_MERGEHKL') + G2G.Define_wxId('wxID_3DALLHKLPLOT','wxID_MERGEHKL','wxID_FIXFSQSQDATA') def _makemenu(): # routine to create menu when first used self.HKLFMenu = wx.MenuBar() self.PrefillDataMenu(self.HKLFMenu) @@ -6598,6 +6638,7 @@ def _makemenu(): # routine to create menu when first used self.ErrorAnal.Append(G2G.wxID_1DHKLSTICKPLOT,'Plot 1D HKLs','Plot of HKLs from single crystal data in 1D') self.ErrorAnal.Append(G2G.wxID_PWD3DHKLPLOT,'Plot 3D HKLs','Plot HKLs from single crystal data in 3D') self.ErrorAnal.Append(G2G.wxID_3DALLHKLPLOT,'Plot all 3D HKLs','Plot HKLs from all single crystal data in 3D') + self.ErrorAnal.Append(G2G.wxID_FIXFSQSQDATA,'Fix (F^2)^2 data','Fix F^2 data imported as F') # self.ErrorAnal.Append(G2G.wxID_PWDCOPY,'Copy params','Copy of HKLF parameters') #unused self.PostfillDataMenu() SetDataMenuBar(G2frame,self.HKLFMenu) @@ -6619,7 +6660,7 @@ def _makemenu(): # routine to create menu when first used self.LimitEdit.Append(G2G.wxID_SETTOPLIMIT,'Set upper limit', 'Click on a data point to set the upper limit location') self.LimitEdit.Append(G2G.wxID_ADDEXCLREGION,'Add excluded region', - 'Add excluded region - select a point on plot; drag later to adjust') + 'Add excluded region - select a point on plot; drag later to adjust') G2frame.CancelSetLimitsMode = self.LimitEdit.Append( G2G.wxID_STOPSETLIMIT,'Cancel set', 'Clears a previous Set limits/add excluded region') @@ -6629,7 +6670,7 @@ def _makemenu(): # routine to create menu when first used self.LimitMenu = _makemenu # PWDR / Background - G2G.Define_wxId('wxID_BACKCOPY', 'wxID_BACKFLAGCOPY','wxID_MAKEBACKRDF', + G2G.Define_wxId('wxID_BACKCOPY', 'wxID_BACKFLAGCOPY','wxID_MAKEBACKRDF', 'wxID_RESCALEALL','wxID_BACKPEAKSMOVE','wxID_BACKSAVE','wxID_BACKLOAD') def _makemenu(): # routine to create menu when first used self.BackMenu = wx.MenuBar() @@ -6648,20 +6689,20 @@ def _makemenu(): # routine to create menu when first used self.wxID_BackPts = {} self.wxID_BackPts['Add'] = wx.NewId() # N.B. not using wxID_ global as for other menu items self.BackFixed.AppendRadioItem(self.wxID_BackPts['Add'],'Add','Add fixed background points with mouse clicks') - self.wxID_BackPts['Move'] = wx.NewId() + self.wxID_BackPts['Move'] = wx.NewId() item = self.BackFixed.AppendRadioItem(self.wxID_BackPts['Move'],'Move','Move selected fixed background points with mouse drags') item.Check(True) self.wxID_BackPts['Del'] = wx.NewId() self.BackFixed.AppendRadioItem(self.wxID_BackPts['Del'],'Delete','Delete fixed background points with mouse clicks') - self.wxID_BackPts['Clear'] = wx.NewId() + self.wxID_BackPts['Clear'] = wx.NewId() self.BackFixed.Append(self.wxID_BackPts['Clear'],'Clear','Clear fixed background points') - self.wxID_BackPts['Fit'] = wx.NewId() + self.wxID_BackPts['Fit'] = wx.NewId() self.BackFixed.Append(self.wxID_BackPts['Fit'],'Fit background', 'Fit background function to fixed background points') self.PostfillDataMenu() SetDataMenuBar(G2frame,self.BackMenu) self.BackMenu = _makemenu - + # PWDR / Instrument Parameters G2G.Define_wxId('wxID_INSTPRMRESET','wxID_INSTCOPY','wxID_INSTFLAGCOPY','wxID_INSTLOAD', 'wxID_INSTSAVE', 'wxID_INST1VAL', 'wxID_INSTCALIB', 'wxID_INSTSAVEALL', @@ -6683,10 +6724,10 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.InstMenu) self.InstMenu = _makemenu - + # PWDR / Sample Parameters G2G.Define_wxId('wxID_SAMPLECOPY', 'wxID_SAMPLECOPYSOME', 'wxID_SAMPLEFLAGCOPY','wxID_SAMPLESAVE', - 'wxID_SAMPLELOAD', 'wxID_SETSCALE', 'wxID_SAMPLE1VAL', 'wxID_ALLSAMPLELOAD',) + 'wxID_SAMPLELOAD', 'wxID_SETSCALE', 'wxID_SAMPLE1VAL', 'wxID_ALLSAMPLELOAD',) def _makemenu(): # routine to create menu when first used self.SampleMenu = wx.MenuBar() self.PrefillDataMenu(self.SampleMenu) @@ -6707,7 +6748,7 @@ def _makemenu(): # routine to create menu when first used self.SampleMenu = _makemenu # PWDR / Peak List - G2G.Define_wxId('wxID_UNDO', 'wxID_LSQPEAKFIT', 'wxID_LSQONECYCLE', 'wxID_RESETSIGGAM', + G2G.Define_wxId('wxID_UNDO', 'wxID_LSQPEAKFIT', 'wxID_LSQONECYCLE', 'wxID_RESETSIGGAM', 'wxID_CLEARPEAKS', 'wxID_AUTOSEARCH','wxID_PEAKSCOPY', 'wxID_SEQPEAKFIT','wxID_PEAKLOAD','wxID_PEAKSAVE') G2G.Define_wxId('wxID_DELPEAKS') G2G.Define_wxId('wxID_SETUNVARIEDWIDTHS') @@ -6727,7 +6768,7 @@ def _makemenu(): # routine to create menu when first used self.PeakCopy = self.PeakEdit.Append(G2G.wxID_PEAKSCOPY,'Peak copy','Copy peaks to other histograms') self.PeakEdit.Append(G2G.wxID_PEAKLOAD,'Load peaks...','Load peak list from file') self.PeakEdit.Append(G2G.wxID_PEAKSAVE,'Save peaks...','Save peak list to file') - self.SeqPeakFit = self.PeakEdit.Append(G2G.wxID_SEQPEAKFIT,'Seq PeakFit', + self.SeqPeakFit = self.PeakEdit.Append(G2G.wxID_SEQPEAKFIT,'Seq PeakFit', 'Sequential Peak fitting for all histograms' ) self.PeakEdit.Append(G2G.wxID_DELPEAKS,'Delete peaks','Delete selected peaks from the list' ) self.PeakEdit.Append(G2G.wxID_CLEARPEAKS,'Clear peaks','Clear the peak list' ) @@ -6768,7 +6809,7 @@ def _makemenu(): # routine to create menu when first used #self.RefineCell2.Enable(False) SetDataMenuBar(G2frame,self.IndPeaksMenu) self.IndPeaksMenu = _makemenu - + # PWDR / Unit Cells List G2G.Define_wxId('wxID_INDEXPEAKS', 'wxID_REFINECELL', 'wxID_COPYCELL', 'wxID_MAKENEWPHASE', 'wxID_EXPORTCELLS','wxID_LOADCELL','wxID_IMPORTCELL','wxID_TRANSFORMCELL', @@ -6791,13 +6832,14 @@ def _makemenu(): # routine to create menu when first used 'If disabled, do Load Phase first') self.RunSubGroupsMag = self.IndexEdit.Append(G2G.wxID_RUNSUBMAG,'Run k-SUBGROUPMAG', 'If disabled, do Load Phase first') - self.CopyCell = self.IndexEdit.Append(G2G.wxID_COPYCELL,'Copy Cell', + self.CopyCell = self.IndexEdit.Append(G2G.wxID_COPYCELL,'Copy Cell', 'Copy selected unit cell from indexing to cell refinement fields') - self.LoadCell = self.IndexEdit.Append(G2G.wxID_LOADCELL,'Load Phase', + self.LoadCell = self.IndexEdit.Append(G2G.wxID_LOADCELL,'Load Phase', 'Load unit cell from a phase tree entry') - self.ImportCell = self.IndexEdit.Append(G2G.wxID_IMPORTCELL,'Import Cell', - 'Import unit cell from file') - self.TransposeCell = self.IndexEdit.Append(G2G.wxID_TRANSFORMCELL,'Transform Cell', + # TODO: broken. Needs to be a cascade menu as per ReImportMenuId? + # self.ImportCell = self.IndexEdit.Append(G2G.wxID_IMPORTCELL,'Import Cell', + # 'Import unit cell from file') + self.TransposeCell = self.IndexEdit.Append(G2G.wxID_TRANSFORMCELL,'Transform Cell', 'Transform unit cell') self.RefineCell = self.IndexEdit.Append(G2G.wxID_REFINECELL,'Refine Cell', 'Refine unit cell parameters from indexed peaks') @@ -6819,7 +6861,7 @@ def _makemenu(): # routine to create menu when first used self.MakeNewPhase.Enable(False) SetDataMenuBar(G2frame,self.IndexMenu) self.IndexMenu = _makemenu - + # PWDR / Reflection Lists G2G.Define_wxId('wxID_SELECTPHASE','wxID_SHOWHIDEEXTINCT','wxID_WILSONSTAT','wxID_CSVFROMTABLE' ) #some wxIDs defined above in PWDR & SASD def _makemenu(): # routine to create menu when first used @@ -6837,7 +6879,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.ReflMenu) self.ReflMenu = _makemenu - + # SASD & REFD / Limits G2G.Define_wxId('wxID_SASDLIMITCOPY', ) def _makemenu(): # routine to create menu when first used @@ -6849,7 +6891,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.SASDLimitMenu) self.SASDLimitMenu = _makemenu - + # SASD / Instrument Parameters G2G.Define_wxId('wxID_SASDINSTCOPY',) def _makemenu(): # routine to create menu when first used @@ -6864,7 +6906,7 @@ def _makemenu(): # routine to create menu when first used #SASD & REFL/ Substance editor G2G.Define_wxId('wxID_LOADSUBSTANCE','wxID_RELOADSUBSTANCES','wxID_ADDSUBSTANCE','wxID_COPYSUBSTANCE', - 'wxID_DELETESUBSTANCE','wxID_ELEMENTADD', 'wxID_ELEMENTDELETE',) + 'wxID_DELETESUBSTANCE','wxID_ELEMENTADD', 'wxID_ELEMENTDELETE',) def _makemenu(): # routine to create menu when first used self.SubstanceMenu = wx.MenuBar() self.PrefillDataMenu(self.SubstanceMenu) @@ -6874,16 +6916,16 @@ def _makemenu(): # routine to create menu when first used self.SubstanceEdit.Append(G2G.wxID_RELOADSUBSTANCES,'Reload substances','Reload all substances from file') self.SubstanceEdit.Append(G2G.wxID_ADDSUBSTANCE,'Add substance','Add new substance to list') self.SubstanceEdit.Append(G2G.wxID_COPYSUBSTANCE,'Copy substances','Copy substances') - self.SubstanceEdit.Append(G2G.wxID_DELETESUBSTANCE,'Delete substance','Delete substance from list') + self.SubstanceEdit.Append(G2G.wxID_DELETESUBSTANCE,'Delete substance','Delete substance from list') self.SubstanceEdit.Append(G2G.wxID_ELEMENTADD,'Add elements','Add elements to substance') self.SubstanceEdit.Append(G2G.wxID_ELEMENTDELETE,'Delete elements','Delete elements from substance') self.PostfillDataMenu() SetDataMenuBar(G2frame,self.SubstanceMenu) self.SubstanceMenu = _makemenu - + # SASD/ Models - G2G.Define_wxId('wxID_MODELCOPY', 'wxID_MODELFIT', 'wxID_MODELADD','wxID_MODELUNDO', - 'wxID_MODELFITALL', 'wxID_MODELCOPYFLAGS','wxID_MODELPLOT',) + G2G.Define_wxId('wxID_MODELCOPY', 'wxID_MODELFIT', 'wxID_MODELADD','wxID_MODELUNDO', + 'wxID_MODELFITALL', 'wxID_MODELCOPYFLAGS','wxID_MODELPLOT',) def _makemenu(): # routine to create menu when first used self.ModelMenu = wx.MenuBar() self.PrefillDataMenu(self.ModelMenu) @@ -6892,7 +6934,7 @@ def _makemenu(): # routine to create menu when first used self.ModelEdit.Append(G2G.wxID_MODELADD,'Add','Add new term to model') self.ModelEdit.Append(G2G.wxID_MODELFIT,'Fit','Fit model parameters to data') self.SasdUndo = self.ModelEdit.Append(G2G.wxID_MODELUNDO,'Undo','Undo model fit') - self.SasdUndo.Enable(False) + self.SasdUndo.Enable(False) self.SasSeqFit = self.ModelEdit.Append(G2G.wxID_MODELFITALL,'Sequential fit','Sequential fit of model parameters to all SASD data') self.SasSeqFit.Enable(False) self.ModelEdit.Append(G2G.wxID_MODELCOPY,'Copy','Copy model parameters to other histograms') @@ -6900,7 +6942,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.ModelMenu) self.ModelMenu = _makemenu - + # REFD/ Models - wxIDs as for SASD/Models def _makemenu(): # routine to create menu when first used self.REFDModelMenu = wx.MenuBar() @@ -6909,7 +6951,7 @@ def _makemenu(): # routine to create menu when first used self.REFDModelMenu.Append(menu=self.REFDModelEdit, title='Models') self.REFDModelEdit.Append(G2G.wxID_MODELFIT,'Fit','Fit model parameters to data') self.REFDUndo = self.REFDModelEdit.Append(G2G.wxID_MODELUNDO,'Undo','Undo model fit') - self.REFDUndo.Enable(False) + self.REFDUndo.Enable(False) self.REFDModelEdit.Append(G2G.wxID_MODELFITALL,'Sequential fit','Sequential fit of model parameters to all REFD data') self.REFDModelEdit.Append(G2G.wxID_MODELCOPY,'Copy','Copy model parameters to other histograms') self.REFDModelEdit.Append(G2G.wxID_MODELPLOT,'Plot','Plot model SDL for selected histograms') @@ -6918,7 +6960,7 @@ def _makemenu(): # routine to create menu when first used self.REFDModelMenu = _makemenu # IMG / Image Controls - G2G.Define_wxId('wxID_IMCALIBRATE', 'wxID_IMRECALIBRATE', 'wxID_IMINTEGRATE', 'wxID_IMCLEARCALIB', 'wxID_IMRECALIBALL', + G2G.Define_wxId('wxID_IMCALIBRATE', 'wxID_IMRECALIBRATE', 'wxID_IMINTEGRATE', 'wxID_IMCLEARCALIB', 'wxID_IMRECALIBALL', 'wxID_IMCOPYCONTROLS', 'wxID_INTEGRATEALL', 'wxID_IMSAVECONTROLS', 'wxID_IMLOADCONTROLS', 'wxID_IMAUTOINTEG', 'wxID_IMCOPYSELECTED', 'wxID_SAVESELECTEDCONTROLS', 'wxID_IMXFERCONTROLS', 'wxID_IMRESETDIST', 'wxID_CALCRINGS', 'wxID_LOADELECTEDCONTROLS','wxID_IMDISTRECALIB', 'wxID_IMINTEGPDFTOOL','wxID_IMMULTGAINMAP') @@ -6956,7 +6998,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.ImageMenu) self.ImageMenu = _makemenu - + # IMG / Masks G2G.Define_wxId('wxID_MASKCOPY', 'wxID_MASKSAVE', 'wxID_MASKLOAD', 'wxID_NEWMASKSPOT', 'wxID_NEWMASKARC', 'wxID_NEWMASKRING', 'wxID_NEWMASKFRAME', 'wxID_NEWMASKPOLY','wxID_NEWMASKXLINE','wxID_NEWMASKYLINE','wxID_MASKLOADNOT', @@ -6986,10 +7028,10 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.MaskMenu) self.MaskMenu = _makemenu - + # IMG / Stress/Strain G2G.Define_wxId('wxID_STRSTACOPY', 'wxID_STRSTAFIT', 'wxID_STRSTASAVE', 'wxID_STRSTALOAD', 'wxID_STRSTSAMPLE', - 'wxID_APPENDDZERO', 'wxID_STRSTAALLFIT', 'wxID_UPDATEDZERO', 'wxID_STRSTAPLOT', 'wxID_STRRINGSAVE',) + 'wxID_APPENDDZERO', 'wxID_STRSTAALLFIT', 'wxID_UPDATEDZERO', 'wxID_STRSTAPLOT', 'wxID_STRRINGSAVE',) def _makemenu(): # routine to create menu when first used self.StrStaMenu = wx.MenuBar() self.PrefillDataMenu(self.StrStaMenu) @@ -6999,7 +7041,7 @@ def _makemenu(): # routine to create menu when first used self.StrStaEdit.Append(G2G.wxID_STRSTAFIT,'Fit stress/strain','Fit stress/strain data') self.StrStaEdit.Append(G2G.wxID_STRSTAPLOT,'Plot intensity distribution','Plot intensity distribution') self.StrStaEdit.Append(G2G.wxID_STRRINGSAVE,'Save intensity distribution','Save intensity distribution') - self.StrStaEdit.Append(G2G.wxID_UPDATEDZERO,'Update d-zero','Update d-zero from ave d-zero') + self.StrStaEdit.Append(G2G.wxID_UPDATEDZERO,'Update d-zero','Update d-zero from ave d-zero') self.StrStaEdit.Append(G2G.wxID_STRSTAALLFIT,'All image fit','Fit stress/strain data for all images') self.StrStaEdit.Append(G2G.wxID_STRSTACOPY,'Copy stress/strain','Copy stress/strain data to other images') self.StrStaEdit.Append(G2G.wxID_STRSTASAVE,'Save stress/strain','Save stress/strain data to file') @@ -7009,7 +7051,7 @@ def _makemenu(): # routine to create menu when first used SetDataMenuBar(G2frame,self.StrStaMenu) self.StrStaMenu = _makemenu - + # PDF / PDF Controls G2G.Define_wxId('wxID_PDFCOPYCONTROLS', 'wxID_PDFSAVECONTROLS', 'wxID_PDFLOADCONTROLS', 'wxID_PDFCOMPUTE', 'wxID_PDFCOMPUTEALL', 'wxID_PDFADDELEMENT', 'wxID_PDFDELELEMENT',) @@ -7028,7 +7070,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() SetDataMenuBar(G2frame,self.PDFMenu) self.PDFMenu = _makemenu - + # PDF / PDF Peaks G2G.Define_wxId('wxID_PDFPKSFIT','wxID_PDFPKSFITALL', 'wxID_PDFCOPYPEAKS', 'wxID_CLEARPDFPEAKS',) def _makemenu(): # routine to create menu when first used @@ -7039,17 +7081,17 @@ def _makemenu(): # routine to create menu when first used self.PDFPksEdit.Append(G2G.wxID_PDFPKSFIT,'PDF peak fit','Fit PDF peaks') self.PDFPksEdit.Append(G2G.wxID_PDFPKSFITALL,'Seq PDF peak fit','Sequential Peak fitting for all PDFs') self.PDFPksEdit.Append(G2G.wxID_PDFCOPYPEAKS,'Copy peaks','Copy PDF peaks') - self.PDFPksEdit.Append(G2G.wxID_CLEARPDFPEAKS,'Clear peaks','Clear PDF peaks') + self.PDFPksEdit.Append(G2G.wxID_CLEARPDFPEAKS,'Clear peaks','Clear PDF peaks') self.PostfillDataMenu() SetDataMenuBar(G2frame,self.PDFPksMenu) self.PDFPksMenu = _makemenu # Phase / General tab - G2G.Define_wxId('wxID_FOURCALC', 'wxID_FOURSEARCH', 'wxID_FOURCLEAR','wxID_CHARGEFLIP','wxID_VALIDPROTEIN', + G2G.Define_wxId('wxID_FOURCALC', 'wxID_FOURSEARCH', 'wxID_FOURCLEAR','wxID_CHARGEFLIP','wxID_VALIDPROTEIN', 'wxID_MULTIMCSA','wxID_SINGLEMCSA', 'wxID_4DCHARGEFLIP', 'wxID_TRANSFORMSTRUCTURE','wxID_USEBILBAOMAG', 'wxID_COMPARESTRUCTURE','wxID_COMPARECELLS','wxID_USEBILBAOSUB') def _makemenu(): # routine to create menu when first used - # note that the phase menus are all interconnected, so they all get created at once. + # note that the phase menus are all interconnected, so they all get created at once. self.DataGeneral = wx.MenuBar() self.PrefillDataMenu(self.DataGeneral) self.DataGeneral.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7059,7 +7101,7 @@ def _makemenu(): # routine to create menu when first used self.GeneralCalc.Append(G2G.wxID_FOURSEARCH,'Search map','Search Fourier map') self.GeneralCalc.Append(G2G.wxID_CHARGEFLIP,'Charge flipping','Run charge flipping') self.GeneralCalc.Append(G2G.wxID_4DCHARGEFLIP,'4D Charge flipping','Run 4D charge flipping') - self.GeneralCalc.Enable(G2G.wxID_4DCHARGEFLIP,False) + self.GeneralCalc.Enable(G2G.wxID_4DCHARGEFLIP,False) self.GeneralCalc.Append(G2G.wxID_FOURCLEAR,'Clear map','Clear map') self.GeneralCalc.Append(G2G.wxID_SINGLEMCSA,'MC/SA','Run Monte Carlo - Simulated Annealing') self.GeneralCalc.Append(G2G.wxID_MULTIMCSA,'Multi MC/SA','Run Monte Carlo - Simulated Annealing on multiprocessors') @@ -7068,7 +7110,7 @@ def _makemenu(): # routine to create menu when first used self.GeneralCalc.Append(G2G.wxID_TRANSFORMSTD,'Std setting','Create a copy of this phase transformed into the standard setting') self.GeneralCalc.Append(G2G.wxID_COMPARECELLS,'Compare Cells','Compare Unit Cells using NIST*LATTICE') self.GeneralCalc.Append(G2G.wxID_COMPARESTRUCTURE,'Compare polyhedra','Compare polyhedra to ideal octahedra/tetrahedra') - self.GeneralCalc.Enable(G2G.wxID_COMPARESTRUCTURE,False) + self.GeneralCalc.Enable(G2G.wxID_COMPARESTRUCTURE,False) self.GeneralCalc.Append(G2G.wxID_USEBILBAOMAG,'Select magnetic/subgroup phase','If disabled, make in PWDR/Unit Cells') self.GeneralCalc.Append(G2G.wxID_USEBILBAOSUB,'Make subgroup project file(s)','Requires subcell search in PWDR/Unit Cells') G2G.Define_wxId('wxID_SUPERSRCH') @@ -7076,9 +7118,20 @@ def _makemenu(): # routine to create menu when first used G2G.Define_wxId('wxID_ISOSRCH') self.GeneralCalc.Append(G2G.wxID_ISOSRCH,'ISOCIF Supergroup search','Search for settings of this phase in higher symmetry') self.GeneralCalc.Append(G2G.wxID_VALIDPROTEIN,'Protein quality','Protein quality analysis') + + submenu = wx.Menu() + self.GeneralCalc.AppendSubMenu(submenu,'Replace phase','Replace phase from file') + # setup a cascade menu for the formats that have been defined + self.ReplaceMenuId = {} # points to readers for each menu entry + for reader in self.parent.GetTopLevelParent().ImportPhaseReaderlist: + item = submenu.Append(wx.ID_ANY,'Replace phase from '+reader.formatName+' file',reader.longFormatName) + self.ReplaceMenuId[item.GetId()] = reader + item = submenu.Append(wx.ID_ANY,'guess format from file','Replace phase, try to determine format from file') + self.ReplaceMenuId[item.GetId()] = None # try all readers + self.PostfillDataMenu() #self.DataGeneral = _makemenu - + # Phase / Data tab or Hist/Phase menu (used in one place or other) G2G.Define_wxId('wxID_DATACOPY', 'wxID_DATACOPYFLAGS', 'wxID_DATASELCOPY', 'wxID_DATAUSE', 'wxID_PWDRADD', 'wxID_HKLFADD','wxID_DATADELETE', @@ -7106,7 +7159,7 @@ def _makemenu(): # routine to create menu when first used 'wxID_ATOMSMODIFY', 'wxID_ATOMSTRANSFORM', 'wxID_ATOMSVIEWADD', 'wxID_ATOMVIEWINSERT', 'wxID_RELOADDRAWATOMS', 'wxID_ATOMSDISAGL', 'wxID_ATOMMOVE', 'wxID_MAKEMOLECULE', 'wxID_ATOMSPDISAGL', 'wxID_ISODISP', 'wxID_ADDHATOM', 'wxID_UPDATEHATOM', - 'wxID_ATOMSROTATE', 'wxID_ATOMSDENSITY','wxID_ATOMSBNDANGLHIST', + 'wxID_ATOMSROTATE', 'wxID_ATOMSDENSITY','wxID_ATOMSBNDANGLHIST', 'wxID_ATOMSSETALL', 'wxID_ATOMSSETSEL','wxID_ATOMFRACSPLIT','wxID_COLLECTATOMS', 'wxID_ATOMSSETVP', 'wxID_ATOMSSETLST') self.AtomsMenu = wx.MenuBar() @@ -7154,7 +7207,7 @@ def _makemenu(): # routine to create menu when first used 'Compute values of ISODISTORT modes from atom parameters') self.PostfillDataMenu() - # Phase / Imcommensurate "waves" tab + # Phase / Imcommensurate "waves" tab G2G.Define_wxId('wxID_WAVEVARY',) self.WavesData = wx.MenuBar() self.PrefillDataMenu(self.WavesData) @@ -7165,7 +7218,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() #Phase / Dysnomia (Maximum Entropy Method) tab - G2G.Define_wxId('wxID_LOADDYSNOMIA', 'wxID_SAVEDYSNOMIA', 'wxID_RUNDYSNOMIA', ) + G2G.Define_wxId('wxID_LOADDYSNOMIA', 'wxID_SAVEDYSNOMIA', 'wxID_RUNDYSNOMIA', ) self.MEMMenu = wx.MenuBar() self.PrefillDataMenu(self.MEMMenu) self.MEMMenu.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7178,7 +7231,7 @@ def _makemenu(): # routine to create menu when first used #Phase / fullrmc & RMCprofile (Reverse Monte Carlo method) tab G2G.Define_wxId('wxID_SETUPRMC','wxID_RUNRMC','wxID_VIEWRMC','wxID_STOPRMC', - 'wxID_ATOMSRMC', 'wxID_SUPERRMC') + 'wxID_ATOMSRMC', 'wxID_SUPERRMC') self.FRMCMenu = wx.MenuBar() self.PrefillDataMenu(self.FRMCMenu) self.FRMCMenu.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7206,8 +7259,8 @@ def _makemenu(): # routine to create menu when first used self.ISODDataEdit.Append(G2G.wxID_SHOWISOMODES,'Show relationships','For ISODISTORT') self.PostfillDataMenu() - # Phase / Layer tab - G2G.Define_wxId('wxID_LOADDIFFAX', 'wxID_LAYERSIMULATE', 'wxID_SEQUENCESIMULATE', 'wxID_LAYERSFIT', 'wxID_COPYPHASE',) + # Phase / Layer tab + G2G.Define_wxId('wxID_LOADDIFFAX', 'wxID_LAYERSIMULATE', 'wxID_SEQUENCESIMULATE', 'wxID_LAYERSFIT', 'wxID_COPYPHASE',) self.LayerData = wx.MenuBar() self.PrefillDataMenu(self.LayerData) self.LayerData.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7237,14 +7290,14 @@ def _makemenu(): # routine to create menu when first used self.DeformationEdit.Append(G2G.wxID_DEFORMDISTSET,'Set bond parms','Set bond selection parameters') self.PostfillDataMenu() - # Phase / Draw Atoms tab + # Phase / Draw Atoms tab G2G.Define_wxId('wxID_DRAWATOMSTYLE', 'wxID_DRAWATOMLABEL', 'wxID_DRAWATOMCOLOR', 'wxID_DRAWATOMRESETCOLOR', 'wxID_DRAWVIEWPOINT', 'wxID_DRAWTRANSFORM', 'wxID_DRAWDELETE', 'wxID_DRAWFILLCELL', 'wxID_DRAWADDEQUIV', 'wxID_DRAWFILLCOORD', 'wxID_DRAWDISAGLTOR', 'wxID_DRAWPLANE', 'wxID_DRAWDISTVP', 'wxID_DRAWADDSPHERE', 'wxID_DRWAEDITRADII', - 'wxID_DRAWSETSEL', 'wxID_DRAWLOADSEL', + 'wxID_DRAWSETSEL', 'wxID_DRAWLOADSEL', ) - G2G.Define_wxId('wxID_DRAWRESTRBOND', 'wxID_DRAWRESTRANGLE', 'wxID_DRAWRESTRPLANE', 'wxID_DRAWRESTRCHIRAL',) + G2G.Define_wxId('wxID_DRAWRESTRBOND', 'wxID_DRAWRESTRANGLE', 'wxID_DRAWRESTRPLANE', 'wxID_DRAWRESTRCHIRAL',) self.DrawAtomsMenu = wx.MenuBar() self.PrefillDataMenu(self.DrawAtomsMenu) self.DrawAtomsMenu.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7266,7 +7319,7 @@ def _makemenu(): # routine to create menu when first used self.DrawAtomEdit.Append(G2G.wxID_DRAWADDEQUIV,'Add atoms','Add symmetry & cell equivalents to drawing set from selected atoms') self.DrawAtomEdit.Append(G2G.wxID_DRAWADDSPHERE,'Add sphere of atoms','Add atoms within sphere of enclosure') self.DrawAtomEdit.Append(G2G.wxID_DRAWTRANSFORM,'Transform draw atoms','Transform selected atoms by symmetry & cell translations') - self.DrawAtomEdit.Append(G2G.wxID_DRAWFILLCOORD,'Fill CN-sphere','Fill coordination sphere for selected atoms') + self.DrawAtomEdit.Append(G2G.wxID_DRAWFILLCOORD,'Fill CN-sphere','Fill coordination sphere for selected atoms') self.DrawAtomEdit.Append(G2G.wxID_DRAWFILLCELL,'Fill unit cell','Fill unit cell with selected atoms') G2G.Define_wxId('wxID_DRAWADDMOLECULE') self.DrawAtomEdit.Append(G2G.wxID_DRAWADDMOLECULE,'Complete molecule','Cyclicly add atoms bonded to selected atoms') @@ -7279,10 +7332,10 @@ def _makemenu(): # routine to create menu when first used G2G.Define_wxId('wxID_DRAWRANDOM') self.DrawAtomEdit.Append(G2G.wxID_DRAWRANDOM,'Randomized action','Perform an action in randomized order') - self.DrawAtomCompute.Append(G2G.wxID_DRAWDISTVP,'View pt. dist.','Compute distance of selected atoms from view point') + self.DrawAtomCompute.Append(G2G.wxID_DRAWDISTVP,'View pt. dist.','Compute distance of selected atoms from view point') self.DrawAtomCompute.Append(G2G.wxID_DRAWDISAGLTOR,'Dist. Ang. Tors.', - 'Compute distance, angle or torsion for 2-4 selected atoms') - self.DrawAtomCompute.Append(G2G.wxID_DRAWPLANE,'Best plane','Compute best plane for 4+ selected atoms') + 'Compute distance, angle or torsion for 2-4 selected atoms') + self.DrawAtomCompute.Append(G2G.wxID_DRAWPLANE,'Best plane','Compute best plane for 4+ selected atoms') self.DrawAtomRestraint.Append(G2G.wxID_DRAWRESTRBOND,'Add bond restraint','Add bond restraint for selected atoms (2)') self.DrawAtomRestraint.Append(G2G.wxID_DRAWRESTRANGLE,'Add angle restraint', 'Add angle restraint for selected atoms (3: one end 1st)') @@ -7295,7 +7348,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() # Phase / MCSA tab - G2G.Define_wxId('wxID_ADDMCSAATOM', 'wxID_ADDMCSARB', 'wxID_CLEARMCSARB', 'wxID_MOVEMCSA', 'wxID_MCSACLEARRESULTS',) + G2G.Define_wxId('wxID_ADDMCSAATOM', 'wxID_ADDMCSARB', 'wxID_CLEARMCSARB', 'wxID_MOVEMCSA', 'wxID_MCSACLEARRESULTS',) self.MCSAMenu = wx.MenuBar() self.PrefillDataMenu(self.MCSAMenu) self.MCSAMenu.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7309,7 +7362,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() # Phase / Texture tab - G2G.Define_wxId('wxID_CLEARTEXTURE', 'wxID_REFINETEXTURE',) + G2G.Define_wxId('wxID_CLEARTEXTURE', 'wxID_REFINETEXTURE',) self.TextureMenu = wx.MenuBar() self.PrefillDataMenu(self.TextureMenu) self.TextureMenu.Append(menu=wx.Menu(title=''),title='Select tab') @@ -7319,7 +7372,7 @@ def _makemenu(): # routine to create menu when first used self.PostfillDataMenu() # Phase / Pawley tab - G2G.Define_wxId('wxID_PAWLEYLOAD', 'wxID_PAWLEYESTIMATE', 'wxID_PAWLEYUPDATE', 'wxID_PAWLEYSELALL', + G2G.Define_wxId('wxID_PAWLEYLOAD', 'wxID_PAWLEYESTIMATE', 'wxID_PAWLEYUPDATE', 'wxID_PAWLEYSELALL', 'wxID_PAWLEYSELNONE','wxID_PAWLEYSELTOGGLE', 'wxID_PAWLEYSET',) self.PawleyMenu = wx.MenuBar() self.PrefillDataMenu(self.PawleyMenu) @@ -7345,8 +7398,8 @@ def _makemenu(): # routine to create menu when first used self.MapPeaksMenu.Append(menu=self.MapPeaksEdit, title='Map peaks') self.MapPeaksEdit.Append(G2G.wxID_PEAKSMOVE,'Move peaks','Move selected peaks to atom list') self.MapPeaksEdit.Append(G2G.wxID_PEAKSVIEWPT,'View point','View point is 1st peak selected') - self.MapPeaksEdit.Append(G2G.wxID_PEAKSDISTVP,'View pt. dist.','Compute distance of selected peaks from view point') - self.MapPeaksEdit.Append(G2G.wxID_SHOWBONDS,'Hide bonds','Hide or show bonds between peak positions') + self.MapPeaksEdit.Append(G2G.wxID_PEAKSDISTVP,'View pt. dist.','Compute distance of selected peaks from view point') + self.MapPeaksEdit.Append(G2G.wxID_SHOWBONDS,'Hide bonds','Hide or show bonds between peak positions') self.MapPeaksEdit.Append(G2G.wxID_PEAKSDA,'Calc dist/ang','Calculate distance or angle for selection') self.MapPeaksEdit.Append(G2G.wxID_FINDEQVPEAKS,'Equivalent peaks','Find equivalent peaks') self.MapPeaksEdit.Append(G2G.wxID_INVERTPEAKS,'Invert peak positions','Inverts map & peak positions') @@ -7383,7 +7436,7 @@ def _makemenu(): # routine to create menu when first used SetDataMenuBar(G2frame,self.DataGeneral) self.DataGeneral = _makemenu # end of GSAS-II menu definitions - + #### Notebook Tree Item editor ############################################## NBinfo = {} def UpdateNotebook(G2frame,data): @@ -7408,7 +7461,8 @@ def OnAddNotebook(event): def OnSaveNotebook(event): filename = os.path.splitext(G2frame.GSASprojectfile)[0]+'_notebook.txt' filename = os.path.join(G2frame.dirname,filename) - open(filename,'w').write(textBox.GetText()) + with open(filename,'w') as fp: + fp.write(textBox.GetText()) print(f'Notebook contents written into {filename}') def onPlotNotebook(): 'Locate R values from the Notebook and plot them' @@ -7469,7 +7523,7 @@ def onPlotNotebook(): botSizer = G2frame.dataWindow.bottomBox botSizer.Clear(True) - parent = G2frame.dataWindow.bottomPanel + parent = G2frame.dataWindow.bottomPanel botSizer.Add((20,-1)) controls['Notebook']['order'] = controls['Notebook'].get('order',True) botSizer.Add(wx.StaticText(parent,wx.ID_ANY,' order: '),0,WACV) @@ -7480,7 +7534,7 @@ def onPlotNotebook(): 'filterSel',[False]+(len(filterLbls)-1)*[True]) NBinfo['plotLbl'] = NBinfo.get('plotLbl',{'none':True,'Rw':False,'GOF':False}) for i in range(len(filterPrefix)-len(controls['Notebook']['filterSel'])): - controls['Notebook']['filterSel'] += [True] # pad list if needed + controls['Notebook']['filterSel'] += [True] # pad list if needed botSizer.Add((20,-1)) fBtn = G2G.popupSelectorButton(parent,'Set filters', filterLbls,controls['Notebook']['filterSel'], @@ -7506,9 +7560,9 @@ def onPlotNotebook(): ls = l.split() if len(ls) < 1: # empty line continue - elif '[' not in ls[0]: + elif '[' not in ls[0]: pos = 0 # old untagged NB entry - elif '[TS]' in ls[0]: + elif '[TS]' in ls[0]: pos = 0 # Move time stamp to top order.insert(pos,i) else: @@ -7526,7 +7580,7 @@ def onPlotNotebook(): show = False if controls['Notebook']['filterSel'][0] or prefix == '': show = True - elif prefix in selLbl: + elif prefix in selLbl: show = True if show: if prefix == 'TS': @@ -7551,7 +7605,7 @@ def onPlotNotebook(): G2frame.dataWindow.SetSizer(bigSizer) G2frame.dataWindow.SetDataSize() G2frame.SendSizeEvent() - + #### Comments ############################################################### def UpdateComments(G2frame,data): '''Place comments into the data window @@ -7571,7 +7625,7 @@ def UpdateComments(G2frame,data): G2obj.StripUnicode(lines)) G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) - + #### Controls Tree Item editor ############################################## def UpdateControls(G2frame,data): '''Edit overall GSAS-II controls in main Controls data tree entry @@ -7582,7 +7636,7 @@ def UpdateControls(G2frame,data): data['deriv type'] = 'analytic Hessian' data['min dM/M'] = 0.001 data['shift factor'] = 1. - data['max cyc'] = 3 + data['max cyc'] = 3 data['F**2'] = False if 'SVDtol' not in data: data['SVDtol'] = 1.e-6 @@ -7612,11 +7666,10 @@ def UpdateControls(G2frame,data): data['Marquardt'] = -3 if 'newLeBail' not in data: data['newLeBail'] = False - #end patch def SeqSizer(): - + def OnSelectData(event): choices = GetGPXtreeDataNames(G2frame,['PWDR','HKLF',]) phaseRIdList,histdict = G2frame.GetPhaseInfofromTree(Used=True) @@ -7642,22 +7695,22 @@ def OnSelectData(event): if dlg.ShowModal() == wx.ID_OK: for sel in dlg.GetSelections(): names.append(choices[sel]) - data['Seq Data'] = names + data['Seq Data'] = names dlg.Destroy() G2frame.SetTitleByGPX() if names: # probably not needed (should be set previously) item = GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') constraints = G2frame.GPXtree.GetItemPyData(item) constraints['_seqmode'] = constraints.get('_seqmode','auto-wildcard') - + wx.CallAfter(UpdateControls,G2frame,data) - + def OnReverse(event): data['Reverse Seq'] = reverseSel.GetValue() - + def OnCopySel(event): data['Copy2Next'] = copySel.GetValue() - + def OnClrSeq(event): sId = GetGPXtreeItemId(G2frame,G2frame.root,'Sequential results') if sId: @@ -7667,7 +7720,7 @@ def OnClrSeq(event): G2frame.GPXtree.Delete(sId) finally: dlg.Destroy() - + seqSizer = wx.BoxSizer(wx.VERTICAL) dataSizer = wx.BoxSizer(wx.HORIZONTAL) SeqData = data.get('Seq Data',[]) @@ -7677,7 +7730,7 @@ def OnClrSeq(event): else: lbl = 'Sequential Refinement with '+str(len(SeqData))+' dataset(s) selected' dataSizer.Add(wx.StaticText(G2frame.dataWindow,label=lbl),0,WACV) - selSeqData = wx.Button(G2frame.dataWindow,label=' Reselect datasets') + selSeqData = wx.Button(G2frame.dataWindow,label=' Reselect datasets') selSeqData.Bind(wx.EVT_BUTTON,OnSelectData) dataSizer.Add(selSeqData,0,WACV) seqSizer.Add(dataSizer) @@ -7696,23 +7749,23 @@ def OnClrSeq(event): selSizer.Add(clrSeq,0,WACV) seqSizer.Add(selSizer,0) return seqSizer - - - def LSSizer(): - + + + def LSSizer(): + def OnDerivType(event): data['deriv type'] = derivSel.GetValue() derivSel.SetValue(data['deriv type']) wx.CallAfter(UpdateControls,G2frame,data) - + def OnMaxCycles(event): data['max cyc'] = int(maxCyc.GetValue()) maxCyc.SetValue(str(data['max cyc'])) - + def OnMarqLam(event): data['Marquardt'] = int(marqLam.GetValue()) marqLam.SetValue(str(data['Marquardt'])) - + def OnFactor(event): event.Skip() try: @@ -7721,10 +7774,10 @@ def OnFactor(event): value = 1.0 data['shift factor'] = value Factr.SetValue('%.5f'%(value)) - + def OnFsqRef(event): data['F**2'] = fsqRef.GetValue() - + LSSizer = wx.FlexGridSizer(cols=4,vgap=5,hgap=5) tmpSizer=wx.BoxSizer(wx.HORIZONTAL) @@ -7736,7 +7789,7 @@ def OnFsqRef(event): style=wx.CB_READONLY|wx.CB_DROPDOWN) derivSel.SetValue(data['deriv type']) derivSel.Bind(wx.EVT_COMBOBOX, OnDerivType) - + LSSizer.Add(derivSel,0,WACV) LSSizer.Add(wx.StaticText(G2frame.dataWindow,label='Min delta-M/M: '),0,WACV) LSSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'min dM/M',nDig=(10,2,'g'),xmin=1.e-9,xmax=1.),0,WACV) @@ -7780,7 +7833,7 @@ def OnFsqRef(event): xmin=usrRej[item][1][0],xmax=usrRej[item][1][1]) ShklSizer.Add(usrrej,0,WACV) return LSSizer,ShklSizer - + def AuthSizer(): def OnAuthor(event): event.Skip() @@ -7799,7 +7852,7 @@ def ClearFrozen(event): 'Removes all frozen parameters by clearing the entire dict' Controls['parmFrozen'] = {} wx.CallAfter(UpdateControls,G2frame,data) - + # start of UpdateControls if 'SVD' in data['deriv type']: G2frame.GetStatusBar().SetStatusText('Hessian SVD not recommended for initial refinements; use analytic Hessian or Jacobian',1) @@ -7812,7 +7865,7 @@ def ClearFrozen(event): topSizer.Add(wx.StaticText(parent,wx.ID_ANY,'Refinement Controls'),0,WACV) topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex='Controls')) - + mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) G2G.HorizontalLine(mainSizer,G2frame.dataWindow) @@ -7824,16 +7877,16 @@ def ClearFrozen(event): G2G.HorizontalLine(mainSizer,G2frame.dataWindow) subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) - subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Single Crystal Refinement Settings'),0,WACV) + subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Single Crystal Refinement Settings'),0,WACV) subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer,0,wx.EXPAND) mainSizer.Add(ShklSizer) - + mainSizer.Add((5,15),0) G2G.HorizontalLine(mainSizer,G2frame.dataWindow) subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) - subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Sequential Settings'),0,WACV) + subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Sequential Settings'),0,WACV) subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer,0,wx.EXPAND) mainSizer.Add(SeqSizer()) @@ -7841,7 +7894,7 @@ def ClearFrozen(event): G2G.HorizontalLine(mainSizer,G2frame.dataWindow) subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) - subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Global Settings'),0,WACV) + subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Global Settings'),0,WACV) subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer,0,wx.EXPAND) mainSizer.Add(AuthSizer()) @@ -7872,8 +7925,8 @@ def ClearFrozen(event): mainSizer.Add(subSizer) G2frame.dataWindow.SetDataSize() # G2frame.SendSizeEvent() - -#### Main PWDR panel ######################################################## + +#### Main PWDR panel ######################################################## def UpdatePWHKPlot(G2frame,kind,item): '''Called when the histogram main tree entry is called. Displays the histogram weight factor, refinement statistics for the histogram @@ -7896,7 +7949,7 @@ def onEditSimRange(event): header='Edit simulation range', minvals=(0.5,1.0,0.0001), maxvals=(500.,500.,.01), - ) + ) else: inp = [ min(data[1][0]), @@ -7917,7 +7970,7 @@ def onEditSimRange(event): if val != wx.ID_OK: return if inp[0] > inp[1]: end,start,step = inp - else: + else: start,end,step = inp step = abs(step) if 'TOF' in data[0].get('simType','CW'): @@ -7934,7 +7987,7 @@ def onEditSimRange(event): G2frame.GPXtree.SetItemPyData(GetGPXtreeItemId(G2frame,item,'Limits'), [(Tmin,Tmax),[Tmin,Tmax]]) wx.CallAfter(UpdatePWHKPlot,G2frame,kind,item) # redisplay data screen - + def OnPlot1DHKL(event): '''Plots a 1D stick diagram of reflection intensities''' refList = data[1]['RefList'] @@ -7952,7 +8005,7 @@ def OnPlot3DHKL(event): 'backColor':[0,0,0],'depthFog':False,'Zclip':10.0,'cameraPos':10.,'Zstep':0.05,'viewUp':[0,1,0], 'Scale':1.0,'oldxy':[],'viewDir':[0,0,1]},'Super':Super,'SuperVec':SuperVec} G2plt.Plot3DSngl(G2frame,newPlot=True,Data=controls,hklRef=refList,Title=phaseName) - + def OnPlotAll3DHKL(event): '''Plots in 3D reciprocal space with green dots proportional to F^2, etc. from all SHKL histograms''' choices = GetGPXtreeDataNames(G2frame,['HKLF',]) @@ -7973,7 +8026,7 @@ def OnPlotAll3DHKL(event): refList = np.concatenate((refList,reflData['RefList'])) else: refList = reflData['RefList'] - + FoMax = np.max(refList.T[8+Super]) Hmin = np.array([int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))]) Hmax = np.array([int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))]) @@ -7983,7 +8036,7 @@ def OnPlotAll3DHKL(event): 'backColor':[0,0,0],'depthFog':False,'Zclip':10.0,'cameraPos':10.,'Zstep':0.05,'viewUp':[0,1,0], 'Scale':1.0,'oldxy':[],'viewDir':[1,0,0]},'Super':Super,'SuperVec':SuperVec} G2plt.Plot3DSngl(G2frame,newPlot=True,Data=controls,hklRef=refList,Title=phaseName) - + def OnMergeHKL(event): '''Merge HKLF data sets to unique set according to Laue symmetry''' Name = G2frame.GPXtree.GetItemText(G2frame.PatternId) @@ -8015,7 +8068,7 @@ def OnMergeHKL(event): Comments.append(" Transformation M*H = H' applied; M=") Comments.append(str(Trans)) refList = G2lat.LaueUnique(Laue,refList) - dlg = wx.ProgressDialog('Build HKL dictonary','',len(refList)+1, + dlg = wx.ProgressDialog('Build HKL dictonary','',len(refList)+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) HKLdict = {} for ih,hkl in enumerate(refList): @@ -8026,7 +8079,7 @@ def OnMergeHKL(event): dlg.Update(ih) dlg.Destroy() mergeRef = [] - dlg = wx.ProgressDialog('Processing merge','',len(HKLdict)+1, + dlg = wx.ProgressDialog('Processing merge','',len(HKLdict)+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) sumDf = 0. sumFo = 0. @@ -8046,7 +8099,7 @@ def OnMergeHKL(event): newHKL[6+Super] = sig newHKL[8+Super] = Fo if newHKL[5+Super] > 0.: - mergeRef.append(list(newHKL)) + mergeRef.append(list(newHKL)) dlg.Destroy() if Super: mergeRef = G2mth.sortArray(G2mth.sortArray(G2mth.sortArray(G2mth.sortArray(mergeRef,3),2),1),0) @@ -8083,24 +8136,65 @@ def OnMergeHKL(event): G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),Inst) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection List'),{}) #dummy entry for GUI use G2frame.GPXtree.SetItemPyData(Id,newData) - + + def OnFixFsqFsq(event): + ''' Fix HKLF data that had been misimported as F instead of F^2''' + Name = G2frame.GPXtree.GetItemText(G2frame.PatternId) + Inst = G2frame.GPXtree.GetItemPyData(GetGPXtreeItemId(G2frame, + G2frame.PatternId,'Instrument Parameters')) + CId = GetGPXtreeItemId(G2frame,G2frame.PatternId,'Comments') + if CId: + Comments = G2frame.GPXtree.GetItemPyData(CId) + else: + Comments = [] + Super = data[1]['Super'] + refList = np.copy(data[1]['RefList']) + Comments.append(' Changing %d reflection F^2^2 to F^2 from %s'%(len(refList),Name)) + for ih,hkl in enumerate(refList): #undo F--> F^2 + hkl[5+Super] = np.sqrt(hkl[5+Super]) + hkl[6+Super] = hkl[6+Super]/(2.0*hkl[5+Super]) + hkl[7+Super] = np.sqrt(hkl[7+Super]) + HKLFlist = [] + if G2frame.GPXtree.GetCount(): + item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) + while item: + name = G2frame.GPXtree.GetItemText(item) + if name.startswith('HKLF ') and name not in HKLFlist: + HKLFlist.append(name) + item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) + newName = G2obj.MakeUniqueLabel(Name,HKLFlist) + newData = copy.deepcopy(data) + newData[0]['ranId'] = ran.randint(0,sys.maxsize) + newData[1]['RefList'] = refList + newData[0]['Nobs'] = refList.shape[0] + newData[0]['wR'] = 0.0 + keys = list(newData[0].keys()) + for item in keys: + if ':' in item: + del newData[0][item] + Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=newName) + G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments) + G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),Inst) + G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection List'),{}) #dummy entry for GUI use + G2frame.GPXtree.SetItemPyData(Id,newData) + def OnErrorAnalysis(event): - '''Plots an "Abrams" plot - sorted delta/sig across data set. + '''Plots an "Abrams" plot - sorted delta/sig across data set. Should be straight line of slope 1 - never is''' G2plt.PlotDeltSig(G2frame,kind) - + # def OnCompression(event): # data[0] = int(comp.GetValue()) - + def onCopyPlotCtrls(event): '''Respond to menu item to copy multiple sections from a histogram. - Need this here to pass on the G2frame object. + Need this here to pass on the G2frame object. ''' G2pdG.CopyPlotCtrls(G2frame) def onCopySelectedItems(event): '''Respond to menu item to copy multiple sections from a histogram. - Need this here to pass on the G2frame object. + Need this here to pass on the G2frame object. ''' G2pdG.CopySelectedHistItems(G2frame) @@ -8125,7 +8219,9 @@ def OnEditMag(**args): iBeg = np.searchsorted(data[1][0],Limits[1][0]) iFin = np.searchsorted(data[1][0],Limits[1][1])+1 meanI = np.mean(data[1][1][iBeg:iFin]) - S = -1.0+np.sum(np.log(meanI**2/(data[1][1][iBeg:iFin]-meanI)**2))/(iFin-iBeg) + S = None + if meanI != 0: + S = -1.0+np.sum(np.log(meanI**2/(data[1][1][iBeg:iFin]-meanI)**2))/(iFin-iBeg) #patches if not data: return @@ -8153,6 +8249,7 @@ def OnEditMag(**args): G2frame.Bind(wx.EVT_MENU, OnPlot1DHKL, id=G2G.wxID_1DHKLSTICKPLOT) G2frame.Bind(wx.EVT_MENU, OnPlot3DHKL, id=G2G.wxID_PWD3DHKLPLOT) G2frame.Bind(wx.EVT_MENU, OnPlotAll3DHKL, id=G2G.wxID_3DALLHKLPLOT) + G2frame.Bind(wx.EVT_MENU, OnFixFsqFsq, id=G2G.wxID_FIXFSQSQDATA) if kind == 'PWDR': lbl = 'Powder' elif kind == 'SASD': @@ -8163,14 +8260,14 @@ def OnEditMag(**args): lbl = 'Single crystal' else: lbl = '?' - lbl += ' histogram: ' + G2frame.GPXtree.GetItemText(item)[:60] + lbl += ' histogram: ' + G2frame.GPXtree.GetItemText(item)[:60] G2frame.dataWindow.ClearData() topSizer = G2frame.dataWindow.topBox parent = G2frame.dataWindow.topPanel topSizer.Add(wx.StaticText(parent,wx.ID_ANY,lbl),0,WACV) topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) - + mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) G2G.HorizontalLine(mainSizer,G2frame.dataWindow) @@ -8187,7 +8284,7 @@ def OnEditMag(**args): # comp.Bind(wx.EVT_COMBOBOX, OnCompression) # wtSizer.Add(comp,0,WACV) - if kind == 'PWDR': + if kind == 'PWDR' and S is not None: wtSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Data "Surprise" factor: %.3f'%S)) mainSizer.Add(wtSizer,0,wx.EXPAND) wtSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -8278,7 +8375,7 @@ def OnEditMag(**args): magSizer.Add(edit,1,wx.ALIGN_CENTER,5) magSizer.Add((1,1)) for i in range(1,lenmag): - edit = G2G.ValidatedTxtCtrl(panel,data[0]['Magnification'][i],0,nDig=(10,3), + edit = G2G.ValidatedTxtCtrl(panel,data[0]['Magnification'][i],0,nDig=(10,3), xmin=data[1][0][0],xmax=data[1][0][-1],OnLeave=OnEditMag) magSizer.Add(edit) edit = G2G.ValidatedTxtCtrl(panel,data[0]['Magnification'][i],1, @@ -8324,7 +8421,7 @@ def OnEditMag(**args): SuperVec = General.get('SuperVec',[]) else: Super = 0 - SuperVec = [] + SuperVec = [] refList = data[1]['RefList'] FoMax = np.max(refList.T[5+data[1].get('Super',0)]) page = G2frame.G2plotNB.nb.GetSelection() @@ -8336,7 +8433,7 @@ def OnEditMag(**args): controls = Page.controls G2plt.Plot3DSngl(G2frame,newPlot=False,Data=controls,hklRef=refList,Title=phaseName) else: - controls = {'Type' : 'Fo','ifFc' : True, + controls = {'Type' : 'Fo','ifFc' : True, 'HKLmax' : [int(np.max(refList.T[0])),int(np.max(refList.T[1])),int(np.max(refList.T[2]))], 'HKLmin' : [int(np.min(refList.T[0])),int(np.min(refList.T[1])),int(np.min(refList.T[2]))], 'FoMax' : FoMax,'Zone' : '001','Layer' : 0,'Scale' : 1.0,'Super':Super,'SuperVec':SuperVec} @@ -8345,25 +8442,25 @@ def OnEditMag(**args): # make sure parent histogram item is displayed if item != G2frame.GPXtree.GetSelection(): wx.CallAfter(G2frame.GPXtree.SelectItem,item) - -##### Data (GPX) tree routines ############################################### + +##### Data (GPX) tree routines ############################################### def GetGPXtreeDataNames(G2frame,dataTypes): '''Finds all items in tree that match a 4 character prefix - + :param wx.Frame G2frame: Data tree frame object :param list dataTypes: Contains one or more data tree item types to be matched such as ['IMG '] or ['PWDR','HKLF'] - :returns: a list of tree item names for the matching items + :returns: a list of tree item names for the matching items ''' names = [] - item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) + item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) while item: name = G2frame.GPXtree.GetItemText(item) if name[:4] in dataTypes: names.append(name) item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) return names - + def GetGPXtreeItemId(G2frame, parentId, itemText): '''Find the tree item that matches the text in itemText starting with parentId @@ -8386,7 +8483,7 @@ def SelectDataTreeItem(G2frame,item,oldFocus=None): Also called from GSASII.OnGPXtreeEndDrag, OnAddPhase -- might be better to select item, triggering the the bind to SelectDataTreeItem - Also Called in GSASIIphsGUI.UpdatePhaseData by OnTransform callback. + Also Called in GSASIIphsGUI.UpdatePhaseData by OnTransform callback. ''' def OnShowShift(event): if 'cycle' in event.EventObject.GetLabel(): @@ -8402,15 +8499,15 @@ def OnShowShift(event): lbl = 'Total shift/esd over last refinement run' G2plt.PlotNamedFloatHBarGraph(G2frame,shftesd,data['varyList'],Xlabel='Total shift/esd', Ylabel='Variables',Title=lbl,PlotName='Shift/esd') - - if G2frame.PickIdText == G2frame.GetTreeItemsList(item): # don't redo the current data tree item + + if G2frame.PickIdText == G2frame.GetTreeItemsList(item): # don't redo the current data tree item if GSASIIpath.GetConfigValue('debug'): print('Skipping SelectDataTreeItem as G2frame.PickIdText unchanged') return # save or finish processing of outstanding events for grid in G2frame.dataWindow.currentGrids: # complete any open wx.Grid edits #if GSASIIpath.GetConfigValue('debug'): print 'Testing grid edit in',grid - try: + try: if grid.IsCellEditControlEnabled(): # complete any grid edits in progress if GSASIIpath.GetConfigValue('debug'): print ('Completing grid edit in%s'%str(grid)) grid.SaveEditControlValue() @@ -8419,10 +8516,10 @@ def OnShowShift(event): grid.DisableCellEditControl() except: pass - if G2frame.dataWindow.GetLabel() == 'Comments': # save any recently entered comments + if G2frame.dataWindow.GetLabel() == 'Comments': # save any recently entered comments try: data = [G2frame.dataDisplay.GetValue()] - G2frame.dataDisplay.ClearData() + G2frame.dataDisplay.ClearData() Id = GetGPXtreeItemId(G2frame,G2frame.root, 'Comments') if Id: G2frame.GPXtree.SetItemPyData(Id,data) except: #clumsy but avoids dead window problem when opening another project @@ -8430,15 +8527,15 @@ def OnShowShift(event): elif G2frame.dataWindow.GetLabel() == 'Notebook': # save any recent notebook entries try: data = [G2frame.dataDisplay.GetValue()] - G2frame.dataDisplay.ClearData() + G2frame.dataDisplay.ClearData() Id = GetGPXtreeItemId(G2frame,G2frame.root, 'Notebook') if Id: G2frame.GPXtree.SetItemPyData(Id,data) except: #clumsy but avoids dead window problem when opening another project pass # elif 'Phase Data for' in G2frame.dataWindow.GetLabel(): -# if G2frame.dataDisplay: +# if G2frame.dataDisplay: # oldPage = G2frame.dataDisplay.GetSelection() - + G2frame.GetStatusBar().SetStatusText('',1) SetDataMenuBar(G2frame) G2frame.SetTitleByGPX() @@ -8481,10 +8578,10 @@ def OnShowShift(event): if GSASIIpath.GetConfigValue('debug'): print ('bug: why here? prfx=%s prfx1=%s'%(prfx,prfx1)) G2obj.HowDidIgetHere() - + # clear out the old panel contents if G2frame.dataWindow: - G2frame.dataWindow.ClearData() + G2frame.dataWindow.ClearData() # process first-level entries in tree if G2frame.GPXtree.GetItemParent(item) == G2frame.root: G2frame.PatternId = 0 @@ -8496,7 +8593,7 @@ def OnShowShift(event): data = G2frame.GPXtree.GetItemPyData(item) if not data: #fill in defaults data = copy.copy(G2obj.DefaultControls) #least squares controls - G2frame.GPXtree.SetItemPyData(item,data) + G2frame.GPXtree.SetItemPyData(item,data) UpdateControls(G2frame,data) for i in G2frame.Refine: i.Enable(True) elif G2frame.GPXtree.GetItemText(item).startswith('Sequential '): @@ -8515,7 +8612,7 @@ def OnShowShift(event): topSizer.Add(wx.StaticText(parent,wx.ID_ANY,lbl),0,WACV) topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex='Covariance')) - + mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) G2G.HorizontalLine(mainSizer,G2frame.dataWindow) @@ -8612,7 +8709,7 @@ def OnShowShift(event): G2frame.PatternId = item data = G2frame.GPXtree.GetItemPyData(GetGPXtreeItemId(G2frame,item,'PDF Controls')) G2pdG.UpdatePDFGrid(G2frame,data) - for i in G2frame.ExportPDF: i.Enable(True) # this should be done on .gpx load; is done on OnMakePDFs (GSASII.py) + for i in G2frame.ExportPDF: i.Enable(True) # this should be done on .gpx load; is done on OnMakePDFs if len(data['G(R)']): G2plt.PlotISFG(G2frame,data,plotType='G(R)') elif G2frame.GPXtree.GetItemText(item) == 'Phases': @@ -8669,20 +8766,20 @@ def OnShowShift(event): elif GSASIIpath.GetConfigValue('debug'): print('Unknown tree item',G2frame.GPXtree.GetItemText(item)) ############################################################################ - # process second-level entries in tree + # process second-level entries in tree elif G2frame.GPXtree.GetItemText(item) == 'PDF Peaks': G2frame.PatternId = G2frame.GPXtree.GetItemParent(item) peaks = G2frame.GPXtree.GetItemPyData(GetGPXtreeItemId(G2frame,G2frame.PatternId,'PDF Peaks')) data = G2frame.GPXtree.GetItemPyData(GetGPXtreeItemId(G2frame,G2frame.PatternId,'PDF Controls')) G2pdG.UpdatePDFPeaks(G2frame,peaks,data) if len(data['G(R)']): - G2plt.PlotISFG(G2frame,data,plotType='G(R)',newPlot=True,peaks=peaks) + G2plt.PlotISFG(G2frame,data,plotType='G(R)',newPlot=True,peaks=peaks) elif G2frame.GPXtree.GetItemText(item) == 'PDF Controls': G2frame.dataWindow.helpKey = G2frame.GPXtree.GetItemText(item) # special treatment to avoid PDF_PDF Controls G2frame.PatternId = G2frame.GPXtree.GetItemParent(item) data = G2frame.GPXtree.GetItemPyData(item) G2pdG.UpdatePDFGrid(G2frame,data) - for i in G2frame.ExportPDF: i.Enable(True) # this should be done on .gpx load; is done on OnMakePDFs (GSASII.py) + for i in G2frame.ExportPDF: i.Enable(True) # this should be done on .gpx load; is done on OnMakePDFs if len(data['G(R)']): if 'I(Q)' in data: G2plt.PlotISFG(G2frame,data,plotType='I(Q)') if 'S(Q)' in data: G2plt.PlotISFG(G2frame,data,plotType='S(Q)') @@ -8695,7 +8792,7 @@ def OnShowShift(event): # print('Debug: reloading G2phG') # import imp # imp.reload(G2phG) - # end debug stuff + # end debug stuff G2phG.UpdatePhaseData(G2frame,item,data) elif G2frame.GPXtree.GetItemText(parentID) == 'Restraints': data = G2frame.GPXtree.GetItemPyData(parentID) @@ -8800,7 +8897,7 @@ def OnShowShift(event): 'FreePrm1':0.,'FreePrm2':0.,'FreePrm3':0., 'Gonio. radius':200.0} G2frame.GPXtree.SetItemPyData(item,data) - + G2pdG.UpdateSampleGrid(G2frame,data) G2pwpl.PlotPatterns(G2frame,True,plotType=datatype) elif G2frame.GPXtree.GetItemText(item) == 'Index Peak List': @@ -8832,11 +8929,11 @@ def OnShowShift(event): data.append([]) #empty dmin data.append({}) #empty superlattice stuff data.append([]) #empty mag cells list - G2frame.GPXtree.SetItemPyData(item,data) + G2frame.GPXtree.SetItemPyData(item,data) #patch if len(data) < 5: data.append({'Use':False,'ModVec':[0,0,0.1],'maxH':1,'ssSymb':''}) #empty superlattice stuff - G2frame.GPXtree.SetItemPyData(item,data) + G2frame.GPXtree.SetItemPyData(item,data) #end patch G2pdG.UpdateUnitCellsGrid(G2frame,data,New=True) elif G2frame.GPXtree.GetItemText(item) == 'Reflection Lists': #powder reflections @@ -8879,24 +8976,24 @@ def OnShowShift(event): # panel.SendSizeEvent() # G2frame.dataWindow.ClearData() # FillWindow(G2frame.dataWindow) - + def SetDataMenuBar(G2frame,menu=None): - '''Attach the appropriate menu (a wx.MenuBar object) for the - selected data tree item to the system's menu bar. + '''Attach the appropriate menu (a wx.MenuBar object) for the + selected data tree item to the system's menu bar. - To speed startup of the main window, most menu bars are not - created at startup of the program, instead, the menu variable - is instead initially defined with a reference to routine that + To speed startup of the main window, most menu bars are not + created at startup of the program, instead, the menu variable + is instead initially defined with a reference to routine that is called to create the menu bar. This routine should overwrite the variable that points to the menu bar (so that the routine - is called only once) and it should call - :func:`GSASII.SetMenuBar` since the name of the created - MenuBar object is not available here. - - Note that there are some data tree items that do not need - their own customized menu bars, for these this routine can - be called without a value for the menu argument. This - causes the standard, uncustomized, menubar to be used. + is called only once) and it should call + :func:`GSASII.SetMenuBar` since the name of the created + MenuBar object is not available here. + + Note that there are some data tree items that do not need + their own customized menu bars, for these this routine can + be called without a value for the menu argument. This + causes the standard, uncustomized, menubar to be used. ''' if menu is None: G2frame.SetMenuBar(G2frame.GSASIIMenu) @@ -8921,7 +9018,7 @@ def SetDataMenuBar(G2frame,menu=None): if obj: for i in obj[1:]: i.Enable(obj[0].IsEnabled()) # N.B. it does not appear that MakePDF, ExportMTZ or ExportHKL are ever disabled - + def FindPhaseItem(G2frame): '''Finds the Phase item in the tree. If not present it adds one also adding 'Hist/Phase' if config var SeparateHistPhaseTreeItem @@ -8934,6 +9031,6 @@ def FindPhaseItem(G2frame): else: sub = GetGPXtreeItemId(G2frame,G2frame.root,'Phases') return sub - + if __name__ == '__main__': ShowVersions() diff --git a/GSASII/GSASIIddataGUI.py b/GSASII/GSASIIddataGUI.py index 2a007d99f..f2f186ae4 100644 --- a/GSASII/GSASIIddataGUI.py +++ b/GSASII/GSASIIddataGUI.py @@ -1,21 +1,21 @@ # -*- coding: utf-8 -*- #GSASII - phase data display routines -'''Routines for Data tab in Phase dataframe follows. +'''Routines for Data tab in Phase dataframe follows. ''' from __future__ import division, print_function import copy import numpy as np import numpy.linalg as nl -import GSASIIpath -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIplot as G2plt -import GSASIIpwd as G2pwd -import GSASIIphsGUI as G2phG -import GSASIIctrlGUI as G2G -import GSASIIdataGUI as G2gd -import GSASIIfiles as G2fil -import GSASIImath as G2mth +from . import GSASIIpath +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIplot as G2plt +from . import GSASIIpwd as G2pwd +from . import GSASIIphsGUI as G2phG +from . import GSASIIctrlGUI as G2G +from . import GSASIIdataGUI as G2gd +from . import GSASIIfiles as G2fil +from . import GSASIImath as G2mth try: import wx @@ -50,7 +50,7 @@ def OnPlotSel(event): generalData['Data plot type'] = Obj.GetStringSelection() G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) wx.CallLater(100,UpdateDData,G2frame,DData,data,G2frame.hist) - + def OnPOhkl(event): event.Skip() Obj = event.GetEventObject() @@ -63,15 +63,15 @@ def OnPOhkl(event): hkl = generalData['POhkl'] generalData['POhkl'] = hkl h,k,l = hkl - Obj.SetValue('%3d %3d %3d'%(h,k,l)) + Obj.SetValue('%3d %3d %3d'%(h,k,l)) G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) - + def OnProj(event): Obj = event.GetEventObject() generalData['3Dproj'] = Obj.GetValue() G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) - - ifkeV = 'E' in UseList[G2frame.hist].get('Type','') + + ifkeV = 'E' in UseList[G2frame.hist].get('Type','') plotSizer = wx.BoxSizer(wx.VERTICAL) if ifkeV: choice = ['None','Mustrain','Size'] @@ -80,7 +80,7 @@ def OnProj(event): plotSel = wx.RadioBox(DData,wx.ID_ANY,'Select plot type:',choices=choice, majorDimension=1,style=wx.RA_SPECIFY_COLS) plotSel.SetStringSelection(generalData['Data plot type']) - plotSel.Bind(wx.EVT_RADIOBOX,OnPlotSel) + plotSel.Bind(wx.EVT_RADIOBOX,OnPlotSel) plotSizer.Add(plotSel) if generalData['Data plot type'] == 'Preferred orientation': POhklSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -99,22 +99,22 @@ def OnProj(event): style=wx.CB_READONLY|wx.CB_DROPDOWN) projType.Bind(wx.EVT_COMBOBOX, OnProj) projSizer.Add(projType,0,WACV) - plotSizer.Add(projSizer) + plotSizer.Add(projSizer) return plotSizer - + def ScaleSizer(): - + def OnScaleRef(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Scale'][1] = Obj.GetValue() def onChangeFraction(invalid,value,tc): wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + scaleSizer = wx.BoxSizer(wx.HORIZONTAL) if 'PWDR' in G2frame.hist: scaleRef = wx.CheckBox(DData,wx.ID_ANY,label=' Phase fraction: ') elif 'HKLF' in G2frame.hist: - scaleRef = wx.CheckBox(DData,wx.ID_ANY,label=' Scale factor: ') + scaleRef = wx.CheckBox(DData,wx.ID_ANY,label=' Scale factor: ') scaleRef.SetValue(bool(UseList[G2frame.hist]['Scale'][1])) scaleRef.Bind(wx.EVT_CHECKBOX, OnScaleRef) scaleSizer.Add(scaleRef,0,WACV|wx.LEFT,5) @@ -127,32 +127,32 @@ def onChangeFraction(invalid,value,tc): weightFr = UseList[G2frame.hist]['Scale'][0]*generalData['Mass']/wtSum scaleSizer.Add(wx.StaticText(DData,label=' Wt. fraction: %.3f'%(weightFr)),0,WACV) return scaleSizer - + def OnLGmixRef(event): Obj = event.GetEventObject() hist,name = Indx[Obj.GetId()] UseList[G2frame.hist][name][2][2] = Obj.GetValue() - + def OnSizeType(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Size'][0] = Obj.GetValue() G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnSizeRef(event): Obj = event.GetEventObject() hist,pid = Indx[Obj.GetId()] if UseList[G2frame.hist]['Size'][0] == 'ellipsoidal': - UseList[G2frame.hist]['Size'][5][pid] = Obj.GetValue() + UseList[G2frame.hist]['Size'][5][pid] = Obj.GetValue() else: UseList[G2frame.hist]['Size'][2][pid] = Obj.GetValue() - + def OnStrainType(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Mustrain'][0] = Obj.GetValue() G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnStrainRef(event): Obj = event.GetEventObject() hist,pid = Indx[Obj.GetId()] @@ -160,7 +160,7 @@ def OnStrainRef(event): UseList[G2frame.hist]['Mustrain'][5][pid] = Obj.GetValue() else: UseList[G2frame.hist]['Mustrain'][2][pid] = Obj.GetValue() - + def OnStrainAxis(event): event.Skip() Obj = event.GetEventObject() @@ -173,9 +173,9 @@ def OnStrainAxis(event): hkl = UseList[G2frame.hist]['Mustrain'][3] UseList[G2frame.hist]['Mustrain'][3] = hkl h,k,l = hkl - Obj.SetValue('%3d %3d %3d'%(h,k,l)) + Obj.SetValue('%3d %3d %3d'%(h,k,l)) wx.CallAfter(G2plt.PlotSizeStrainPO,G2frame,data,hist) - + def OnResetStrain(event): Obj = event.GetEventObject() item,name = Indx[Obj.GetId()] @@ -191,7 +191,7 @@ def OnResetStrain(event): UseList[item]['Mustrain'][4] = vals G2plt.PlotSizeStrainPO(G2frame,data,item) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnPOAxis(event): event.Skip() Obj = event.GetEventObject() @@ -204,16 +204,24 @@ def OnPOAxis(event): hkl = UseList[G2frame.hist]['Pref.Ori.'][3] UseList[G2frame.hist]['Pref.Ori.'][3] = hkl h,k,l = hkl - Obj.SetValue('%3d %3d %3d'%(h,k,l)) - + Obj.SetValue('%3d %3d %3d'%(h,k,l)) + def OnPOOrder(event): + '''Respond when the SH order is changed in the GUI. + Updates the dict with the cylindrical Spherical harmonics + coefficients for the specified order, retaining values from + the previous dict, if values were already present + ''' Obj = event.GetEventObject() Order = int(Obj.GetValue()) UseList[G2frame.hist]['Pref.Ori.'][4] = Order - UseList[G2frame.hist]['Pref.Ori.'][5] = SetPOCoef(Order,G2frame.hist) + UseList[G2frame.hist]['Pref.Ori.'][5] = SetPOCoef(Order) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) def OnPOType(event): + '''Respond when the SH type is changed in the GUI. + Note that values set that are not used are also not changed. + ''' Obj = event.GetEventObject() if 'March' in Obj.GetValue(): UseList[G2frame.hist]['Pref.Ori.'][0] = 'MD' @@ -240,7 +248,7 @@ def OnAddFixed(event): selected = [] for i,key in enumerate(phaseKeys): if key in fixedVars: selected.append(i) - dlg = G2G.G2MultiChoiceDialog(G2frame, 'Choose phase vars to fix for this histogram only', + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Choose phase vars to fix for this histogram only', 'Choose items to edit', phaseKeys,selected=selected) if dlg.ShowModal() == wx.ID_OK: sel = dlg.GetSelections() @@ -251,27 +259,31 @@ def OnAddFixed(event): UseList[G2frame.hist]['FixedSeqVars'] = [phaseKeys[i] for i in sel] wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - def SetPOCoef(Order,hist): + def SetPOCoef(Order): + '''Sets up a dict with the cylindrical Spherical harmonics + coefficients for the specified order. + Retains values from the previous dict, if values were already present + ''' cofNames = G2lat.GenSHCoeff(SGData['SGLaue'],'0',Order,False) #cylindrical & no M - newPOCoef = dict(zip(cofNames,np.zeros(len(cofNames)))) + newPOCoef = dict(zip(cofNames,len(cofNames)*[0.])) POCoeff = UseList[G2frame.hist]['Pref.Ori.'][5] for cofName in POCoeff: - if cofName in cofNames: + if cofName in cofNames: newPOCoef[cofName] = POCoeff[cofName] return newPOCoef - + def checkAxis(axis): if not np.any(np.array(axis)): return False return axis - + def OnNewValue(invalid,value,tc): G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) - + def OnNewValueReDraw(invalid,value,tc): G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def TopSizer(name,choices,parm,OnType): topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(wx.StaticText(DData,wx.ID_ANY,name),0,WACV) @@ -280,7 +292,7 @@ def TopSizer(name,choices,parm,OnType): sizeType.Bind(wx.EVT_COMBOBOX, OnType) topSizer.Add(sizeType,0,WACV|wx.BOTTOM,5) return topSizer - + def LGmixSizer(name,Limits,OnRef): lgmixSizer = wx.BoxSizer(wx.HORIZONTAL) lgmixRef = wx.CheckBox(DData,wx.ID_ANY,label='LGmix') @@ -293,7 +305,7 @@ def LGmixSizer(name,Limits,OnRef): nDig=(10,3),xmin=Limits[0],xmax=Limits[1]) lgmixSizer.Add(lgmixVal,0,WACV|wx.LEFT,5) return lgmixSizer - + def ResetSizer(name,OnReset): resetSizer = wx.BoxSizer(wx.HORIZONTAL) reset = wx.Button(DData,wx.ID_ANY,label='Reset?') @@ -302,7 +314,7 @@ def ResetSizer(name,OnReset): reset.Bind(wx.EVT_BUTTON,OnReset) resetSizer.Add(reset,0,WACV) return resetSizer - + def IsoSizer(name,parm,fmt,Limits,OnRef): isoSizer = wx.BoxSizer(wx.HORIZONTAL) sizeRef = wx.CheckBox(DData,wx.ID_ANY,label=name) @@ -315,7 +327,7 @@ def IsoSizer(name,parm,fmt,Limits,OnRef): nDig=fmt,xmin=Limits[0],xmax=Limits[1],OnLeave=OnNewValue) isoSizer.Add(sizeVal,0,WACV) return isoSizer - + def UniSizer(parm,OnAxis): uniSizer = wx.BoxSizer(wx.HORIZONTAL) uniSizer.Add(wx.StaticText(DData,wx.ID_ANY,' Unique axis, H K L: '),0,WACV) @@ -325,7 +337,7 @@ def UniSizer(parm,OnAxis): Axis.Bind(wx.EVT_KILL_FOCUS,OnAxis) uniSizer.Add(Axis,0,WACV|wx.LEFT,5) return uniSizer - + def UniDataSizer(parmName,parm,fmt,Limits,OnRef): dataSizer = wx.BoxSizer(wx.HORIZONTAL) parms = zip([' Equatorial '+parmName,' Axial '+parmName], @@ -380,7 +392,7 @@ def EllSizeDataSizer(): compSizer.Add(wx.StaticText(DData,label='Length: %.3f'%lengths[Id]),0,WACV) dataSizer.Add(compSizer) return dataSizer - + def GenStrainDataSizer(): Snames = G2spc.MustrainNames(SGData) numb = len(Snames) @@ -409,12 +421,12 @@ def GenStrainDataSizer(): return mainSizer def HstrainSizer(): - + def OnHstrainRef(event): Obj = event.GetEventObject() hist,pid = Indx[Obj.GetId()] UseList[G2frame.hist]['HStrain'][1][pid] = Obj.GetValue() - + hSizer = wx.BoxSizer(wx.VERTICAL) hstrainSizer = wx.FlexGridSizer(0,6,5,5) Hsnames = G2spc.HStrainNames(SGData) @@ -455,7 +467,7 @@ def OnHstrainRef(event): cellstr += ', Vol = {:.3f}'.format(G2lat.calc_V(newA)) hSizer.Add(wx.StaticText(DData,wx.ID_ANY,' '+cellstr),0) return hSizer - + def PoTopSizer(POData): poSizer = wx.FlexGridSizer(0,6,5,5) choice = ['March-Dollase','Spherical harmonics'] @@ -476,7 +488,7 @@ def PoTopSizer(POData): poRef.Bind(wx.EVT_CHECKBOX,OnPORef) poSizer.Add(poRef,0,WACV) return poSizer - + def MDDataSizer(POData): poSizer = wx.BoxSizer(wx.HORIZONTAL) poRef = wx.CheckBox(DData,wx.ID_ANY,label=' March-Dollase ratio: ') @@ -492,9 +504,9 @@ def MDDataSizer(POData): poAxis.Bind(wx.EVT_KILL_FOCUS,OnPOAxis) poSizer.Add(poAxis,0,WACV) return poSizer - + def SHDataSizer(POData): - + ODFSizer = wx.FlexGridSizer(0,8,2,2) ODFkeys = list(POData[5].keys()) ODFkeys.sort() @@ -503,9 +515,9 @@ def SHDataSizer(POData): ODFval = G2G.ValidatedTxtCtrl(DData,POData[5],odf,nDig=(8,3),typeHint=float,OnLeave=OnNewValue) ODFSizer.Add(ODFval,0,WACV|wx.LEFT,5) return ODFSizer - + def SHPenalty(POData): - + def OnHKLList(event): dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select penalty hkls', 'Penalty hkls',hkls,filterBox=False) @@ -519,7 +531,7 @@ def OnHKLList(event): finally: dlg.Destroy() wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,G2frame.hist) Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Instrument Parameters'))[0] reflSets = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Reflection Lists')) @@ -546,28 +558,28 @@ def OnHKLList(event): shPenalty.Add(wx.StaticText(DData,wx.ID_ANY,' Zero MRD tolerance: '),0,WACV) shToler = G2G.ValidatedTxtCtrl(DData,POData,7,nDig=(10,2),typeHint=float) shPenalty.Add(shToler,0,WACV) - return shPenalty - + return shPenalty + def ExtSizer(Type): - + def OnSCExtType(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] UseList[item[0]]['Extinction'][item[1]] = Obj.GetValue() wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnEref(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] UseList[item[0]]['Extinction'][2][item[1]][1] = Obj.GetValue() - + def OnExtRef(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Extinction'][1] = Obj.GetValue() - + if Type == 'HKLF': extSizer = wx.BoxSizer(wx.VERTICAL) - typeSizer = wx.BoxSizer(wx.HORIZONTAL) + typeSizer = wx.BoxSizer(wx.HORIZONTAL) typeSizer.Add(wx.StaticText(DData,wx.ID_ANY,' Extinction type: '),0,WACV) Choices = ['None','Primary','Secondary Type I','Secondary Type II',] # remove 'Secondary Type I & II' typeTxt = wx.ComboBox(DData,wx.ID_ANY,choices=Choices,value=UseList[G2frame.hist]['Extinction'][1], @@ -585,8 +597,8 @@ def OnExtRef(event): if UseList[G2frame.hist]['Extinction'][1] == 'None': extSizer.Add(typeSizer,0) else: - extSizer.Add(typeSizer,0,wx.BOTTOM,5) - if 'Tbar' in UseList[G2frame.hist]['Extinction'][2]: #skipped for TOF + extSizer.Add(typeSizer,0,wx.BOTTOM,5) + if 'Tbar' in UseList[G2frame.hist]['Extinction'][2]: #skipped for TOF valSizer =wx.BoxSizer(wx.HORIZONTAL) valSizer.Add(wx.StaticText(DData,wx.ID_ANY,' Tbar(mm):'),0,WACV) tbarVal = G2G.ValidatedTxtCtrl(DData,UseList[G2frame.hist]['Extinction'][2],'Tbar', @@ -627,14 +639,14 @@ def OnExtRef(event): extSizer.Add(extVal,0,WACV) return extSizer - + def BabSizer(): - + def OnBabRef(event): Obj = event.GetEventObject() item,bab = Indx[Obj.GetId()] UseList[item]['Babinet']['Bab'+bab][1] = Obj.GetValue() - + babSizer = wx.BoxSizer(wx.HORIZONTAL) for bab in ['A','U']: babRef = wx.CheckBox(DData,wx.ID_ANY,label=' Babinet '+bab+': ') @@ -646,13 +658,13 @@ def OnBabRef(event): nDig=(10,3),xmin=0.,typeHint=float) babSizer.Add(babVal,0,WACV) return babSizer - + def FlackSizer(): - + def OnFlackRef(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Flack'][1] = Obj.GetValue() - + flackSizer = wx.BoxSizer(wx.HORIZONTAL) flackRef = wx.CheckBox(DData,wx.ID_ANY,label=' Flack parameter: ') flackRef.SetValue(UseList[G2frame.hist]['Flack'][1]) @@ -661,13 +673,13 @@ def OnFlackRef(event): flackVal = G2G.ValidatedTxtCtrl(DData,UseList[G2frame.hist]['Flack'],0,nDig=(10,3),typeHint=float) flackSizer.Add(flackVal,0,WACV) return flackSizer - + def DispSizer(): - + def OnDispRef(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Layer Disp'][1] = Obj.GetValue() - + dispSizer = wx.BoxSizer(wx.HORIZONTAL) dispRef = wx.CheckBox(DData,wx.ID_ANY,label=u' Layer displacement (\xb5m): ') dispRef.SetValue(UseList[G2frame.hist]['Layer Disp'][1]) @@ -675,9 +687,9 @@ def OnDispRef(event): dispSizer.Add(dispRef,0,WACV|wx.LEFT,5) dispSizer.Add(G2G.ValidatedTxtCtrl(DData,UseList[G2frame.hist]['Layer Disp'],0,nDig=(10,2),typeHint=float),0,WACV) return dispSizer - + def twinSizer(): - + def OnAddTwin(event): twinMat = np.array([[-1,0,0],[0,-1,0],[0,0,-1]]) #inversion by default twinVal = 0.0 @@ -687,7 +699,7 @@ def OnAddTwin(event): UseList[G2frame.hist]['Twins'].append([False,0.0]) addtwin.SetValue(False) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnMat(event): event.Skip() Obj = event.GetEventObject() @@ -699,7 +711,7 @@ def OnMat(event): uvw = UseList[G2frame.hist]['Twins'][it][0][im] UseList[G2frame.hist]['Twins'][it][0][im] = uvw Obj.SetValue('%3d %3d %3d'%(uvw[0],uvw[1],uvw[2])) - + def OnTwinVal(invalid,value,tc): it = Indx[tc.GetId()] sumTw = 0. @@ -708,16 +720,16 @@ def OnTwinVal(invalid,value,tc): sumTw += twin[1] UseList[G2frame.hist]['Twins'][0][1][0] = 1.-sumTw wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnTwinRef(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Twins'][0][1][1] = Obj.GetValue() - + def OnTwinInv(event): Obj = event.GetEventObject() it = Indx[Obj.GetId()] UseList[G2frame.hist]['Twins'][it][0] = Obj.GetValue() - + def OnTwinDel(event): Obj = event.GetEventObject() it = Indx[Obj.GetId()] @@ -732,11 +744,11 @@ def OnTwinDel(event): UseList[G2frame.hist]['Twins'][0][1][0] = 1.-sumTw if len(UseList[G2frame.hist]['Twins']) == 1: UseList[G2frame.hist]['Twins'][0][1][1] = False - wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) + nTwin = len(UseList[G2frame.hist]['Twins']) twinsizer = wx.BoxSizer(wx.VERTICAL) - topsizer = wx.BoxSizer(wx.HORIZONTAL) + topsizer = wx.BoxSizer(wx.HORIZONTAL) topsizer.Add(wx.StaticText(DData,wx.ID_ANY,' Merohedral twins: '),0,WACV) #temporary - add twin not allowed if nonmerohedral twins present # if nTwin == 1 or 'bool' not in str(type(UseList[G2frame.hist]['Twins'][1][0])): @@ -798,14 +810,14 @@ def OnTwinDel(event): valSizer.Add(twinref,0,WACV) twinsizer.Add(valSizer,0,wx.LEFT,5) return twinsizer - + def OnSelect(event): G2frame.hist = G2frame.dataWindow.HistsInPhase[DData.select.GetSelection()] oldFocus = wx.Window.FindFocus() G2plt.PlotSizeStrainPO(G2frame,data,G2frame.hist) wx.CallLater(100,RepaintHistogramInfo) if oldFocus: wx.CallAfter(oldFocus.SetFocus) - + def RepaintHistogramInfo(Scroll=0): if 'phoenix' in wx.version(): G2frame.bottomSizer.Clear(True) @@ -816,7 +828,7 @@ def RepaintHistogramInfo(Scroll=0): if GSASIIpath.GetConfigValue('debug'): print('DBG: DData window deleted. Ignoring RepaintHistogramInfo, forcing redraw') # Repaint called while DData window deleted, force redraw of entire window - import GSASIIdataGUI + from . import GSASIIdataGUI G2frame.PickIdText = '' wx.CallLater(100,GSASIIdataGUI.SelectDataTreeItem,G2frame,G2frame.GPXtree.Selection) return @@ -836,11 +848,11 @@ def RepaintHistogramInfo(Scroll=0): G2frame.dataWindow.SendSizeEvent() if LeBailMsg: G2G.G2MessageBox(G2frame,LeBailMsg,title='LeBail refinement changes') - + def ShowHistogramInfo(): '''This creates a sizer with all the information pulled out from the Phase/data dict ''' - + def OnUseData(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Use'] = Obj.GetValue() @@ -849,7 +861,7 @@ def OnUseData(event): def OnLeBail(event): '''Toggle the LeBail flag (Phase['Histograms'][hist]['LeBail'] and when turned on, set the Controls['newLeBail'] flag to indicate that - the a new Le Bail extraction will be performed at the next + the a new Le Bail extraction will be performed at the next refinement ''' UseList[G2frame.hist]['LeBail'] = not UseList[G2frame.hist]['LeBail'] @@ -858,7 +870,7 @@ def OnLeBail(event): else: Controls['newLeBail'] = False wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - + def OnResetSize(event): Obj = event.GetEventObject() item,name = Indx[Obj.GetId()] @@ -873,8 +885,8 @@ def OnResetSize(event): UseList[item]['Size'][4][i+3] = 0.0 G2plt.PlotSizeStrainPO(G2frame,data,item) wx.CallLater(100,RepaintHistogramInfo,DData.GetScrollPos(wx.VERTICAL)) - - def OnSizeAxis(event): + + def OnSizeAxis(event): event.Skip() Obj = event.GetEventObject() Saxis = Obj.GetValue().split() @@ -886,13 +898,13 @@ def OnSizeAxis(event): hkl = UseList[G2frame.hist]['Size'][3] UseList[G2frame.hist]['Size'][3] = hkl h,k,l = hkl - Obj.SetValue('%3d %3d %3d'%(h,k,l)) - + Obj.SetValue('%3d %3d %3d'%(h,k,l)) + def OnFixVals(event): Obj = event.GetEventObject() UseList[G2frame.hist]['Fix FXU'] = Obj.GetValue() - if G2frame.hist not in UseList: + if G2frame.hist not in UseList: G2frame.ErrorDialog('Missing data error', G2frame.hist+' not in GSAS-II data tree') return @@ -914,7 +926,7 @@ def OnFixVals(event): if 'Layer Disp' not in UseList[G2frame.hist]: UseList[G2frame.hist]['Layer Disp'] = [0.0,False] #end patch - ifkeV = 'E' in UseList[G2frame.hist].get('Type','') + ifkeV = 'E' in UseList[G2frame.hist].get('Type','') offMsg = '' bottomSizer = wx.BoxSizer(wx.VERTICAL) useBox = wx.BoxSizer(wx.HORIZONTAL) @@ -953,13 +965,16 @@ def OnFixVals(event): fixBox.Add(addFixed,0,WACV) addFixed.Bind(wx.EVT_BUTTON,OnAddFixed) fixedVars = UseList[G2frame.hist].get('FixedSeqVars',[]) - if len(fixedVars): + if len(fixedVars): fixBox.Add(wx.StaticText(DData,label=' (currently {} fixed)'.format(len(fixedVars))),0,WACV) bottomSizer.Add(fixBox) - + if not UseList[G2frame.hist]['LeBail'] or 'HKLF' in G2frame.hist[:4]: bottomSizer.Add(ScaleSizer(),0,wx.BOTTOM,5) - + else: + # if phase fraction/scale is hidden, turn off flag + UseList[G2frame.hist]['Scale'][1] = False + if G2frame.hist[:4] == 'PWDR': if UseList[G2frame.hist]['Size'][0] == 'isotropic': isoSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -986,7 +1001,7 @@ def OnFixVals(event): ellSizer.Add(ResetSizer('ellipsoidal',OnResetSize),0,WACV) bottomSizer.Add(ellSizer) bottomSizer.Add(EllSizeDataSizer(),0,wx.BOTTOM,5) - + if UseList[G2frame.hist]['Mustrain'][0] == 'isotropic': isoSizer = wx.BoxSizer(wx.HORIZONTAL) isoSizer.Add(TopSizer(' Mustrain model: ',['isotropic','uniaxial','generalized',], @@ -1012,11 +1027,11 @@ def OnFixVals(event): genSizer.Add(ResetSizer('generalized',OnResetStrain),0,WACV) bottomSizer.Add(genSizer) bottomSizer.Add(GenStrainDataSizer(),0,wx.BOTTOM,5) - + bottomSizer.Add(wx.StaticText(DData,wx.ID_ANY,' Hydrostatic/elastic strain:')) bottomSizer.Add(HstrainSizer()) bottomSizer.Add(DispSizer()) - + if not UseList[G2frame.hist]['LeBail'] and not ifkeV: poSizer = wx.BoxSizer(wx.VERTICAL) POData = UseList[G2frame.hist]['Pref.Ori.'] @@ -1042,7 +1057,7 @@ def OnFixVals(event): print('SHPenalty error occurred') bottomSizer.Add(poSizer) #,0,wx.TOP|wx.BOTTOM,5) bottomSizer.Add(ExtSizer('PWDR'),0,wx.TOP|wx.BOTTOM,5) - if generalData['Type'] != 'magnetic': + if generalData['Type'] != 'magnetic': bottomSizer.Add(BabSizer(),0,wx.BOTTOM,5) else: # turn off preferred orientation fitting in LeBail mode (hidden) @@ -1070,7 +1085,7 @@ def OnFixVals(event): broken = [i for i in UseList if i not in keyList] if broken: msg = 'Removing histogram(s) referenced in this phase that are no longer in project:\n\n' - for i,j in enumerate(broken): + for i,j in enumerate(broken): if i > 0: msg += ', ' msg += j del data['Histograms'][j] @@ -1086,7 +1101,7 @@ def OnFixVals(event): generalData = data['General'] cell = generalData['Cell'][1:] Amat,Bmat = G2lat.cell2AB(cell[:6]) - PhaseName = generalData['Name'] + PhaseName = generalData['Name'] SGData = generalData['SGData'] if len(G2frame.dataWindow.HistsInPhase) == 0: # no associated histograms, nothing to display here G2frame.hist = '' @@ -1095,10 +1110,10 @@ def OnFixVals(event): elif (not G2frame.hist) or (G2frame.hist not in G2frame.dataWindow.HistsInPhase): # no or bad selection but have data, take the first G2frame.hist = G2frame.dataWindow.HistsInPhase[0] Indx = {} - + if DData.GetSizer(): try: - if hasattr(DData,'select'): + if hasattr(DData,'select'): DData.select.Unbind(wx.EVT_LISTBOX) # remove binding to avoid event on Linux except: pass @@ -1123,7 +1138,7 @@ def OnFixVals(event): topSizer.Add(DData.select,0,WACV|wx.LEFT,5) if any(['PWDR' in item for item in keyList]): topSizer.Add(PlotSizer()) - mainSizer.Add(topSizer) + mainSizer.Add(topSizer) G2frame.bottomSizer,LeBailMsg = ShowHistogramInfo() mainSizer.Add(G2frame.bottomSizer) elif not keyList: @@ -1134,7 +1149,7 @@ def OnFixVals(event): ' (This phase has no associated data; use appropriate Edit/Add... menu item)'),0,wx.TOP,10) else: mainSizer.Add(wx.StaticText(DData,wx.ID_ANY,' (Strange, how did we get here?)'),0,wx.TOP,10) - + G2phG.SetPhaseWindow(DData,mainSizer,Scroll=Scroll) if LeBailMsg: G2G.G2MessageBox(G2frame,LeBailMsg,title='LeBail refinement changes') @@ -1149,7 +1164,7 @@ def MakeHistPhaseWin(G2frame): TabSelectionIdDict = {} def OnSelectPage(event): 'Called when an item is selected from the Select page menu' - tabname = TabSelectionIdDict.get(event.GetId()) # lookup phase + tabname = TabSelectionIdDict.get(event.GetId()) # lookup phase if not tabname: print ('Warning: menu item not in dict! id= %d'%event.GetId()) return @@ -1161,7 +1176,7 @@ def OnSelectPage(event): return else: print ("Warning: tab "+tabname+" was not found") - + def OnPageChanged(event): 'respond to a notebook tab' page = event.GetSelection() @@ -1170,19 +1185,19 @@ def OnPageChanged(event): def getDDataWindow(): 'Get the current scrollwindow for selected phase' return DData[HAPBook.GetSelection()] - + def getDDataPhaseinfo(): 'Get the data tree entry for the currently selected phase' page = HAPBook.GetSelection() return G2frame.GPXtree.GetItemPyData(phaseIds[page]) - + def FillDDataWindow(page): 'display the DData info' G2frame.HistPhaseLastSel = phaseList[page] data = G2frame.GPXtree.GetItemPyData(phaseIds[page]) - G2plt.PlotSizeStrainPO(G2frame,data,hist='') + G2plt.PlotSizeStrainPO(G2frame,data,hist='') UpdateDData(G2frame,DData[page],data) - + #### G2frame.dataWindow.DataMenu/"Edit Phase" menu routines follow # where these duplicate similar routines in GSASIIphsGUI.py. def OnHklfAdd(event): @@ -1199,12 +1214,12 @@ def OnHklfAdd(event): result = G2phG.CheckAddHKLF(G2frame,data) if result is None: return wx.CallAfter(UpdateDData,G2frame,getDDataWindow(),data) - + def OnDataUse(event): data = getDDataPhaseinfo() # hist = G2frame.hist if data['Histograms']: - dlg = G2G.G2MultiChoiceDialog(G2frame, 'Use histograms', + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Use histograms', 'Use which histograms?',G2frame.dataWindow.HistsInPhase) try: if dlg.ShowModal() == wx.ID_OK: @@ -1213,11 +1228,11 @@ def OnDataUse(event): if Id in sel: data['Histograms'][item]['Use'] = True else: - data['Histograms'][item]['Use'] = False + data['Histograms'][item]['Use'] = False finally: dlg.Destroy() wx.CallAfter(UpdateDData,G2frame,getDDataWindow(),data) - + def OnDataCopy(event): data = getDDataPhaseinfo() hist = G2frame.hist @@ -1229,7 +1244,7 @@ def OnDataCopy(event): sourceDict = copy.deepcopy(data['Histograms'][hist]) if 'HKLF' in sourceDict['Histogram']: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR + else: #PWDR copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','Layer Disp'] copyNames += ['Scale','Fix FXU','FixedSeqVars'] copyDict = {} @@ -1244,7 +1259,7 @@ def OnDataCopy(event): data['Histograms'][keyList[sel]].update(copy.deepcopy(copyDict)) finally: dlg.Destroy() - + def OnDataCopyFlags(event): data = getDDataPhaseinfo() hist = G2frame.hist @@ -1252,7 +1267,7 @@ def OnDataCopyFlags(event): copyDict = {} if 'HKLF' in sourceDict['Histogram']: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR + else: #PWDR copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','Layer Disp'] copyNames += ['Scale','Fix FXU','FixedSeqVars'] babNames = ['BabA','BabU'] @@ -1282,7 +1297,7 @@ def OnDataCopyFlags(event): for bab in babNames: copyDict[name][bab] = sourceDict[name][bab][1] elif name in ['Fix FXU','FixedSeqVars']: - copyDict[name] = copy.deepcopy(sourceDict[name]) + copyDict[name] = copy.deepcopy(sourceDict[name]) keyList = G2frame.dataWindow.HistsInPhase[:] if hist in keyList: keyList.remove(hist) if not keyList: @@ -1324,10 +1339,10 @@ def OnDataCopyFlags(event): for bab in babNames: data['Histograms'][item][name][bab][1] = copy.deepcopy(copyDict[name][bab]) elif name in ['Fix FXU','FixedSeqVars']: - data['Histograms'][item][name] = copy.deepcopy(sourceDict[name]) + data['Histograms'][item][name] = copy.deepcopy(sourceDict[name]) finally: dlg.Destroy() - + def OnSelDataCopy(event): data = getDDataPhaseinfo() hist = G2frame.hist @@ -1339,9 +1354,9 @@ def OnSelDataCopy(event): return if 'HKLF' in sourceDict['Histogram']: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR + else: #PWDR copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','Layer Disp'] - copyNames += ['Scale','Fix FXU','FixedSeqVars'] + copyNames += ['Scale','Fix FXU','FixedSeqVars'] dlg = G2G.G2MultiChoiceDialog(G2frame,'Select which parameters to copy', 'Select phase data parameters', copyNames) selectedItems = [] @@ -1362,8 +1377,8 @@ def OnSelDataCopy(event): for sel in dlg.GetSelections(): data['Histograms'][keyList[sel]].update(copy.deepcopy(copyDict)) finally: - dlg.Destroy() - + dlg.Destroy() + def OnPwdrAdd(event): data = getDDataPhaseinfo() generalData = data['General'] @@ -1380,7 +1395,7 @@ def OnPwdrAdd(event): if name not in keyList and 'PWDR' in name: TextList.append(name) item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) - if not TextList: + if not TextList: G2G.G2MessageBox(G2frame,'No histograms') return dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select powder histograms to use', @@ -1400,14 +1415,14 @@ def OnPwdrAdd(event): 'Mustrain':['isotropic',[1000.0,1000.0,1.0],[False,False,False],[0,0,1], NShkl*[0.01,],NShkl*[False,]], 'HStrain':[NDij*[0.0,],NDij*[False,]], - 'Layer Disp':[0.0,False], + 'Layer Disp':[0.0,False], 'Extinction':[0.0,False],'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},'Fix FXU':' ','FixedSeqVars':[]} refList = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Reflection Lists')) - refList[generalData['Name']] = {} + refList[generalData['Name']] = {} wx.CallAfter(UpdateDData,G2frame,getDDataWindow(),data) finally: dlg.Destroy() - + def OnDataDelete(event): data = getDDataPhaseinfo() if G2frame.dataWindow.HistsInPhase: @@ -1418,7 +1433,7 @@ def OnDataDelete(event): opts = extraOpts else: opts = {} - dlg = G2G.G2MultiChoiceDialog(G2frame, + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select histogram(s) to remove \nfrom this phase:', 'Remove histograms', G2frame.dataWindow.HistsInPhase, extraOpts=opts) @@ -1428,17 +1443,17 @@ def OnDataDelete(event): finally: dlg.Destroy() if extraOpts['value_0']: - for p in pd: + for p in pd: for i in DelList: if i in pd[p]['Histograms']: del pd[p]['Histograms'][i] else: for i in DelList: del data['Histograms'][i] wx.CallLater(100,UpdateDData,G2frame,getDDataWindow(),data) - + def OnDataApplyStrain(event): data = getDDataPhaseinfo() - SGData = data['General']['SGData'] + SGData = data['General']['SGData'] DijVals = data['Histograms'][G2frame.hist]['HStrain'][0][:] # apply the Dij values to the reciprocal cell newA = [] @@ -1462,7 +1477,7 @@ def OnDataApplyStrain(event): # newA.append(Aij + Dijdict.get(lbl,0.0)) # print(hist, G2lat.A2cell(newA)[:3], G2lat.calc_V(newA)) wx.CallAfter(UpdateDData,G2frame,getDDataWindow(),data) - + #### start of MakeHistPhaseWin. # binds the menu items and shows the Data Window info G2frame.dataWindow.ClearData() diff --git a/GSASII/GSASIIexprGUI.py b/GSASII/GSASIIexprGUI.py index f39fcc236..9b83fd890 100644 --- a/GSASII/GSASIIexprGUI.py +++ b/GSASII/GSASIIexprGUI.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- #GSASIIexprGUI - Expression Definition and Evaluation -'''Routines for users to input Python expressions used within +'''Routines for users to input Python expressions used within GSAS-II computations follow. ''' from __future__ import division, print_function @@ -10,11 +10,11 @@ import wx import wx.lib.scrolledpanel as wxscroll import numpy as np -import GSASIIpath -import GSASIIctrlGUI as G2G -import GSASIIobj as G2obj -import GSASIImath as G2mth -import GSASIIfiles as G2fil +from . import GSASIIpath +from . import GSASIIctrlGUI as G2G +from . import GSASIIobj as G2obj +from . import GSASIImath as G2mth +from . import GSASIIfiles as G2fil # Define a short name for convenience WACV = wx.ALIGN_CENTER_VERTICAL @@ -22,15 +22,15 @@ def IndexParmDict(parmDict,wildcard): '''Separate the parameters in parmDict into list of keys by parameter type. - + :param dict parmDict: a dict with GSAS-II parameters :param bool wildcard: True if wildcard versions of parameters should be generated and added to the lists :returns: a dict of lists where key 1 is a list of phase parameters, 2 is histogram/phase parms, 3 is histogram parms and 4 are global parameters ''' - prex = re.compile('[0-9]+::.*') - hrex = re.compile(':[0-9\*]+:.*') + prex = re.compile(r'[0-9]+::.*') + hrex = re.compile(r':[0-9\*]+:.*') parmLists = {} for i in (1,2,3,4): parmLists[i] = [] @@ -60,7 +60,7 @@ def LoadDefaultExpressions(): global defaultExpressions if defaultExpressions is not None: return # run this routine only once defaultExpressions = sorted(list(set(GSASIIpath.LoadConfigFile('DefaultExpressions.txt')))) - + #========================================================================== class ExpressionDialog(wx.Dialog): '''A wx.Dialog that allows a user to input an arbitrary expression @@ -68,7 +68,7 @@ class ExpressionDialog(wx.Dialog): To do this, the user assigns a new (free) or existing GSAS-II parameter to each parameter label used in the expression. - The free parameters can optionally be designated to be refined. + The free parameters can optionally be designated to be refined. For example, is an expression is used such as:: 'A*np.exp(-B/C)' @@ -89,7 +89,7 @@ class ExpressionDialog(wx.Dialog): the parameter value (non-float/int values in dict are ignored) :param exprObj: a :class:`GSASIIobj.ExpressionObj` object with an expression and label assignments or None (default) - :param str wintitle: String placed on title bar of dialog; + :param str wintitle: String placed on title bar of dialog; defaults to "Expression Editor" :param str header: String placed at top of dialog to tell the user what they will do here; default is "Enter restraint expression here" @@ -107,7 +107,7 @@ class ExpressionDialog(wx.Dialog): will not be used. :param list usedVars: defines a list of previously used variable names. These names will not be reused as defaults for new free variables. - (The default is an empty list). + (The default is an empty list). :param bool wildCard: If True (default), histogram names are converted to wildcard values, as is appropriate for the sequential refinement table ''' @@ -138,7 +138,7 @@ def __init__(self, parent, parmDict, exprObj=None, * If the value is 0, then the varible is "free" -- a new refineable parameter. * Values above 1 determine what variables will be shown - when the option is selected. + when the option is selected. ''' self.varName = {} 'Name assigned to each variable' @@ -164,11 +164,7 @@ def __init__(self, parent, parmDict, exprObj=None, continue # there were dicts in parmDict (should be gone now) except (TypeError,IndexError): val = parmDict[key] - if '2' in platform.python_version_tuple()[0]: - basestr = basestring - else: - basestr = str - if isinstance(val, basestr): continue + if isinstance(val, str): continue try: self.parmDict[key] = float(val) except: @@ -268,10 +264,10 @@ def __init__(self, parent, parmDict, exprObj=None, if var in self.depVarDict: self.depLabel.SetLabel(var) self.dependentVar = var - + self.exCtrl.SetValue(self.expr) self.OnValidate(None) - self.SetMinSize(defSize) + self.SetMinSize(defSize) #self.errbox.SetAutoLayout(1) #self.errbox.SetupScrolling() #self.varbox.SetAutoLayout(1) @@ -324,7 +320,7 @@ def Show(self,mode=True): return exprObj else: return None - + def setEvalResult(self,msg): 'Show a string in the expression result area' self.result.SetLabel(msg) @@ -337,7 +333,7 @@ def RestartTimer(self): if self.timer.IsRunning(): self.timer.Stop() self.timer.Start(2000,oneShot=True) - + def OnChar(self,event): '''Called as each character is entered. Cancels any running timer and starts a new one. The timer causes a check of syntax after 2 seconds @@ -349,12 +345,12 @@ def OnChar(self,event): if self.ExtraBtn: self.ExtraBtn.Disable() event.Skip() return - + def CheckVars(self): '''Check that appropriate variables are defined for each symbol used in :data:`self.expr` - :returns: a text error message or None if all needed input is present + :returns: a text error message or None if all needed input is present ''' invalid = 0 for v in self.exprVarLst: @@ -363,14 +359,14 @@ def CheckVars(self): if invalid==1: return '(a variable is not assigned)' elif invalid: - return '('+str(invalid)+' variables are not assigned)' + return f'({invalid} variables are not assigned)' msg = '' for v in self.exprVarLst: varname = self.varName.get(v) if not varname: invalid += 1 if msg: msg += "; " - msg += 'No variable for '+str(v) + msg += f'No variable for {v}' elif self.varSelect.get(v,0) > 0: if varname in self.parmDict: pass @@ -379,11 +375,11 @@ def CheckVars(self): if len(l) == 0: invalid += 1 if msg: msg += "; " - msg += 'No variables match '+str(varname) + msg += f'No variables match {varname}' elif varname not in self.parmDict: invalid += 1 if msg: msg += "; " - msg += 'No variables match '+str(varname) + msg += f'No variables match {varname}' else: # value assignment: this check is likely unneeded val = self.varValue.get(v) @@ -393,11 +389,11 @@ def CheckVars(self): invalid += 1 if msg: msg += "; " if val is None: - msg += 'No value for '+str(v) + msg += f'No value for {v}' else: - msg += 'Value '+str(val)+' invalid for '+str(v) + msg += f'Value {val} invalid for {v}' if invalid: - return '('+msg+')' + return f'({msg})' return def OnDepChoice(self,event): @@ -421,7 +417,7 @@ def GetDepVar(self): '''Returns the name of the dependent variable, when depVarDict is used. ''' return self.dependentVar - + def OnChoice(self,event): '''Respond to a selection of a variable type for a label in an expression @@ -444,7 +440,7 @@ def OnChoice(self,event): self.OnValidate(None) def SelectG2var(self,sel,var,parmList): - '''Offer a selection of a GSAS-II variable. + '''Offer a selection of a GSAS-II variable. :param int sel: Determines the type of variable to be selected. where 1 is used for Phase variables, and 2 for Histogram/Phase vars, @@ -474,7 +470,7 @@ def wildHist(var): fmt = u"{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}" varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in wildList] dlg = G2G.G2SingleChoiceDialog( - self,'Select GSAS-II parameter for variable "'+str(var)+'":', + self,f'Select GSAS-II parameter for variable "{var}":', 'Select parameter', varListlbl,monoFont=True) dlg.SetSize((625,250)) @@ -488,8 +484,8 @@ def wildHist(var): def showError(self,msg1,msg2='',msg3=''): '''Show an error message of 1 to 3 sections. The second - section is shown in an equally-spaced font. - + section is shown in an equally-spaced font. + :param str msg1: msg1 is shown in a the standard font :param str msg2: msg2 is shown in a equally-spaced (wx.MODERN) font :param str msg3: msg3 is shown in a the standard font @@ -514,7 +510,7 @@ def showError(self,msg1,msg2='',msg3=''): self.errbox.SetSizer(Siz,True) Siz.Fit(self.errbox) errMsg1.SetLabel(msg1) - errMsg2.SetLabel(" "+msg2) + errMsg2.SetLabel(f' {msg2}') errMsg2.Wrap(-1) errMsg3.SetLabel(msg3) self.Layout() @@ -527,7 +523,7 @@ def OnValidate(self,event): self.setEvalResult('(expression cannot be evaluated)') self.timer.Stop() self.expr = self.exCtrl.GetValue().strip() - if not self.expr: + if not self.expr: wx.CallAfter(self.showError, "Invalid Expression:",""," (an expression must be entered)") return @@ -614,7 +610,7 @@ def Repaint(self,exprObj): s = G2fil.FormatSigFigs(val).rstrip('0') elif '*' in var: vs = G2obj.LookupWildCard(var,self.parmDict.keys()) - s = '('+str(len(vs))+' values)' + s = f'({len(vs)} values)' else: s = '?' wid = wx.StaticText(self.varbox,wx.ID_ANY,s) @@ -634,7 +630,7 @@ def Repaint(self,exprObj): # evaluate the expression, displaying errors or the expression value try: - msg = self.CheckVars() + msg = self.CheckVars() if msg: self.setEvalResult(msg) return @@ -651,7 +647,7 @@ def Repaint(self,exprObj): calcobj.SetupCalc(self.parmDict) val = calcobj.EvalExpression() except Exception as msg: - self.setEvalResult("Error in evaluation: "+str(msg)) + self.setEvalResult(f'Error in evaluation: {msg}') return if not np.isfinite(val): self.setEvalResult("Expression value is infinite or out-of-bounds") @@ -672,7 +668,7 @@ def Repaint(self,exprObj): self.setEvalResult(f"Expression evaluates to: {s}{depVal}{msg}") self.OKbtn.Enable() if self.ExtraBtn: self.ExtraBtn.Enable() - finally: + finally: xwid,yhgt = Siz.Fit(self.varbox) self.varbox.SetMinSize((xwid,130)) self.varbox.SetAutoLayout(1) @@ -680,7 +676,7 @@ def Repaint(self,exprObj): self.varbox.Refresh() self.Layout() self.SendSizeEvent() # force repaint - + #========================================================================== class BondDialog(wx.Dialog): '''A wx.Dialog that allows a user to select a bond length to be evaluated. @@ -688,7 +684,7 @@ class BondDialog(wx.Dialog): 0. Select phase 1. Select 1st atom from dialog 2. Find neighbors & select one from dialog - 3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog + 3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog Use existing bond & esd calculate routines ''' def __init__(self, parent, Phases, parmDict, exprObj=None, @@ -696,7 +692,7 @@ def __init__(self, parent, Phases, parmDict, exprObj=None, wintitle='Select bond', VarLabel=None,depVarDict=None, ExtraButton=None,usedVars=[]): - wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, + wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.parent = parent @@ -710,7 +706,7 @@ def __init__(self, parent, Phases, parmDict, exprObj=None, self.OnSetRadii(None) else: self.Draw() - + def OnSetRadii(self,event): if 'DisAglCtls' in self.Phases[self.pName]['General']: DisAglCtls = copy.deepcopy(self.Phases[self.pName]['General']['DisAglCtls']) @@ -721,7 +717,7 @@ def OnSetRadii(self,event): self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData() dlg.Destroy() wx.CallAfter(self.Draw) - + def Draw(self): 'paints the distance dialog window' def OnPhase(event): @@ -732,12 +728,12 @@ def OnPhase(event): self.OnSetRadii(None) else: wx.CallAfter(self.Draw) - + def OnOrigAtom(event): Obj = event.GetEventObject() self.Oatom = Obj.GetValue() wx.CallAfter(self.Draw) - + def OnTargAtom(event): Obj = event.GetEventObject() self.Tatom = Obj.GetValue() @@ -784,7 +780,7 @@ def OnTargAtom(event): targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom) else: targAtom = wx.StaticText(self.panel,label='(none in search range)') - atomSizer.Add(targAtom,0,WACV) + atomSizer.Add(targAtom,0,WACV) mainSizer.Add(atomSizer) OkBtn = wx.Button(self.panel,-1,"Ok") @@ -798,7 +794,7 @@ def OnTargAtom(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() @@ -816,8 +812,8 @@ def OnOk(self,event): def OnCancel(self,event): parent = self.GetParent() parent.Raise() - self.EndModal(wx.ID_CANCEL) - + self.EndModal(wx.ID_CANCEL) + #========================================================================== class AngleDialog(wx.Dialog): '''A wx.Dialog that allows a user to select a bond angle to be evaluated. @@ -825,7 +821,7 @@ class AngleDialog(wx.Dialog): 0. Select phase 1. Select 1st atom from dialog 2. Find neighbors & select two from dialog - 3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog + 3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog Use existing angle & esd calculate routines ''' def __init__(self, parent, Phases, parmDict, exprObj=None, @@ -833,7 +829,7 @@ def __init__(self, parent, Phases, parmDict, exprObj=None, wintitle='Select angle', VarLabel=None,depVarDict=None, ExtraButton=None,usedVars=[]): - wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, + wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.parent = parent @@ -858,7 +854,7 @@ def OnSetRadii(self,event): self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData() dlg.Destroy() wx.CallAfter(self.Draw) - + def Draw(self): 'paints the angle dialog window' def OnPhase(event): @@ -869,11 +865,11 @@ def OnPhase(event): self.OnSetRadii(None) else: wx.CallAfter(self.Draw) - + def OnOrigAtom(event): Obj = event.GetEventObject() self.Oatom = Obj.GetValue() - wx.CallAfter(self.Draw) + wx.CallAfter(self.Draw) def OnTargAtoms(event): Obj = event.GetEventObject() @@ -907,7 +903,7 @@ def OnTargAtoms(event): origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames, style=wx.CB_READONLY|wx.CB_DROPDOWN) origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom) - atomSizer.Add(origAtom,0,WACV) + atomSizer.Add(origAtom,0,WACV) mainSizer.Add(atomSizer) atomSizer = wx.BoxSizer(wx.HORIZONTAL) atomSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV) @@ -946,7 +942,7 @@ def OnTargAtoms(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() @@ -964,9 +960,9 @@ def OnOk(self,event): def OnCancel(self,event): parent = self.GetParent() parent.Raise() - self.EndModal(wx.ID_CANCEL) - - + self.EndModal(wx.ID_CANCEL) + + if __name__ == "__main__": app = wx.PySimpleApp() # create the App frm = wx.Frame(None) @@ -978,14 +974,14 @@ def OnCancel(self,event): header="Edit the PseudoVar expression", fit=False, depVarDict=PSvarDict, - #VarLabel="New PseudoVar", + #VarLabel="New PseudoVar", ) newobj = dlg.Show(True) dlg = ExpressionDialog(frm,indepvarDict,newobj, header="Edit the PseudoVar expression", fit=False, depVarDict=PSvarDict, - #VarLabel="New PseudoVar", + #VarLabel="New PseudoVar", ) newobj = dlg.Show(True) print (dlg.GetDepVar()) @@ -997,10 +993,7 @@ def OnCancel(self,event): #app.MainLoop() - if '2' in platform.python_version_tuple()[0]: - import cPickle - else: - import pickle as cPickle + import pickle def showEQ(calcobj): print print (calcobj.eObj.expression) @@ -1018,9 +1011,9 @@ def showEQ(calcobj): calcobj.SetupCalc(parmDict2) showEQ(calcobj) fp = open('/tmp/obj.pickle','w') - cPickle.dump(obj,fp) + pickle.dump(obj,fp) fp.close() - + obj.expression = "A*np.exp(-2/B)" obj.assgnVars = {'A': '0::Afrac:0', 'B': '0::Afrac:1'} obj.freeVars = {} @@ -1030,7 +1023,7 @@ def showEQ(calcobj): showEQ(calcobj) fp = open('/tmp/obj.pickle','r') - obj = cPickle.load(fp) + obj = pickle.load(fp) fp.close() parmDict2 = {'0::Afrac:0':0.0, '0::Afrac:1': 1.0} calcobj = G2obj.ExpressionCalcObj(obj) @@ -1041,4 +1034,3 @@ def showEQ(calcobj): calcobj = G2obj.ExpressionCalcObj(obj) calcobj.SetupCalc(parmDict2) showEQ(calcobj) - diff --git a/GSASII/GSASIIfiles.py b/GSASII/GSASIIfiles.py index e668f92a6..8723c90ed 100644 --- a/GSASII/GSASIIfiles.py +++ b/GSASII/GSASIIfiles.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- ''' -Code for accessing files, including support for reading and writing +Code for accessing files, including support for reading and writing instrument parameter files and exporting various types of data files. This module has some routines that require wxPython, but imports -for wx and GSAS-II GUI routines is done on a per-function basis so +for wx and GSAS-II GUI routines is done on a per-function basis so that this module can be imported for GSASIIscriptable use when wx is not installed. ''' @@ -13,15 +13,17 @@ import os import sys import glob -import inspect +#import inspect import re import numpy as np -import GSASIIpath -import GSASIIlattice as G2lat -import GSASIIstrIO as G2stIO -import GSASIImapvars as G2mv +from . import GSASIIpath +from . import GSASIIlattice as G2lat +from . import GSASIIstrIO as G2stIO +from . import GSASIImapvars as G2mv +from . import GSASIImath as G2mth +#from . import GSASIIlattice as G2lat #if not sys.platform.startswith('win'): # try: @@ -47,24 +49,24 @@ def sfloat(S): return 0.0 G2printLevel = 'all' -'''This defines the level of output from calls to :func:`GSASIIfiles.G2Print`, -which should be used in place of print() within GSASII where possible. +'''This defines the level of output from calls to :func:`GSASIIfiles.G2Print`, +which should be used in place of print() within GSASII where possible. Settings for this are 'all', 'warn', 'error' or 'none'. Best to change this with :func:`G2SetPrintLevel`. .. seealso:: - :func:`G2Print` + :func:`G2Print` :func:`G2SetPrintLevel`. ''' def G2SetPrintLevel(level): - '''Set the level of output from calls to :func:`G2Print`, which should - be used in place of print() within GSASII. Settings for the mode are + '''Set the level of output from calls to :func:`G2Print`, which should + be used in place of print() within GSASII. Settings for the mode are 'all', 'warn', 'error' or 'none' - - :param str level: a string used to set the print level, which may be + + :param str level: a string used to set the print level, which may be 'all', 'warn', 'error' or 'none'. - Note that capitalization and extra letters in level are ignored, so + Note that capitalization and extra letters in level are ignored, so 'Warn', 'warnings', etc. will all set the mode to 'warn' ''' global G2printLevel @@ -75,21 +77,21 @@ def G2SetPrintLevel(level): else: G2Print('G2SetPrintLevel Error: level={} cannot be interpreted.', 'Use all, warn, error or none.') - + def find(name, path): '''find 1st occurance of file in path ''' for root, dirs, files in os.walk(path): if name in files: return os.path.join(root, name) - + def G2Print(*args,**kwargs): '''Print with filtering based level of output (see :func:`G2SetPrintLevel`). - Use G2Print() as replacement for print(). + Use G2Print() as replacement for print(). :param str mode: if specified, this should contain the mode for printing - ('error', 'warn' or anything else). If not specified, the first argument - of the print command (args[0]) should contain the string 'error' for + ('error', 'warn' or anything else). If not specified, the first argument + of the print command (args[0]) should contain the string 'error' for error messages and 'warn' for warning messages (capitalization and additional letters ignored.) ''' @@ -98,7 +100,7 @@ def G2Print(*args,**kwargs): testStr = str(args[0]).lower() else: testStr = kwargs['mode'][:].lower() - del kwargs['mode'] + del kwargs['mode'] level = 2 for i,mode in enumerate(('error', 'warn')): if mode in testStr: @@ -120,6 +122,47 @@ def get_python_versions(packagelist): ' ' + platform.machine()]) return versions +ImportErrors = [] +condaRequestList = {} +def ImportErrorMsg(errormsg=None,pkg={}): + '''Store error message(s) from loading importers (usually missing + packages. Or, report back all messages, if called with no argument. + + :param str errormsg: a string containing the error message. If not + supplied, the function returns the error message(s). + :param dict pkg: a dict where the key is the name of the importer and the + value is a list containing the packages that need to be installed to + allow the importer to be used. + + :returns: the error messages as a list (an empty list if there are none), + only if errormsg is None (the default). + ''' + if errormsg is None: + return ImportErrors + ImportErrors.append(errormsg) + if pkg: NeededPackage(pkg) + +def NeededPackage(pkgDict): + '''Store packages that are needed to add functionality to GSAS-II + + :param dict pkgDict: a dict where the key is the describes the routine + or the unavailable functionality that requires addition of a Python + package and the value is a list containing the packages that need to + be installed. If a specific version of a package is required then + indicate that by placing version information into the name (see + https://docs.conda.io/projects/conda/en/4.6.0/_downloads/52a95608c49671267e40c689e0bc00ca/conda-cheatsheet.pdf). Note that the names containing "pkg=x.y" + will be translated to "pkg==x.y" for pip installs, but more complex + package specifications (see + https://pip.pypa.io/en/latest/reference/requirement-specifiers/) will + probably work only for conda installations. + + Examples:: + + {'MIDAS Zarr importer':['zarr=2.18.*']} + {'HDF5 image importer':['h5py','hdf5']} + ''' + condaRequestList.update(pkgDict) + def makeInstDict(names,data,codes): inst = dict(zip(names,zip(data,data,codes))) for item in inst: @@ -298,25 +341,25 @@ def SetPowderInstParms(Iparm, rd): def ReadInstprm(instLines, bank, Sample={}): '''Read contents of a GSAS-II (new) .instprm instrument parameter file - :param list instLines: contents of GSAS-II parameter file as a + :param list instLines: contents of GSAS-II parameter file as a list of str; N.B. lines can be concatenated with ';' - :param int bank: bank number to use when instprm file has values - for multiple banks (noted by headers of '#BANK n:...'.). This - is ignored for instprm files without those headers. + :param int bank: bank number to use when instprm file has values + for multiple banks (noted by headers of '#BANK n:...'.). This + is ignored for instprm files without those headers. If bank is None with multiple banks, the first bank is used. - Note that multibank .instprm files are made by - a "Save all profile" command in Instrument Parameters. - :param dict Sample: A dict containing sample parameters, + Note that multibank .instprm files are made by + a "Save all profile" command in Instrument Parameters. + :param dict Sample: A dict containing sample parameters, typically corresponding to rd.Sample, - where rd is a reader object that - is being read from. Sample parameters - determined by instrument settings or information + where rd is a reader object that + is being read from. Sample parameters + determined by instrument settings or information from the instprm file are placed here. - :returns: bank,instdict where bank is the sample parameter set - number and instdict is the instrument parameter dict + :returns: bank,instdict where bank is the sample parameter set + number and instdict is the instrument parameter dict - Note if 'Type' is set as Debye-Scherrer or Bragg-Brentano this will be used and - will set defaults in the sample parameters. Otherwise, a single-wavelength file + Note if 'Type' is set as Debye-Scherrer or Bragg-Brentano this will be used and + will set defaults in the sample parameters. Otherwise, a single-wavelength file will set Debye-Scherrer mode and dual wavelength will set Bragg-Brentano. ''' if 'GSAS-II' not in instLines[0]: @@ -428,7 +471,7 @@ def WriteInstprm(fp, InstPrm, Sample={}, bank=None): :param dict InstPrm: Instrument parameters :param dict Sample: Sample parameters (optional) :param int bank: Bank number. If not None (default), this causes - a "#Bank" heading to be placed in the file before the + a "#Bank" heading to be placed in the file before the parameters are written. ''' if bank is not None: @@ -447,153 +490,192 @@ def WriteInstprm(fp, InstPrm, Sample={}, bank=None): if not Sample.get(item): continue fp.write(f"{indent}{item}:{Sample[item]}\n") -def LoadImportRoutines(prefix, errprefix=None, traceback=False): - '''Routine to locate GSASII importers matching a prefix string. - - Warns if more than one file with the same name is in the path - or if a file is found that is not in the main directory tree. - ''' - if errprefix is None: - errprefix = prefix +# version of LoadImportRoutines from before switch to "main" +# def _old_LoadImportRoutines(prefix, errprefix=None, traceback=False): +# '''Routine to locate GSASII importers matching a prefix string. +# +# Warns if more than one file with the same name is in the path +# or if a file is found that is not in the main directory tree. +# ''' +# if errprefix is None: +# errprefix = prefix +# +# readerlist = [] +# import_files = {} +# if '.' not in sys.path: sys.path.append('.') +# for path in sys.path: +# for filename in glob.iglob(os.path.join(path, 'G2'+prefix+'*.py')): +# pkg = os.path.splitext(os.path.split(filename)[1])[0] +# if pkg in import_files: +# G2Print('Warning: importer {} overrides {}'.format(import_files[pkg],os.path.abspath(filename))) +# elif not filename.startswith(GSASIIpath.path2GSAS2): +# G2Print('Note: found importer in non-standard location:'+ +# f'\n\t{os.path.abspath(filename)}') +# import_files[pkg] = filename +# else: +# import_files[pkg] = filename + +# for pkg in sorted(import_files.keys()): +# try: +# exec('import '+pkg) +# #print(eval(pkg+'.__file__')) +# for name, value in inspect.getmembers(eval(pkg)): +# if name.startswith('_'): +# continue +# if inspect.isclass(value): +# for method in 'Reader', 'ExtensionValidator', 'ContentsValidator': +# if not hasattr(value, method): +# break +# if not callable(getattr(value, method)): +# break +# else: +# reader = value() +# if reader.UseReader: +# readerlist.append(reader) +# except AttributeError: +# G2Print ('Import_' + errprefix + ': Attribute Error ' + import_files[pkg]) +# if traceback: +# traceback.print_exc(file=sys.stdout) +# except Exception as exc: +# G2Print ('\nImport_' + errprefix + ': Error importing file ' + import_files[pkg]) +# G2Print (u'Error message: {}\n'.format(exc)) +# if traceback: +# traceback.print_exc(file=sys.stdout) +# return readerlist +def LoadImportRoutines(prefix, errprefix=None, traceback=False): + from . import imports readerlist = [] - import_files = {} - if '.' not in sys.path: sys.path.append('.') - for path in sys.path: - for filename in glob.iglob(os.path.join(path, 'G2'+prefix+'*.py')): - pkg = os.path.splitext(os.path.split(filename)[1])[0] - if pkg in import_files: - G2Print('Warning: importer {} overrides {}'.format(import_files[pkg],os.path.abspath(filename))) - elif not filename.startswith(GSASIIpath.path2GSAS2): - G2Print('Note: found importer in non-standard location:'+ - f'\n\t{os.path.abspath(filename)}') - import_files[pkg] = filename - else: - import_files[pkg] = filename - - for pkg in sorted(import_files.keys()): - try: - exec('import '+pkg) - #print(eval(pkg+'.__file__')) - for name, value in inspect.getmembers(eval(pkg)): - if name.startswith('_'): - continue - if inspect.isclass(value): - for method in 'Reader', 'ExtensionValidator', 'ContentsValidator': - if not hasattr(value, method): - break - if not callable(getattr(value, method)): - break - else: - reader = value() - if reader.UseReader: - readerlist.append(reader) - except AttributeError: - G2Print ('Import_' + errprefix + ': Attribute Error ' + import_files[pkg]) - if traceback: - traceback.print_exc(file=sys.stdout) - except Exception as exc: - G2Print ('\nImport_' + errprefix + ': Error importing file ' + import_files[pkg]) - G2Print (u'Error message: {}\n'.format(exc)) - if traceback: - traceback.print_exc(file=sys.stdout) + # how to import directly from a file for extra search magic if we need + # https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path + # TODO handle non-bundled readers + for mod_name in (_ for _ in dir(imports) if _.startswith(f'G2{prefix}')): + mod = getattr(imports, mod_name) + for member_name in dir(mod): + if member_name.startswith('_'): + continue + member = getattr(mod, member_name) + if all( + hasattr(member, meth) + for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator') + ): + reader = member() + if reader.UseReader: + readerlist.append(reader) return readerlist -ImportErrors = [] -condaRequestList = {} -def ImportErrorMsg(errormsg=None,pkg={}): - '''Store error message(s) from loading importers (usually missing - packages. Or, report back all messages, if called with no argument. - - :param str errormsg: a string containing the error message. If not - supplied, the function returns the error message(s). - :param dict pkg: a dict where the key is the name of the importer and the - value is a list containing the packages that need to be installed to - allow the importer to be used. - - :returns: the error messages as a list (an empty list if there are none), - only if errormsg is None (the default). - ''' - if errormsg is None: - return ImportErrors - ImportErrors.append(errormsg) - if pkg: condaRequestList.update(pkg) - -def LoadExportRoutines(parent, traceback=False): - '''Routine to locate GSASII exporters. Warns if more than one file - with the same name is in the path or if a file is found that is not - in the main directory tree. - ''' +# version of LoadExportRoutines from before switch to "main" +# def _LoadExportRoutines(parent, usetraceback=False): +# '''Routine to locate GSASII exporters. Warns if more than one file +# with the same name is in the path or if a file is found that is not +# in the main directory tree. +# ''' +# exporterlist = [] +# export_files = {} +# if '.' not in sys.path: sys.path.append('.') +# for path in sys.path: +# for filename in glob.iglob(os.path.join(path,"G2export*.py")): +# pkg = os.path.splitext(os.path.split(filename)[1])[0] +# if pkg in export_files: +# G2Print('Warning: exporter {} overrides {}'.format(export_files[pkg],os.path.abspath(filename))) +# elif not filename.startswith(GSASIIpath.path2GSAS2): +# G2Print('Note, found non-standard exporter: {}'.format(os.path.abspath(filename))) +# export_files[pkg] = filename +# else: +# export_files[pkg] = filename +# # go through the routines and import them, saving objects that +# # have export routines (method Exporter) +# for pkg in sorted(export_files.keys()): +# try: +# exec('import '+pkg) +# for clss in inspect.getmembers(eval(pkg)): # find classes defined in package +# if clss[0].startswith('_'): continue +# if not inspect.isclass(clss[1]): continue +# # check if we have the required methods +# if not hasattr(clss[1],'Exporter'): continue +# if not callable(getattr(clss[1],'Exporter')): continue +# if parent is None: +# if not hasattr(clss[1],'Writer'): continue +# else: +# if not hasattr(clss[1],'loadParmDict'): continue +# if not callable(getattr(clss[1],'loadParmDict')): continue +# try: +# exporter = clss[1](parent) # create an export instance +# except AttributeError: +# pass +# except Exception as exc: +# G2Print ('\nExport init: Error substantiating class ' + clss[0]) +# G2Print (u'Error message: {}\n'.format(exc)) +# if usetraceback: +# import traceback +# traceback.print_exc(file=sys.stdout) +# continue +# exporterlist.append(exporter) +# except AttributeError: +# G2Print ('Export Attribute Error ' + export_files[pkg]) +# if usetraceback: +# import traceback +# traceback.print_exc(file=sys.stdout) +# except Exception as exc: +# G2Print ('\nExport init: Error importing file ' + export_files[pkg]) +# G2Print (u'Error message: {}\n'.format(exc)) +# if usetraceback: +# import traceback +# traceback.print_exc(file=sys.stdout) +# return exporterlist + +def LoadExportRoutines(parent, usetraceback=False): + from . import exports exporterlist = [] - export_files = {} - if '.' not in sys.path: sys.path.append('.') - for path in sys.path: - for filename in glob.iglob(os.path.join(path,"G2export*.py")): - pkg = os.path.splitext(os.path.split(filename)[1])[0] - if pkg in export_files: - G2Print('Warning: exporter {} overrides {}'.format(export_files[pkg],os.path.abspath(filename))) - elif not filename.startswith(GSASIIpath.path2GSAS2): - G2Print('Note, found non-standard exporter: {}'.format(os.path.abspath(filename))) - export_files[pkg] = filename + # how to import directly from a file for extra search magic if we need + # https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path + # TODO handle non-bundled readers + for mod_name in (_ for _ in dir(exports) if _.startswith('G2export')): + mod = getattr(exports, mod_name) + for member_name in dir(mod): + if member_name.startswith('_'): + continue + member = getattr(mod, member_name) + if not hasattr(member, 'Exporter'): + continue + if parent is None: + if not hasattr(member, 'Writer'): + continue else: - export_files[pkg] = filename - # go through the routines and import them, saving objects that - # have export routines (method Exporter) - for pkg in sorted(export_files.keys()): - try: - exec('import '+pkg) - for clss in inspect.getmembers(eval(pkg)): # find classes defined in package - if clss[0].startswith('_'): continue - if not inspect.isclass(clss[1]): continue - # check if we have the required methods - if not hasattr(clss[1],'Exporter'): continue - if not callable(getattr(clss[1],'Exporter')): continue - if parent is None: - if not hasattr(clss[1],'Writer'): continue - else: - if not hasattr(clss[1],'loadParmDict'): continue - if not callable(getattr(clss[1],'loadParmDict')): continue - try: - exporter = clss[1](parent) # create an export instance - except AttributeError: - pass - except Exception as exc: - G2Print ('\nExport init: Error substantiating class ' + clss[0]) - G2Print (u'Error message: {}\n'.format(exc)) - if traceback: - traceback.print_exc(file=sys.stdout) + if not hasattr(member, 'loadParmDict'): continue + try: + exporter = member(parent) exporterlist.append(exporter) - except AttributeError: - G2Print ('Export Attribute Error ' + export_files[pkg]) - if traceback: - traceback.print_exc(file=sys.stdout) - except Exception as exc: - G2Print ('\nExport init: Error importing file ' + export_files[pkg]) - G2Print (u'Error message: {}\n'.format(exc)) - if traceback: - traceback.print_exc(file=sys.stdout) + except Exception as exc: + G2Print ('\nExport init: Error substantiating class ' + member_name) + G2Print ('Error message: {}\n'.format(exc)) + if usetraceback: + import traceback + traceback.print_exc(file=sys.stdout) + continue return exporterlist + def readColMetadata(imagefile): '''Reads image metadata from a column-oriented metadata table (1-ID style .par file). Called by :func:`GetColumnMetadata` - + The .par file has any number of columns separated by spaces. The directory for the file must be specified in Config variable :data:`config_example.Column_Metadata_directory`. As an index to the .par file a second "label file" must be specified with the same file root name as the .par file but the extension must be .XXX_lbls (where .XXX is the extension of the image) or if that is not present extension - .lbls. + .lbls. :param str imagefile: the full name of the image file (with extension, directory optional) :returns: a dict with parameter values. Named parameters will have the type based on the specified Python function, named columns will be character strings - + The contents of the label file will look like this:: - + # define keywords filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34 distance: float | 75 @@ -612,16 +694,16 @@ def readColMetadata(imagefile): This file contains three types of lines in any order. * Named parameters are evaluated with user-supplied Python code (see - subsequent information). Specific named parameters are used + subsequent information). Specific named parameters are used to determine values that are used for image interpretation (see table, below). Any others are copied to the Comments subsection of the Image - tree item. + tree item. * Column labels are defined with a column number (integer) followed by a colon (:) and a label to be assigned to that column. All labeled columns are copied to the Image's Comments subsection. * Comments are any line that does not contain a colon. - Note that columns are numbered starting at zero. + Note that columns are numbered starting at zero. Any named parameter may be defined provided it is not a valid integer, but the named parameters in the table have special meanings, as descibed. @@ -632,20 +714,20 @@ def readColMetadata(imagefile): Note that several keywords, if defined in the Comments, will be found and placed in the appropriate section of the powder histogram(s)'s Sample Parameters after an integration: ``Temperature``, ``Pressure``, ``Time``, - ``FreePrm1``, ``FreePrm2``, ``FreePrm3``, ``Omega``, ``Chi``, and ``Phi``. + ``FreePrm1``, ``FreePrm2``, ``FreePrm3``, ``Omega``, ``Chi``, and ``Phi``. After the Python code, supply a vertical bar (|) and then a list of one more more columns that will be supplied as arguments to that function. Note that the labels for the three FreePrm items can be changed by including that label as a third item with an additional vertical bar. Labels - will be ignored for any other named parameters. - + will be ignored for any other named parameters. + The examples above are discussed here: ``filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34`` Here the function to be used is defined with a lambda statement:: - + lambda x,y: "{}_{:0>6}".format(x,y) This function will use the format function to create a file name from the @@ -660,15 +742,15 @@ def readColMetadata(imagefile): Here a third parameter is used to specify the number of images generated, where the image number is incremented for each image. - + ``distance: float | 75`` Here the contents of column 75 will be converted to a floating point number by calling float on it. Note that the spaces here are ignored. - + ``wavelength:lambda keV: 12.398425/float(keV)|9`` Here we define an algebraic expression to convert an energy in keV to a wavelength and pass the contents of column 9 as that input energy - + ``pixelSize:lambda x: [74.8, 74.8]|0`` In this case the pixel size is a constant (a list of two numbers). The first column is passed as an argument as at least one argument is required, but that @@ -679,20 +761,20 @@ def readColMetadata(imagefile): and formats them in a different way. This parameter is not one of the pre-defined parameter names below. Some external code could be used to change the month string (argument ``m``) to a integer from 1 to 12. - + ``FreePrm2: int | 34 | Free Parm2 Label`` In this example, the contents of column 34 will be converted to an integer and placed as the second free-named parameter in the Sample Parameters after an integration. The label for this parameter will be changed to "Free Parm2 Label". - + **Pre-defined parameter names** - + ============= ========= ======== ===================================================== keyword required type Description ============= ========= ======== ===================================================== filename yes str or generates the file name prefix for the matching image list file (MyImage001 for file /tmp/MyImage001.tif) or - a list of file names. + a list of file names. polarization no float generates the polarization expected based on the monochromator angle, defaults to 0.99. center no list of generates the approximate beam center on the detector @@ -700,10 +782,10 @@ def readColMetadata(imagefile): distance yes float generates the distance from the sample to the detector in mm pixelSize no list of generates the size of the pixels in microns such as - 2 floats [200.0, 200.0]. + 2 floats [200.0, 200.0]. wavelength yes float generates the wavelength in Angstroms ============= ========= ======== ===================================================== - + ''' dir,fil = os.path.split(os.path.abspath(imagefile)) imageName,ext = os.path.splitext(fil) @@ -774,7 +856,7 @@ def readColMetadataLabels(lblFil): if len(items) < 2: continue # lines with no colon are also comments # does this line a definition for a named parameter? key = items[0] - try: + try: int(key) except ValueError: # try as named parameter since not a valid number items = line.split(':',1)[1].split('|') @@ -823,9 +905,9 @@ def evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,ShowError=False): def GetColumnMetadata(reader): '''Add metadata to an image from a column-type metadata file using :func:`readColMetadata` - + :param reader: a reader object from reading an image - + ''' if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return parParms = readColMetadata(reader.readfilename) @@ -875,7 +957,7 @@ def LoadControls(Slines,data): save[key] = eval(val) else: vals = val.strip('[] ').split() - save[key] = [float(vals[0]),float(vals[1])] + save[key] = [float(vals[0]),float(vals[1])] elif key in cntlList: save[key] = eval(val) data.update(save) @@ -908,7 +990,7 @@ def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''): First image is read if None (default). :param str formatName: the image reader formatName - :returns: an image as a numpy array. + :returns: an image as a numpy array. Formerly if imageOnly=False this would return a list of four items: Comments, Data, Npix and the Image, but now an exception occurs when imageOnly=False. ''' @@ -917,7 +999,7 @@ def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''): secondaryReaders = [] for rd in G2frame.ImportImageReaderlist: flag = rd.ExtensionValidator(imagefile) - if flag is None: + if flag is None: secondaryReaders.append(rd) elif flag: if not FormatName: @@ -935,7 +1017,7 @@ def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''): rd.errors = "" # clear out any old errors if not rd.ContentsValidator(imagefile): # rejected on cursory check errorReport += "\n "+rd.formatName + ' validator error' - if rd.errors: + if rd.errors: errorReport += ': '+rd.errors continue if imageOnly: @@ -969,11 +1051,11 @@ def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''): else: print('Error reading file '+imagefile) print('Error messages(s)\n'+errorReport) - raise Exception('No image read') + raise Exception('No image read') def RereadImageData(ImageReaderlist,imagefile,ImageTag=None,FormatName=''): - '''Read a single image with an image importer. This is called to - reread an image after it has already been imported, so it is not + '''Read a single image with an image importer. This is called to + reread an image after it has already been imported, so it is not necessary to reload metadata. Based on :func:`GetImageData` which this can replace @@ -992,7 +1074,7 @@ def RereadImageData(ImageReaderlist,imagefile,ImageTag=None,FormatName=''): secondaryReaders = [] for rd in ImageReaderlist: flag = rd.ExtensionValidator(imagefile) - if flag is None: + if flag is None: secondaryReaders.append(rd) elif flag: if not FormatName: @@ -1010,7 +1092,7 @@ def RereadImageData(ImageReaderlist,imagefile,ImageTag=None,FormatName=''): rd.errors = "" # clear out any old errors if not rd.ContentsValidator(imagefile): # rejected on cursory check errorReport += "\n "+rd.formatName + ' validator error' - if rd.errors: + if rd.errors: errorReport += ': '+rd.errors continue flag = rd.Reader(imagefile,None,blocknum=ImageTag) @@ -1040,7 +1122,7 @@ def readMasks(filename,masks,ignoreThreshold): [key,val] = S.strip().split(':',1) if key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds','Xlines','Ylines']: if ignoreThreshold and key == 'Thresholds': - S = File.readline() + S = File.readline() continue save[key] = eval(val) if key == 'Thresholds': @@ -1055,15 +1137,15 @@ def readMasks(filename,masks,ignoreThreshold): masks[key] = [i for i in masks[key] if len(i)] def PDFWrite(PDFentry,fileroot,PDFsaves,PDFControls,Inst={},Limits=[]): - '''Write PDF-related data (G(r), S(Q),...) into files, as + '''Write PDF-related data (G(r), S(Q),...) into files, as selected. - :param str PDFentry: name of the PDF entry in the tree. This is - used for comments in the file specifying where it came from; + :param str PDFentry: name of the PDF entry in the tree. This is + used for comments in the file specifying where it came from; it can be arbitrary - :param str fileroot: name of file(s) to be written. The extension + :param str fileroot: name of file(s) to be written. The extension will be ignored. - :param list PDFsaves: flags that determine what type of file will be + :param list PDFsaves: flags that determine what type of file will be written: PDFsaves[0], if True writes a I(Q) file with a .iq extension PDFsaves[1], if True writes a S(Q) file with a .sq extension @@ -1071,13 +1153,13 @@ def PDFWrite(PDFentry,fileroot,PDFsaves,PDFControls,Inst={},Limits=[]): PDFsaves[3], if True writes a G(r) file with a .gr extension PDFsaves[4], if True writes G(r) in a pdfGUI input file with a .gr extension. Note that if PDFsaves[3] and PDFsaves[4] are - both True, the pdfGUI overwrites the G(r) file. + both True, the pdfGUI overwrites the G(r) file. PDFsaves[5], if True writes F(Q) & g(R) with .fq & .gr extensions overwrites these if selected by option 2, 3 or 4 :param dict PDFControls: The PDF parameters and computed results - :param dict Inst: Instrument parameters from the PDWR entry used + :param dict Inst: Instrument parameters from the PDWR entry used to compute the PDF. Needed only when PDFsaves[4] is True. - :param list Limits: Computation limits from the PDWR entry used + :param list Limits: Computation limits from the PDWR entry used to compute the PDF. Needed only when PDFsaves[4] is True. ''' import scipy.interpolate as scintp @@ -1139,8 +1221,6 @@ def PDFWrite(PDFentry,fileroot,PDFsaves,PDFControls,Inst={},Limits=[]): G2Print (' G(R) saved to: '+grfilename) if PDFsaves[4]: #pdfGUI file for G(R) - import GSASIImath as G2mth - import GSASIIlattice as G2lat grfilename = fileroot+'.gr' grdata = PDFControls['G(R)'][1] qdata = PDFControls['I(Q)'][1][0] @@ -1188,14 +1268,14 @@ def PDFWrite(PDFentry,fileroot,PDFsaves,PDFControls,Inst={},Limits=[]): grfile.write('\n') grfile.write('#### start data\n') grfile.write('#S 1\n') - grfile.write('#L r($\\AA$) G($\\AA^{-2}$)\n') + grfile.write('#L r($\\AA$) G($\\AA^{-2}$)\n') for r,gr in grnew: grfile.write("%15.2F %15.6F\n" % (r,gr)) grfile.close() G2Print (' G(R) saved to: '+grfilename) - + if len(PDFsaves) > 5 and PDFsaves[5]: #RMCProfile files for F(Q) & g(r) overwrites any above - + fqfilename = fileroot+'.fq' fqdata = PDFControls['F(Q)'][1] fqfxn = scintp.interp1d(fqdata[0],fqdata[1],kind='linear') @@ -1211,7 +1291,7 @@ def PDFWrite(PDFentry,fileroot,PDFsaves,PDFControls,Inst={},Limits=[]): fqfile.write("%15.6g %15.6g\n" % (q,fq)) fqfile.close() G2Print (' F(Q) saved to: '+fqfilename) - + grfilename = fileroot+'.gr' grdata = PDFControls['g(r)'][1] grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear') @@ -1260,8 +1340,8 @@ def FormulaEval(string): to be evaluated. :returns: the value for the expression as a float or None if the expression does not - evaluate to a valid number. - + evaluate to a valid number. + ''' try: val = float(eval(string)) @@ -1291,21 +1371,21 @@ def FormatPadValue(val,maxdigits=None): return s else: return s+' ' - + def FormatValue(val,maxdigits=None): '''Format a float to fit in at most ``maxdigits[0]`` spaces with maxdigits[1] after decimal. - Note that this code has been hacked from FormatSigFigs and may have unused sections. + Note that this code has been hacked from FormatSigFigs and may have unused sections. :param float val: number to be formatted. :param list maxdigits: the number of digits, places after decimal and 'f' or 'g' to be used for display of the number (defaults to [10,2,'f']). - :returns: a string with <= maxdigits characters (usually). + :returns: a string with <= maxdigits characters (usually). ''' if 'str' in str(type(val)) and (val == '?' or val == '.'): - return val + return val if maxdigits is None: digits = [10,2,'f'] else: @@ -1341,7 +1421,7 @@ def FormatValue(val,maxdigits=None): decimals = min(digits[0]-3,digits[1]) fmt = "{" + (":{:d}.{:d}f".format(digits[0],decimals))+"}" else: # in range where g formatting should do what I want - # used? + # used? decimals = digits[0] - 6 fmt = "{" + (":{:d}.{:d}g".format(digits[0],decimals))+"}" try: @@ -1363,19 +1443,19 @@ def FormatSigFigs(val, maxdigits=10, sigfigs=5, treatAsZero=1e-20): :param float treatAsZero: numbers that are less than this in magnitude are treated as zero. Defaults to 1.0e-20, but this can be disabled - if set to None. + if set to None. - :returns: a string with <= maxdigits characters (I hope). + :returns: a string with <= maxdigits characters (I hope). ''' if 'str' in str(type(val)) and (val == '?' or val == '.'): - return val + return val if treatAsZero is not None: if abs(val) < treatAsZero: return '0.0' # negative numbers, leave room for a sign if np.isnan(val): return str(val) - if val < 0: maxdigits -= 1 + if val < 0: maxdigits -= 1 if abs(val) < 1e-99 or abs(val) > 9.999e99: decimals = min(maxdigits-6,sigfigs) fmt = "{" + (":{:d}.{:d}g".format(maxdigits,decimals))+"}" # create format string @@ -1393,7 +1473,7 @@ def FormatSigFigs(val, maxdigits=10, sigfigs=5, treatAsZero=1e-20): fmt = "{" + (":{:d}.{:d}f".format(maxdigits,decimals))+"}" else: # larger numbers, remove decimal places decimals = sigfigs - 1 - int(np.log10(np.abs(val))) - if decimals <= 0: + if decimals <= 0: fmt = "{" + (":{:d}.0f".format(maxdigits))+"}." else: fmt = "{" + (":{:d}.{:d}f".format(maxdigits,decimals))+"}" @@ -1404,28 +1484,29 @@ def FormatSigFigs(val, maxdigits=10, sigfigs=5, treatAsZero=1e-20): return str(val) #=========================================================================== +# N.B. duplicated in GSASIIpath def openInNewTerm(project=None,g2script=None,pythonapp=sys.executable): - '''Open a new and independent GSAS-II session in separate terminal + '''Open a new and independent GSAS-II session in separate terminal or console window and as a separate process that will continue even if the calling process exits. - Intended to work on all platforms. + Intended to work on all platforms. This could be used to run other scripts inside python other than GSAS-II :param str project: the name of an optional parameter to be - passed to the script (usually a .gpx file to be opened in + passed to the script (usually a .gpx file to be opened in a new GSAS-II session) :param str g2script: the script to be run. If None (default) - the GSASII.py file in the same directory as this file will - be used. - :param str pythonapp: the Python interpreter to be used. + the G2.py file in the same directory as this file will + be used. + :param str pythonapp: the Python interpreter to be used. Defaults to sys.executable which is usually what is wanted. :param str terminal: a name for a preferred terminal emulator ''' import subprocess if g2script is None: - g2script = os.path.join(os.path.dirname(__file__),'GSASII.py') - + g2script = os.path.join(os.path.dirname(__file__),'G2.py') + if sys.platform == "darwin": if project: script = f''' @@ -1498,7 +1579,7 @@ def openInNewTerm(project=None,g2script=None,pythonapp=sys.executable): elif term == "terminology": cmds = [term,'-T="GSAS-II console"','--hold','-e'] script = "echo; echo This window can now be closed" - break + break else: print("No known terminal was found to use, Can't start {}") return @@ -1517,15 +1598,15 @@ def openInNewTerm(project=None,g2script=None,pythonapp=sys.executable): subprocess.Popen(cmds,start_new_session=True) def CleanupFromZip(label,cleanupList): - '''Delete files extracted from a zip archive, typically created with - :func:`GSASIIctrl.ExtractFileFromZip` during the data import process. + '''Delete files extracted from a zip archive, typically created with + :func:`GSASIIctrl.ExtractFileFromZip` during the data import process. - Note that image files should not be deleted as they will be reused + Note that image files should not be deleted as they will be reused every time the image is displayed, but everything else will be saved in the data tree and the file copy is not needed. :param str label: for imports, this is the type of file being read - :param list cleanupList: a list of files that have been extracted from + :param list cleanupList: a list of files that have been extracted from the zip archive and can be deleted. ''' if not cleanupList: return @@ -1554,7 +1635,7 @@ def trim(val): '''Simplify a string containing leading and trailing spaces as well as newlines, tabs, repeated spaces etc. into a shorter and more simple string, by replacing all ranges of whitespace - characters with a single space. + characters with a single space. :param str val: the string to be simplified @@ -1606,7 +1687,7 @@ def __init__(self,G2frame,formatName,extension,longFormatName=None,): self.filename = None # name of file to be written (single export) or template (multiple files) self.dirname = '' # name of directory where file(s) will be written self.fullpath = '' # name of file being written -- full path - + # items that should be defined in a subclass of this class self.exporttype = [] # defines the type(s) of exports that the class can handle. # The following types are defined: 'project', "phase", "powder", "single" @@ -1615,7 +1696,7 @@ def __init__(self,G2frame,formatName,extension,longFormatName=None,): def InitExport(self,event): '''Determines the type of menu that called the Exporter and - misc initialization. + misc initialization. ''' self.filename = None # name of file to be written (single export) self.dirname = '' # name of file to be written (multiple export) @@ -1646,7 +1727,7 @@ def ExportSelect(self,AskFile='ask'): :param bool AskFile: Determines how this routine processes getting a location to store the current export(s). - + * if AskFile is 'ask' (default option), get the name of the file to be written; self.filename and self.dirname are always set. In the case where multiple files must be generated, the export routine should do this @@ -1658,7 +1739,7 @@ def ExportSelect(self,AskFile='ask'): multiple items will be written (as multiple files) are used *or* a complete file name is requested when a single file name is selected. self.dirname is always set and self.filename used - only when a single file is selected. + only when a single file is selected. * if AskFile is 'default', creates a name of the file to be used from the name of the project (.gpx) file. If the project has not been saved, then the name of file is requested. @@ -1671,9 +1752,9 @@ def ExportSelect(self,AskFile='ask'): :returns: True in case of an error ''' - + numselected = 1 - import GSASIIctrlGUI as G2G + from . import GSASIIctrlGUI as G2G if self.currentExportType == 'phase': if len(self.Phases) == 0: self.G2frame.ErrorDialog( @@ -1682,7 +1763,7 @@ def ExportSelect(self,AskFile='ask'): return True elif len(self.Phases) == 1: self.phasenam = list(self.Phases.keys()) - elif self.multiple: + elif self.multiple: choices = sorted(self.Phases.keys()) phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=True) if phasenum is None: return True @@ -1798,7 +1879,7 @@ def ExportSelect(self,AskFile='ask'): mapPhases = [] choices = [] for phasenam in sorted(self.Phases): - phasedict = self.Phases[phasenam] # pointer to current phase info + phasedict = self.Phases[phasenam] # pointer to current phase info if len(phasedict['General']['Map'].get('rho',[])): mapPhases.append(phasenam) if phasedict['General']['Map'].get('Flip'): @@ -1817,7 +1898,7 @@ def ExportSelect(self,AskFile='ask'): return True elif len(mapPhases) == 1: self.phasenam = mapPhases - else: + else: phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple) if self.multiple: if not phasenum: return True @@ -1827,7 +1908,7 @@ def ExportSelect(self,AskFile='ask'): self.phasenam = [mapPhases[phasenum]] numselected = len(self.phasenam) - # items selected, now set self.dirname and usually self.filename + # items selected, now set self.dirname and usually self.filename if AskFile == 'ask' or (AskFile == 'single' and numselected == 1) or ( AskFile == 'default' and not self.G2frame.GSASprojectfile ): @@ -1854,9 +1935,9 @@ def loadParmDict(self,computeSU=False): Expands the parm & sig dicts to include values derived from constraints. This could be made faster for sequential fits as info for each histogram is loaded - later when iterating over histograms. + later when iterating over histograms. ''' - import GSASIIdataGUI as G2gd + from . import GSASIIdataGUI as G2gd self.G2frame.CheckNotebook() self.parmDict = {} self.sigDict = {} # dict with s.u. values, currently used only for CIF & Bracket exports @@ -1913,7 +1994,7 @@ def loadParmDict(self,computeSU=False): else: varyList = list(varyList) # add symmetry-generated constraints - rigidbodyDict = self.G2frame.GPXtree.GetItemPyData( + rigidbodyDict = self.G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Rigid bodies')) rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]}) rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False) # done twice, needed? @@ -1939,7 +2020,7 @@ def loadParmDict(self,computeSU=False): self.sigDict.update( {i:v[1] for i,v in self.OverallParms['Covariance']['depSigDict'].items()}) # compute the s.u.'s on rigid bodies - import GSASIIstrMath as G2stMth + from . import GSASIIstrMath as G2stMth self.RBsuDict = G2stMth.computeRBsu(self.parmDict,Phases,rigidbodyDict, covDict['covMatrix'],covDict['varyList'],covDict['sig']) @@ -1947,14 +2028,14 @@ def loadTree(self): '''Load the contents of the data tree into a set of dicts (self.OverallParms, self.Phases and self.Histogram as well as self.powderDict & self.xtalDict) - + * The childrenless data tree items are overall parameters/controls for the entire project and are placed in self.OverallParms * Phase items are placed in self.Phases * Data items are placed in self.Histogram. The key for these data items begin with a keyword, such as PWDR, IMG, HKLF,... that identifies the data type. ''' - import GSASIIdataGUI as G2gd + from . import GSASIIdataGUI as G2gd self.OverallParms = {} self.powderDict = {} self.sasdDict = {} @@ -1966,7 +2047,7 @@ def loadTree(self): self.SeqRefhist = None self.DelayOpen = False if self.G2frame.GPXtree.IsEmpty(): return # nothing to do - histType = None + histType = None if self.currentExportType == 'phase': # if exporting phases load them here sub = G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Phases') @@ -1978,7 +2059,7 @@ def loadTree(self): phaseName = self.G2frame.GPXtree.GetItemText(item) self.Phases[phaseName] = self.G2frame.GPXtree.GetItemPyData(item) item, cookie = self.G2frame.GPXtree.GetNextChild(sub, cookie) - # Get rigid body info into self.OverallParms + # Get rigid body info into self.OverallParms for key in ('Rigid bodies','Covariance'): item = G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,key) if item: @@ -2007,32 +2088,32 @@ def loadTree(self): name = name[:-1] + '10' elif name[-1] in '012345678': name = name[:-1] + str(int(name[-1])+1) - else: + else: name += '-1' self.Histograms[name] = {} # the main info goes into Data, but the 0th # element contains refinement results, carry - # that over too now. + # that over too now. self.Histograms[name]['Data'] = self.G2frame.GPXtree.GetItemPyData(item)[1] self.Histograms[name][0] = self.G2frame.GPXtree.GetItemPyData(item)[0] item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item) - while item2: + while item2: child = self.G2frame.GPXtree.GetItemText(item2) self.Histograms[name][child] = self.G2frame.GPXtree.GetItemPyData(item2) item2, cookie2 = self.G2frame.GPXtree.GetNextChild(item, cookie2) item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie) # index powder and single crystal histograms by number for hist in self.Histograms: - if hist.startswith("PWDR"): + if hist.startswith("PWDR"): d = self.powderDict - elif hist.startswith("HKLF"): + elif hist.startswith("HKLF"): d = self.xtalDict elif hist.startswith("SASD"): d = self.sasdDict elif hist.startswith("REFD"): d = self.refdDict else: - return + return i = self.Histograms[hist].get('hId') if i is None and not d.keys(): i = 0 @@ -2049,15 +2130,15 @@ def loadTree(self): self.OverallParms[name] = self.G2frame.GPXtree.GetItemPyData(item) else: item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item) - if not item2: + if not item2: self.OverallParms[name] = self.G2frame.GPXtree.GetItemPyData(item) item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie) # index powder and single crystal histograms for hist in self.Histograms: i = self.Histograms[hist]['hId'] - if hist.startswith("PWDR"): + if hist.startswith("PWDR"): self.powderDict[i] = hist - elif hist.startswith("HKLF"): + elif hist.startswith("HKLF"): self.xtalDict[i] = hist elif hist.startswith("SASD"): self.sasdDict[i] = hist @@ -2090,14 +2171,14 @@ def defaultSaveFile(self): return os.path.abspath( os.path.splitext(self.G2frame.GSASprojectfile )[0]+self.extension) - + def askSaveFile(self): '''Ask the user to supply a file name :returns: a file name (str) or None if Cancel is pressed ''' - import GSASIIctrlGUI as G2G + from . import GSASIIctrlGUI as G2G #pth = G2G.GetExportPath(self.G2frame) if self.G2frame.GSASprojectfile: defnam = os.path.splitext( @@ -2109,14 +2190,14 @@ def askSaveFile(self): def askSaveDirectory(self): '''Ask the user to supply a directory name. Path name is used as the - starting point for the next export path search. + starting point for the next export path search. :returns: a directory name (str) or None if Cancel is pressed TODO: Can this be replaced with G2G.askSaveDirectory? ''' import wx - import GSASIIctrlGUI as G2G + from . import GSASIIctrlGUI as G2G pth = G2G.GetExportPath(self.G2frame) dlg = wx.DirDialog( self.G2frame, 'Input directory where file(s) will be written', pth, @@ -2132,16 +2213,16 @@ def askSaveDirectory(self): dlg.Destroy() return filename - # Tools for file writing. + # Tools for file writing. def OpenFile(self,fil=None,mode='w',delayOpen=False): '''Open the output file :param str fil: The name of the file to open. If None (default) the name defaults to self.dirname + self.filename. If an extension is supplied, it is not overridded, - but if not, the default extension is used. - :param str mode: The mode can 'w' to write a file, or 'a' to append to it. If - the mode is 'd' (for debug), output is displayed on the console. + but if not, the default extension is used. + :param str mode: The mode can 'w' to write a file, or 'a' to append to it. If + the mode is 'd' (for debug), output is displayed on the console. :returns: the file object opened by the routine which is also saved as self.fp ''' @@ -2170,24 +2251,24 @@ def openDelayed(self,mode='w'): def Write(self,line): '''write a line of output, attaching a line-end character - :param str line: the text to be written. + :param str line: the text to be written. ''' if self.fp is None: raise Exception('Attempt to Write without use of OpenFile') self.fp.write(line+'\n') - + def CloseFile(self,fp=None): '''Close a file opened in OpenFile :param file fp: the file object to be closed. If None (default) - file object self.fp is closed. + file object self.fp is closed. ''' if self.fp is None and self.DelayOpen: - if GSASIIpath.GetConfigValue('debug'): + if GSASIIpath.GetConfigValue('debug'): print('Delayed open: close before uncreated file') return if self.fp is None: - if GSASIIpath.GetConfigValue('debug'): + if GSASIIpath.GetConfigValue('debug'): raise Exception('Attempt to CloseFile without use of OpenFile') else: print('Attempt to CloseFile without use of OpenFile') @@ -2197,7 +2278,7 @@ def CloseFile(self,fp=None): fp = self.fp self.fp = None if fp is not None: fp.close() - + def SetSeqRef(self,data,hist): '''Set the exporter to retrieve results from a sequential refinement rather than the main tree @@ -2227,7 +2308,7 @@ def GetCell(self,phasenam,unique=False): :param str phasenam: the name for the selected phase :param bool unique: when True, only directly refined parameters - (a in cubic, a & alpha in rhombohedral cells) are assigned + (a in cubic, a & alpha in rhombohedral cells) are assigned positive s.u. values. Used as True for CIF generation. :returns: `cellList,cellSig` where each is a 7 element list corresponding to a, b, c, alpha, beta, gamma, volume where `cellList` has the @@ -2239,14 +2320,14 @@ def GetCell(self,phasenam,unique=False): try: pfx = str(phasedict['pId'])+'::' A,sigA = G2stIO.cellFill(pfx,phasedict['General']['SGData'],self.parmDict,self.sigDict) - cellSig = G2stIO.getCellEsd(pfx,phasedict['General']['SGData'],A, + cellSig = G2lat.getCellEsd(pfx,phasedict['General']['SGData'],A, self.OverallParms['Covariance'],unique=unique) # returns 7 vals, includes sigVol cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),) return cellList,cellSig except KeyError: cell = phasedict['General']['Cell'][1:] return cell,7*[0] - + def GetSeqCell(self,phasenam,data_name): """Gets the unit cell parameters and their s.u.'s for a selected phase and histogram in a sequential fit @@ -2280,8 +2361,8 @@ def GetSeqCell(self,phasenam,data_name): 'varyList': [Dlookup.get(striphist(v),v) for v in data_name['varyList']], 'covMatrix': data_name['covMatrix'] } - return list(G2lat.A2cell(A)) + [G2lat.calc_V(A)], G2stIO.getCellEsd(str(pId)+'::',SGdata,A,covData) - + return list(G2lat.A2cell(A)) + [G2lat.calc_V(A)], G2lat.getCellEsd(str(pId)+'::',SGdata,A,covData) + def GetAtoms(self,phasenam): """Gets the atoms associated with a phase. Can be used with standard or macromolecular phases @@ -2300,10 +2381,10 @@ def GetAtoms(self,phasenam): U\\ :sub:`11`, U\\ :sub:`22`, U\\ :sub:`33`, U\\ :sub:`12`, U\\ :sub:`13` & U\\ :sub:`23` paired with their standard uncertainty (or a negative value) """ - phasedict = self.Phases[phasenam] # pointer to current phase info + phasedict = self.Phases[phasenam] # pointer to current phase info cx,ct,cs,cia = phasedict['General']['AtomPtrs'] cfrac = cx+3 - fpfx = str(phasedict['pId'])+'::Afrac:' + fpfx = str(phasedict['pId'])+'::Afrac:' atomslist = [] for i,at in enumerate(phasedict['Atoms']): if phasedict['General']['Type'] == 'macromolecular': diff --git a/GSASII/GSASIIfpaGUI.py b/GSASII/GSASIIfpaGUI.py index 44bde23e4..01e7c8e20 100644 --- a/GSASII/GSASIIfpaGUI.py +++ b/GSASII/GSASIIfpaGUI.py @@ -9,15 +9,15 @@ import wx import wx.lib.scrolledpanel as wxscroll -import NIST_profile as FP - -import GSASIIpath -import GSASIIctrlGUI as G2G -import GSASIIdataGUI as G2gd -import GSASIIplot as G2plt -import GSASIImath as G2mth -import GSASIIpwd as G2pwd -import GSASIIfiles as G2fil +from . import NIST_profile as FP + +from . import GSASIIpath +from . import GSASIIctrlGUI as G2G +from . import GSASIIdataGUI as G2gd +from . import GSASIIplot as G2plt +from . import GSASIImath as G2mth +from . import GSASIIpwd as G2pwd +from . import GSASIIfiles as G2fil WACV = wx.ALIGN_CENTER_VERTICAL simParms = {} @@ -25,13 +25,13 @@ ''' parmDict = {} -'''Parameter dict used for reading Topas-style values. These are +'''Parameter dict used for reading Topas-style values. These are converted to SI units and placed into :data:`NISTparms` ''' NISTparms = {} -'''Parameters in a nested dict, with an entry for each concolutor. Entries in -those dicts have values in SI units (of course). NISTparms can be +'''Parameters in a nested dict, with an entry for each concolutor. Entries in +those dicts have values in SI units (of course). NISTparms can be can be input directly or can be from created from :data:`parmDict` by :func:`XferFPAsettings` ''' @@ -47,7 +47,7 @@ ('sample_thickness', 1., 'Depth of sample (mm)'), ('convolution_steps', 8, 'Number of Fourier-space bins per two-theta step'), ('source_width', 0.04,'Tube filament width, in projection at takeoff angle (mm)'), - ('tube-tails_L-tail', -1.,'Left-side tube tails width, in projection (mm)'), + ('tube-tails_L-tail', -1.,'Left-side tube tails width, in projection (mm)'), ('tube-tails_R-tail', 1.,'Right-side tube tails width, in projection (mm)'), ('tube-tails_rel-I', 0.001,'Tube tails fractional intensity (no units)'), ] @@ -58,13 +58,13 @@ BBPointDetector = [ ('receiving_slit_width', 0.2, 'Width of receiving slit (mm)'),] -'''Additional FPA dict entries used in :func:`FillParmSizer` +'''Additional FPA dict entries used in :func:`FillParmSizer` needed for Bragg Brentano instruments with point detectors. ''' BBPSDDetector = [ ('SiPSD_th2_angular_range', 3.0, 'Angular range observed by PSD (degrees 2Theta)'),] -'''Additional FPA dict entries used in :func:`FillParmSizer` +'''Additional FPA dict entries used in :func:`FillParmSizer` needed for Bragg Brentano instruments with linear (1-D) Si PSD detectors. ''' @@ -79,17 +79,12 @@ ] '''Additional FPA dict entries used in :func:`FillParmSizer`, needed for Incident Beam Monochromator ''' - -Citation = '''MH Mendenhall, K Mullen && JP Cline (2015), J. Res. of NIST, 120, p223. DOI: 10.6028/jres.120.014 - -For Incident Beam Mono model, also cite: MH Mendenhall, D Black && JP Cline (2019), J. Appl. Cryst., 52, p1087. DOI: 10.1107/S1600576719010951 -''' IBmono = False '''set to True if an incident beam monochromator is in use ''' DetMode = 'BBpoint' -'''The type of detector, either 'BBpoint' for Bragg-Brentano point detector or +'''The type of detector, either 'BBpoint' for Bragg-Brentano point detector or or 'BBPSD' (linear) position sensitive detector ''' @@ -109,7 +104,7 @@ def SetCu6wave(): parmDict['lwidth'] = {i:v for i,v in enumerate((0.436, 0.487, 0.63, 0.558, 2.93, 2.93,))} def SetMonoWave(): - '''Eliminates the short-wavelength line from the six-line Cu K + '''Eliminates the short-wavelength line from the six-line Cu K alpha spectrum when incident beam mono; resets it to 6 if no mono ''' if IBmono and len(parmDict['wave']) == 6: @@ -122,9 +117,9 @@ def SetMonoWave(): def writeNIST(filename): '''Write the NIST FPA terms into a JSON-like file that can be reloaded in _onReadFPA - ''' + ''' if not filename: return - + fp = open(filename,'w') fp.write('# parameters to be used in the NIST XRD Fundamental Parameters program\n') fp.write('{\n') @@ -134,7 +129,7 @@ def writeNIST(filename): fp.write('\n') fp.write('}\n') fp.close() - + #SetCu2Wave() # use these as default SetCu6wave() # use these as default SetMonoWave() @@ -157,7 +152,7 @@ def FillParmSizer(): else: raise Exception('Unknown DetMode in FillParmSizer: '+DetMode) if IBmono: - itemList += IBmonoParms + itemList += IBmonoParms text = wx.StaticText(prmPnl,wx.ID_ANY,'label',style=wx.ALIGN_CENTER, size=(170,-1)) # make just a bit bigger than largest item in column text.SetBackgroundColour(wx.WHITE) @@ -186,14 +181,14 @@ def FillParmSizer(): prmSizer.Layout() FPdlg = prmPnl.GetParent() FPdlg.SendSizeEvent() - + def MakeTopasFPASizer(G2frame,FPdlg,SetButtonStatus): - '''Create a GUI with parameters for the NIST XRD Fundamental Parameters Code. + '''Create a GUI with parameters for the NIST XRD Fundamental Parameters Code. Parameter input is modeled after Topas input parameters. :param wx.Frame G2frame: main GSAS-II window :param wx.Window FPdlg: Frame or Dialog where GUI will appear - :param SetButtonStatus: a callback function to call to see what buttons in + :param SetButtonStatus: a callback function to call to see what buttons in this windows can be enabled. Called with done=True to trigger closing the parent window as well. :returns: a sizer with the GUI controls @@ -319,10 +314,10 @@ def _onSaveFPA(event): btn.Bind(wx.EVT_BUTTON,_onSetCu6wave) MainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER, 0) MainSizer.Add((-1,5)) - + btnsizer = wx.GridBagSizer( 2, 5) btnsizer.Add( wx.StaticText(FPdlg, wx.ID_ANY, 'Detector type'), - (0,0), (2,1), wx.ALIGN_CENTER | wx.ALL, 5) + (0,0), (2,1), wx.ALIGN_CENTER | wx.ALL, 5) detBtn1 = wx.RadioButton(FPdlg,wx.ID_ANY,'Point',style=wx.RB_GROUP) detBtn1.SetValue(DetMode == 'BBpoint') btnsizer.Add(detBtn1, (0,1)) @@ -331,9 +326,9 @@ def _onSaveFPA(event): detBtn2.SetValue(not DetMode == 'BBpoint') btnsizer.Add(detBtn2, (1,1)) detBtn2.Bind(wx.EVT_RADIOBUTTON,_onSetDetBtn) - btnsizer.Add( (40,-1), (0,2), (1,1), wx.ALIGN_CENTER | wx.ALL, 5) + btnsizer.Add( (40,-1), (0,2), (1,1), wx.ALIGN_CENTER | wx.ALL, 5) btnsizer.Add( wx.StaticText(FPdlg, wx.ID_ANY, 'Incident Beam Mono'), - (0,3), (2,1), wx.ALIGN_CENTER | wx.ALL, 5) + (0,3), (2,1), wx.ALIGN_CENTER | wx.ALL, 5) monoBtn1 = wx.RadioButton(FPdlg,wx.ID_ANY,'No',style=wx.RB_GROUP) monoBtn1.SetValue(not IBmono) btnsizer.Add(monoBtn1, (0,4)) @@ -349,11 +344,11 @@ def _onSaveFPA(event): style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER) prmSizer = wx.FlexGridSizer(cols=3,hgap=3,vgap=5) prmPnl.SetSizer(prmSizer) - FillParmSizer() + FillParmSizer() MainSizer.Add(prmPnl,1,wx.EXPAND,1) prmPnl.SetAutoLayout(1) prmPnl.SetupScrolling() - + MainSizer.Add((-1,4)) btnsizer = wx.BoxSizer(wx.HORIZONTAL) btn = wx.Button(FPdlg, wx.ID_ANY, 'Plot peak') @@ -363,7 +358,7 @@ def _onSaveFPA(event): if 'plotpos' not in simParms: simParms['plotpos'] = simParms['minTT'] ctrl = G2G.ValidatedTxtCtrl(FPdlg,simParms,'plotpos',size=(70,-1)) btnsizer.Add(ctrl) - btnsizer.Add(wx.StaticText(FPdlg,wx.ID_ANY,' deg. ')) + btnsizer.Add(wx.StaticText(FPdlg,wx.ID_ANY,' deg. ')) saveBtn = wx.Button(FPdlg, wx.ID_ANY,'Save FPA dict') btnsizer.Add(saveBtn) saveBtn.Bind(wx.EVT_BUTTON,_onSaveFPA) @@ -373,7 +368,7 @@ def _onSaveFPA(event): OKbtn = wx.Button(FPdlg, wx.ID_OK) OKbtn.SetDefault() btnsizer.Add(OKbtn) - Cbtn = wx.Button(FPdlg, wx.ID_CLOSE,"Cancel") + Cbtn = wx.Button(FPdlg, wx.ID_CLOSE,"Cancel") btnsizer.Add(Cbtn) MainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER, 0) MainSizer.Add((-1,4)) @@ -387,16 +382,16 @@ def _onSaveFPA(event): px,py = prmSizer.GetSize() dx,dy = FPdlg.GetSize() FPdlg.SetMinSize((-1,-1)) - FPdlg.SetMaxSize((-1,-1)) + FPdlg.SetMaxSize((-1,-1)) FPdlg.SetMinSize((dx,dy+200)) # leave a min of 200 points for scroll panel - FPdlg.SetMaxSize((max(dx,700),850)) + FPdlg.SetMaxSize((max(dx,700),850)) FPdlg.SetSize((max(dx,px+20),min(750,dy+py+30))) # 20 for scroll bar, 30 for a bit of room at bottom - + def XferFPAsettings(InpParms): '''convert Topas-type parameters to SI units for NIST and place in a dict sorted according to use in each convoluter - :param dict InpParms: a dict with Topas-like parameters, as set in + :param dict InpParms: a dict with Topas-like parameters, as set in :func:`MakeTopasFPASizer` :returns: a nested dict with global parameters and those for each convolution ''' @@ -404,11 +399,11 @@ def XferFPAsettings(InpParms): for key in "tube_tails","absorption","si_psd","displacement","receiver_slit": if key in NISTparms: del NISTparms[key] - + keys = list(InpParms['wave'].keys()) source_wavelengths_m = 1.e-10 * np.array([InpParms['wave'][i] for i in keys]) la = [InpParms['int'][i] for i in keys] - + if IBmono: # kludge: apply mono_slit_attenuation since it is not part of NIST FPA code norm = [InpParms['mono_slit_attenuation'] if (1.5443 < InpParms['wave'][i] < 1.5447) else 1. @@ -418,7 +413,7 @@ def XferFPAsettings(InpParms): source_intensities = np.array(la)/max(la) source_lor_widths_m = 1.e-10 * 1.e-3 * np.array([InpParms['lwidth'][i] for i in keys]) source_gauss_widths_m = 1.e-10 * 1.e-3 * np.array([0.001 for i in keys]) - + NISTparms["emission"] = {'emiss_wavelengths' : source_wavelengths_m, 'emiss_intensities' : source_intensities, 'emiss_gauss_widths' : source_gauss_widths_m, @@ -438,13 +433,13 @@ def XferFPAsettings(InpParms): 'tail_left' : -1e-3 * InpParms.get('tube-tails_L-tail',0.), 'tail_right' : 1e-3 * InpParms.get('tube-tails_R-tail',0.), 'tail_intens' : InpParms.get('tube-tails_rel-I',0.),} - - if InpParms['filament_length'] == InpParms['receiving_slit_length']: # workaround: + + if InpParms['filament_length'] == InpParms['receiving_slit_length']: # workaround: InpParms['receiving_slit_length'] *= 1.00001 # avoid bug when slit lengths are identical NISTparms["axial"] = { 'axDiv':"full", 'slit_length_source' : 1e-3*InpParms['filament_length'], 'slit_length_target' : 1e-3*InpParms['receiving_slit_length'], - 'length_sample' : 1e-3 * InpParms['sample_length'], + 'length_sample' : 1e-3 * InpParms['sample_length'], 'n_integral_points' : 10, 'angI_deg' : InpParms['soller_angle'], 'angD_deg': InpParms['soller_angle'] @@ -478,11 +473,11 @@ def XferFPAsettings(InpParms): } def setupFPAcalc(): - '''Create a peak profile object using the NIST XRD Fundamental - Parameters Code. - - :returns: a profile object that can provide information on - each convolution or compute the composite peak shape. + '''Create a peak profile object using the NIST XRD Fundamental + Parameters Code. + + :returns: a profile object that can provide information on + each convolution or compute the composite peak shape. ''' if IBmono: p=FP.FP_windowed(anglemode="twotheta", @@ -492,7 +487,7 @@ def setupFPAcalc(): p=FP.FP_profile(anglemode="twotheta", output_gaussian_smoother_bins_sigma=1.0, oversampling=NISTparms.get('oversampling',10)) - + p.debug_cache=False #set parameters for each convolver for key in NISTparms: @@ -501,11 +496,11 @@ def setupFPAcalc(): else: p.set_parameters(**NISTparms[key]) return p - + def doFPAcalc(NISTpk,ttArr,twotheta,calcwid,step): '''Compute a single peak using a NIST profile object - :param object NISTpk: a peak profile computational object from the + :param object NISTpk: a peak profile computational object from the NIST XRD Fundamental Parameters Code, typically established from a call to :func:`SetupFPAcalc` :param np.Array ttArr: an evenly-spaced grid of two-theta points (degrees) @@ -523,13 +518,13 @@ def doFPAcalc(NISTpk,ttArr,twotheta,calcwid,step): return center_bin_idx,NISTpk.compute_line_profile() def MakeSimSizer(G2frame, dlg): - '''Create a GUI to get simulation with parameters for Fundamental - Parameters fitting. + '''Create a GUI to get simulation with parameters for Fundamental + Parameters fitting. :param wx.Window dlg: Frame or Dialog where GUI will appear - :returns: a sizer with the GUI controls - + :returns: a sizer with the GUI controls + ''' def _onOK(event): msg = '' @@ -682,7 +677,7 @@ def FitFPApeaks(ttArr, intArr, peaklist, maxPtsHM): wx.EndBusyCursor() # save Iparms pth = G2G.GetExportPath(G2frame) - fldlg = wx.FileDialog(G2frame, 'Set name to save GSAS-II instrument parameters file', pth, '', + fldlg = wx.FileDialog(G2frame, 'Set name to save GSAS-II instrument parameters file', pth, '', 'instrument parameter files (*.instprm)|*.instprm',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if fldlg.ShowModal() == wx.ID_OK: @@ -696,7 +691,7 @@ def FitFPApeaks(ttArr, intArr, peaklist, maxPtsHM): finally: fldlg.Destroy() #GSASIIpath.IPyBreak() - + def _onClose(event): dlg.Destroy() def SetButtonStatus(done=False): @@ -710,12 +705,12 @@ def _onSetFPA(event): MakeTopasFPASizer(G2frame,FPdlg,SetButtonStatus) FPdlg.CenterOnParent() FPdlg.Raise() - FPdlg.Show() + FPdlg.Show() def _onSaveFPA(event): filename = G2G.askSaveFile(G2frame,'','.NISTfpa', 'dict of NIST FPA values',dlg) writeNIST(filename) - + def _onReadFPA(event): filename = G2G.GetImportFile(G2frame, message='Read file with dict of values for NIST Fundamental Parameters', @@ -745,7 +740,7 @@ def _onReadFPA(event): topSizer.Add(G2G.HelpButton(dlg,helpIndex='FPA')) MainSizer.Add(topSizer,0,wx.EXPAND) G2G.HorizontalLine(MainSizer,dlg) - MainSizer.Add((5,5),0) + MainSizer.Add((5,5),0) prmSizer = wx.FlexGridSizer(cols=2,hgap=3,vgap=5) text = wx.StaticText(dlg,wx.ID_ANY,'value',style=wx.ALIGN_CENTER) text.SetBackgroundColour(wx.WHITE) @@ -782,14 +777,16 @@ def _onReadFPA(event): readBtn.Bind(wx.EVT_BUTTON,_onReadFPA) MainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER, 0) MainSizer.Add((-1,4),1,wx.EXPAND,1) - txt = wx.StaticText(dlg,wx.ID_ANY,'If you use this, please cite: '+Citation,size=(350,-1)) + txt = wx.StaticText(dlg,wx.ID_ANY,'If you use this, please cite: '+ + G2G.GetCite('Fundamental parameter fitting'), + size=(350,-1)) txt.Wrap(340) MainSizer.Add(txt,0,wx.ALIGN_CENTER) btnsizer = wx.BoxSizer(wx.HORIZONTAL) OKbtn = wx.Button(dlg, wx.ID_OK) OKbtn.SetDefault() btnsizer.Add(OKbtn) - Cbtn = wx.Button(dlg, wx.ID_CLOSE,"Cancel") + Cbtn = wx.Button(dlg, wx.ID_CLOSE,"Cancel") btnsizer.Add(Cbtn) MainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER, 0) MainSizer.Add((-1,4),1,wx.EXPAND,1) @@ -803,7 +800,7 @@ def _onReadFPA(event): dlg.SetMinSize(dlg.GetSize()) dlg.SendSizeEvent() dlg.Raise() - + def GetFPAInput(G2frame): dlg = wx.Dialog(G2frame,wx.ID_ANY,'FPA input', style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) @@ -811,19 +808,19 @@ def GetFPAInput(G2frame): dlg.CenterOnParent() dlg.Show() return - + if __name__ == "__main__": app = wx.PySimpleApp() GSASIIpath.InvokeDebugOpts() frm = wx.Frame(None) # create a frame frm.Show(True) frm.TutorialImportDir = '/tmp' - size = wx.Size(700,600) + size = wx.Size(700,600) frm.plotFrame = wx.Frame(None,-1,'GSASII Plots',size=size, style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX) frm.G2plotNB = G2plt.G2PlotNoteBook(frm.plotFrame,G2frame=frm) frm.plotFrame.Show() GetFPAInput(frm) - + app.MainLoop() diff --git a/GSASII/GSASIIimage.py b/GSASII/GSASIIimage.py index 16969a1f8..d39d15168 100644 --- a/GSASII/GSASIIimage.py +++ b/GSASII/GSASIIimage.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- #GSASII image calculations: Image calibration, masking & integration routines. ''' -Classes and routines defined in :mod:`GSASIIimage` follow. +Classes and routines defined in :mod:`GSASIIimage` follow. ''' from __future__ import division, print_function @@ -13,13 +13,15 @@ import numpy.ma as ma from scipy.optimize import leastsq import scipy.interpolate as scint -import GSASIIpath -import GSASIIlattice as G2lat -import GSASIIpwd as G2pwd -import GSASIIspc as G2spc +import scipy.special as sc +from . import GSASIIpath +GSASIIpath.SetBinaryPath() +from . import GSASIIlattice as G2lat +from . import GSASIIpwd as G2pwd +from . import GSASIIspc as G2spc #import GSASIImath as G2mth -import GSASIIfiles as G2fil -import ImageCalibrants as calFile +from . import GSASIIfiles as G2fil +from . import ImageCalibrants as calFile # trig functions in degrees sind = lambda x: math.sin(x*math.pi/180.) @@ -40,7 +42,7 @@ npatan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi nxs = np.newaxis debug = False - + def pointInPolygon(pXY,xy): 'Needs a doc string' #pXY - assumed closed 1st & last points are duplicates @@ -56,11 +58,11 @@ def pointInPolygon(pXY,xy): Inside = not Inside p1x,p1y = p2x,p2y return Inside - + def peneCorr(tth,dep,dist): 'Needs a doc string' return dep*(1.-npcosd(tth))*dist**2/1000. #best one - + def makeMat(Angle,Axis): '''Make rotation matrix from Angle and Axis @@ -71,9 +73,9 @@ def makeMat(Angle,Axis): ss = npsind(Angle) M = np.array(([1.,0.,0.],[0.,cs,-ss],[0.,ss,cs]),dtype=np.float32) return np.roll(np.roll(M,Axis,axis=0),Axis,axis=1) - + def FitEllipse(xy): - + def ellipse_center(p): ''' gives ellipse center coordinates ''' @@ -82,14 +84,14 @@ def ellipse_center(p): x0=(c*d-b*f)/num y0=(a*f-b*d)/num return np.array([x0,y0]) - + def ellipse_angle_of_rotation( p ): ''' gives rotation of ellipse major axis from x-axis range will be -90 to 90 deg ''' b,c,a = p[1]/2., p[2], p[0] return 0.5*npatand(2*b/(a-c)) - + def ellipse_axis_length( p ): ''' gives ellipse radii in [minor,major] order ''' @@ -100,7 +102,7 @@ def ellipse_axis_length( p ): res1=np.sqrt(up/down1) res2=np.sqrt(up/down2) return np.array([ res2,res1]) - + xy = np.array(xy) x = np.asarray(xy.T[0])[:,np.newaxis] y = np.asarray(xy.T[1])[:,np.newaxis] @@ -128,10 +130,10 @@ def FitDetector(rings,varyList,parmDict,Print=True,covar=False): :param dict parmDict: all calibration parameters :param bool Print: set to True (default) to print the results :param bool covar: set to True to return the covariance matrix (default is False) - :returns: [chisq,vals,sigList] unless covar is True, then + :returns: [chisq,vals,sigList] unless covar is True, then [chisq,vals,sigList,coVarMatrix] is returned ''' - + def CalibPrint(ValSig,chisq,Npts): print ('Image Parameters: chi**2: %12.3g, Np: %d'%(chisq,Npts)) ptlbls = 'names :' @@ -149,10 +151,10 @@ def CalibPrint(ValSig,chisq,Npts): sigstr += 12*' ' print (ptlbls) print (ptstr) - print (sigstr) - + print (sigstr) + def ellipseCalcD(B,xyd,varyList,parmDict): - + x,y,dsp = xyd varyDict = dict(zip(varyList,B)) parms = {} @@ -167,7 +169,7 @@ def ellipseCalcD(B,xyd,varyList,parmDict): dxy = peneCorr(tth,parms['dep'],parms['dist']) stth = npsind(tth) cosb = npcosd(parms['tilt']) - tanb = nptand(parms['tilt']) + tanb = nptand(parms['tilt']) tbm = nptand((tth-parms['tilt'])/2.) tbp = nptand((tth+parms['tilt'])/2.) d = parms['dist']+dxy @@ -187,7 +189,7 @@ def ellipseCalcD(B,xyd,varyList,parmDict): Rcalc = (P+Q)/R M = (Robs-Rcalc)*25. #why 25? does make "chi**2" more reasonable return M - + names = ['dist','det-X','det-Y','tilt','phi','dep','wave'] fmt = ['%12.3f','%12.3f','%12.3f','%12.3f','%12.3f','%12.4f','%12.6f'] Fmt = dict(zip(names,fmt)) @@ -200,7 +202,7 @@ def ellipseCalcD(B,xyd,varyList,parmDict): sig = [] ValSig = [] sigList = [] - else: + else: sig = list(np.sqrt(chisq*np.diag(result[1]))) sigList = np.zeros(7) for i,name in enumerate(varyList): @@ -224,10 +226,10 @@ def FitMultiDist(rings,varyList,parmDict,Print=True,covar=False): :param dict parmDict: calibration parameters :param bool Print: set to True (default) to print the results :param bool covar: set to True to return the covariance matrix (default is False) - :returns: [chisq,vals,sigDict] unless covar is True, then + :returns: [chisq,vals,sigDict] unless covar is True, then [chisq,vals,sigDict,coVarMatrix] is returned ''' - + def CalibPrint(parmDict,sigDict,chisq,Npts): ptlbls = 'names :' ptstr = 'values:' @@ -270,7 +272,7 @@ def CalibPrint(parmDict,sigDict,chisq,Npts): fmt = '%12.4f' else: fmt = '%12.3f' - + ptlbls += "%s" % (name.rjust(12)) if name == 'phi': ptstr += fmt % (parmDict[name]%360.) @@ -301,14 +303,14 @@ def ellipseCalcD(B,xyd,varyList,parmDict): deltaDist = parms['deltaDist'] else: deltaDist = np.array([parms['delta'+str(int(d))] for d in dist]) - + phi = parms['phi']-90. #get rotation of major axis from tilt axis tth = 2.0*npasind(parms['wavelength']/(2.*dsp)) phi0 = npatan2d(y-detY,x-detX) dxy = peneCorr(tth,parms['dep'],dist-deltaDist) stth = npsind(tth) cosb = npcosd(parms['tilt']) - tanb = nptand(parms['tilt']) + tanb = nptand(parms['tilt']) tbm = nptand((tth-parms['tilt'])/2.) tbp = nptand((tth+parms['tilt'])/2.) d = (dist-deltaDist)+dxy @@ -327,7 +329,7 @@ def ellipseCalcD(B,xyd,varyList,parmDict): P = 2.*R0**2*zdis*npcosd(phi0-phi) Rcalc = (P+Q)/R return (Robs-Rcalc)*25. #why 25? does make "chi**2" more reasonable - + p0 = [parmDict[key] for key in varyList] result = leastsq(ellipseCalcD,p0,args=(rings.T,varyList,parmDict),full_output=True,ftol=1.e-8) chisq = np.sum(result[2]['fvec']**2)/(rings.shape[0]-len(p0)) #reduced chi^2 = M/(Nobs-Nvar) @@ -344,7 +346,7 @@ def ellipseCalcD(B,xyd,varyList,parmDict): return [chisq,vals,sigDict,result[1]] else: return [chisq,vals,sigDict] - + def ImageLocalMax(image,w,Xpix,Ypix): 'Needs a doc string' w2 = w*2 @@ -363,8 +365,8 @@ def ImageLocalMax(image,w,Xpix,Ypix): ypix += Zmax//w2-w return xpix,ypix,np.ravel(ZMax)[Zmax],max(0.0001,np.ravel(ZMin)[Zmin]) #avoid neg/zero minimum else: - return 0,0,0,0 - + return 0,0,0,0 + def makeRing(dsp,ellipse,pix,reject,scalex,scaley,image,mul=1): 'Needs a doc string' def ellipseC(): @@ -376,7 +378,7 @@ def ellipseC(): apb = radii[1]+radii[0] amb = radii[1]-radii[0] return np.pi*apb*(1+3*(amb/apb)**2/(10+np.sqrt(4-3*(amb/apb)**2))) - + cent,phi,radii = ellipse cphi = cosd(phi-90.) #convert to major axis rotation sphi = sind(phi-90.) @@ -402,12 +404,12 @@ def ellipseC(): ring = [] azm = [] return ring,azm - + def GetEllipse2(tth,dxy,dist,cent,tilt,phi): '''uses Dandelin spheres to find ellipse or hyperbola parameters from detector geometry on output radii[0] (b-minor axis) set < 0. for hyperbola - + ''' radii = [0,0] stth = sind(tth) @@ -440,7 +442,7 @@ def GetEllipse2(tth,dxy,dist,cent,tilt,phi): #thus shift from beam to ellipse center is [Z*sin(phi),-Z*cos(phi)] elcent = [cent[0]+zdis*sind(phi),cent[1]-zdis*cosd(phi)] return elcent,phi,radii - + def GetEllipse(dsp,data): '''uses Dandelin spheres to find ellipse or hyperbola parameters from detector geometry as given in image controls dictionary (data) and a d-spacing (dsp) @@ -455,22 +457,22 @@ def GetEllipse(dsp,data): return GetEllipse2(tth,dxy,dist,cent,tilt,phi) def GetDetectorXY(dsp,azm,data): - '''Get detector x,y position from d-spacing (dsp), azimuth (azm,deg) + '''Get detector x,y position from d-spacing (dsp), azimuth (azm,deg) & image controls dictionary (data) - new version - it seems to be only used in plotting + it seems to be only used in plotting ''' def LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint, epsilon=1e-6): - + ndotu = planeNormal.dot(rayDirection) - if ndotu < epsilon: + if ndotu < epsilon: return None - + w = rayPoint - planePoint si = -planeNormal.dot(w) / ndotu Psi = w + si * rayDirection + planePoint return Psi - - + + dist = data['distance'] cent = data['center'] T = makeMat(data['tilt'],0) @@ -493,12 +495,12 @@ def LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint, epsilon= xyz -= np.array([0.,0.,dist]) #translate back xyz = np.inner(xyz,iMN) return np.squeeze(xyz)[:2]+cent - + def GetDetectorXY2(dsp,azm,data): - '''Get detector x,y position from d-spacing (dsp), azimuth (azm,deg) + '''Get detector x,y position from d-spacing (dsp), azimuth (azm,deg) & image controls dictionary (data) - it seems to be only used in plotting - ''' + it seems to be only used in plotting + ''' elcent,phi,radii = GetEllipse(dsp,data) phi = data['rotation']-90. #to give rotation of major axis tilt = data['tilt'] @@ -540,15 +542,15 @@ def GetDetectorXY2(dsp,azm,data): if data['det2theta']: xy[0] += dist*nptand(data['det2theta']+data['tilt']*npsind(data['rotation'])) return xy - + def GetDetXYfromThAzm(Th,Azm,data): '''Computes a detector position from a 2theta angle and an azimultal angle (both in degrees) - apparently not used! ''' - dsp = data['wavelength']/(2.0*npsind(Th)) + dsp = data['wavelength']/(2.0*npsind(Th)) return GetDetectorXY(dsp,Azm,data) -# this suite not used for integration - only image plotting & mask positioning +# this suite not used for integration - only image plotting & mask positioning def GetTthAzmDsp2(x,y,data): #expensive '''Computes a 2theta, etc. from a detector position and calibration constants - checked OK for ellipses & hyperbola. @@ -573,11 +575,11 @@ def GetTthAzmDsp2(x,y,data): #expensive dxy = peneCorr(tth,dep,dist) DX = dist-Z+dxy DY = np.sqrt(dx**2+dy**2-Z**2) - tth = npatan2d(DY,DX) + tth = npatan2d(DY,DX) dsp = wave/(2.*npsind(tth/2.)) azm = (npatan2d(dy,dx)+azmthoff+720.)%360. return np.array([tth,azm,dsp]) - + def GetTthAzmDsp(x,y,data): #expensive '''Computes a 2theta, etc. from a detector position and calibration constants - checked OK for ellipses & hyperbola. @@ -586,11 +588,11 @@ def GetTthAzmDsp(x,y,data): #expensive :returns: np.array(tth,azm,G,dsp) where tth is 2theta, azm is the azimutal angle, and dsp is the d-space - not used in integration ''' - + def costth(xyz): u = xyz/nl.norm(xyz,axis=-1)[:,:,nxs] return np.dot(u,np.array([0.,0.,1.])) - + #zero detector 2-theta: tested with tilted images - perfect integrations wave = data['wavelength'] dx = x-data['center'][0] @@ -608,7 +610,7 @@ def costth(xyz): dzp = peneCorr(tth0,data['DetDepth'],dist) dxyz0[:,:,2] += dzp #non zero detector 2-theta: - if data['det2theta']: + if data['det2theta']: tthMat = makeMat(data['det2theta'],1) dxyz = np.inner(dxyz0,tthMat.T) else: @@ -616,37 +618,37 @@ def costth(xyz): ctth = costth(dxyz) tth = npacosd(ctth) dsp = wave/(2.*npsind(tth/2.)) - azm = (npatan2d(dxyz[:,:,1],dxyz[:,:,0])+data['azmthOff']+720.)%360. + azm = (npatan2d(dxyz[:,:,1],dxyz[:,:,0])+data['azmthOff']+720.)%360. return [tth,azm,dsp] - + def GetTth(x,y,data): 'Give 2-theta value for detector x,y position; calibration info in data' if data['det2theta']: return GetTthAzmDsp(x,y,data)[0] else: return GetTthAzmDsp2(x,y,data)[0] - + def GetTthAzm(x,y,data): 'Give 2-theta, azimuth values for detector x,y position; calibration info in data' if data['det2theta']: return GetTthAzmDsp(x,y,data)[0:2] else: return GetTthAzmDsp2(x,y,data)[0:2] - + def GetDsp(x,y,data): 'Give d-spacing value for detector x,y position; calibration info in data' if data['det2theta']: return GetTthAzmDsp(x,y,data)[2] else: return GetTthAzmDsp2(x,y,data)[2] - + def GetAzm(x,y,data): 'Give azimuth value for detector x,y position; calibration info in data' if data['det2theta']: return GetTthAzmDsp(x,y,data)[1] else: return GetTthAzmDsp2(x,y,data)[1] -# these two are used only for integration & finding pixel masks +# these two are used only for integration & finding pixel masks def GetTthAzmG2(x,y,data): '''Give 2-theta, azimuth & geometric corr. values for detector x,y position; calibration info in data - only used in integration for detector 2-theta = 0 @@ -656,11 +658,11 @@ def GetTthAzmG2(x,y,data): MN = -np.inner(makeMat(data['rotation'],2),makeMat(tilt,0)) dx = x-data['center'][0] dy = y-data['center'][1] - dz = np.dot(np.dstack([dx.T,dy.T,np.zeros_like(dx.T)]),MN).T[2] - xyZ = dx**2+dy**2-dz**2 + dz = np.dot(np.dstack([dx.T,dy.T,np.zeros_like(dx.T)]),MN).T[2] + xyZ = dx**2+dy**2-dz**2 tth0 = npatand(np.sqrt(xyZ)/(dist-dz)) dzp = peneCorr(tth0,data['DetDepth'],dist) - tth = npatan2d(np.sqrt(xyZ),dist-dz+dzp) + tth = npatan2d(np.sqrt(xyZ),dist-dz+dzp) azm = (npatan2d(dy,dx)+data['azmthOff']+720.)%360. # G-calculation - use Law of sines distm = data['distance']/1000.0 @@ -676,11 +678,11 @@ def GetTthAzmG(x,y,data): calibration info in data - only used in integration for detector 2-theta != 0. checked OK for ellipses & hyperbola This is the slow step in image integration - ''' + ''' def costth(xyz): u = xyz/nl.norm(xyz,axis=-1)[:,:,nxs] return np.dot(u,np.array([0.,0.,1.])) - + #zero detector 2-theta: tested with tilted images - perfect integrations dx = x-data['center'][0] dy = y-data['center'][1] @@ -697,7 +699,7 @@ def costth(xyz): dzp = peneCorr(tth0,data['DetDepth'],dist) dxyz0[:,:,2] += dzp #non zero detector 2-theta: - if data.get('det2theta',0): + if data.get('det2theta',0): tthMat = makeMat(data['det2theta'],1) dxyz = np.inner(dxyz0,tthMat.T) else: @@ -729,19 +731,19 @@ def meanAzm(a,b): # azm += 180. # elif quad == 3: # azm = 360-azm - return azm - + return azm + def ImageCompress(image,scale): ''' Reduces size of image by selecting every n'th point param: image array: original image - param: scale int: intervsl between selected points + param: scale int: intervsl between selected points returns: array: reduced size image ''' if scale == 1: return image else: return image[::scale,::scale] - + def checkEllipse(Zsum,distSum,xSum,ySum,dist,x,y): 'Needs a doc string' avg = np.array([distSum/Zsum,xSum/Zsum,ySum/Zsum]) @@ -782,56 +784,15 @@ def EdgeFinder(image,data): pixelSize = data['pixelSize'] edgemin = data['edgemin'] scalex = pixelSize[0]/1000. - scaley = pixelSize[1]/1000. + scaley = pixelSize[1]/1000. tay,tax = np.mgrid[0:Nx,0:Ny] - tax = np.asfarray(tax*scalex,dtype=np.float32) - tay = np.asfarray(tay*scaley,dtype=np.float32) + tax = np.asarray(tax*scalex,dtype=np.float32) + tay = np.asarray(tay*scaley,dtype=np.float32) tam = ma.getmask(ma.masked_less(image.flatten(),edgemin)) tax = ma.compressed(ma.array(tax.flatten(),mask=tam)) tay = ma.compressed(ma.array(tay.flatten(),mask=tam)) return zip(tax,tay) -# def MakeFrameMask(data,frame): #obsolete -# '''Assemble a Frame mask for a image, according to the input supplied. -# Note that this requires use of the Fortran polymask routine that is limited -# to 1024x1024 arrays, so this computation is done in blocks (fixed at 512) -# and the master image is assembled from that. - -# :param dict data: Controls for an image. Used to find the image size -# and the pixel dimensions. -# :param list frame: Frame parameters, typically taken from ``Masks['Frames']``. -# :returns: a mask array with dimensions matching the image Controls. -# ''' -# import polymask as pm -# pixelSize = data['pixelSize'] -# scalex = pixelSize[0]/1000. -# scaley = pixelSize[1]/1000. -# blkSize = 512 -# Nx,Ny = data['size'] -# nXBlks = (Nx-1)//blkSize+1 -# nYBlks = (Ny-1)//blkSize+1 -# tam = ma.make_mask_none(data['size']) -# for iBlk in range(nXBlks): -# iBeg = iBlk*blkSize -# iFin = min(iBeg+blkSize,Nx) -# for jBlk in range(nYBlks): -# jBeg = jBlk*blkSize -# jFin = min(jBeg+blkSize,Ny) -# nI = iFin-iBeg -# nJ = jFin-jBeg -# tax,tay = np.mgrid[iBeg+0.5:iFin+.5,jBeg+.5:jFin+.5] #bin centers not corners -# tax = np.asfarray(tax*scalex,dtype=np.float32) -# tay = np.asfarray(tay*scaley,dtype=np.float32) -# tamp = ma.make_mask_none((1024*1024)) -# tamp = ma.make_mask(pm.polymask(nI*nJ,tax.flatten(), -# tay.flatten(),len(frame),frame,tamp)[:nI*nJ])^True #switch to exclude around frame -# if tamp.shape: -# tamp = np.reshape(tamp[:nI*nJ],(nI,nJ)) -# tam[iBeg:iFin,jBeg:jFin] = ma.mask_or(tamp[0:nI,0:nJ],tam[iBeg:iFin,jBeg:jFin]) -# else: -# tam[iBeg:iFin,jBeg:jFin] = True -# return tam.T - def CalcRings(G2frame,ImageZ,data,masks): pixelSize = data['pixelSize'] scalex = 1000./pixelSize[0] @@ -840,7 +801,7 @@ def CalcRings(G2frame,ImageZ,data,masks): data['ellipses'] = [] if not data['calibrant']: G2fil.G2Print ('warning: no calibration material selected') - return + return skip = data['calibskip'] dmin = data['calibdmin'] if data['calibrant'] not in calFile.Calibrants: @@ -869,7 +830,7 @@ def CalcRings(G2frame,ImageZ,data,masks): if frame: tam = ma.mask_or(tam,ma.make_mask(np.abs(polymask(data,frame)-255))) for iH,H in enumerate(HKL): - if debug: print (H) + if debug: print (H) dsp = H[3] tth = 2.0*asind(wave/(2.*dsp)) if tth+abs(data['tilt']) > 90.: @@ -883,12 +844,12 @@ def CalcRings(G2frame,ImageZ,data,masks): if Ring: if iH not in absent and iH >= skip: data['rings'].append(np.array(Ring)) - data['ellipses'].append(copy.deepcopy(ellipse+('r',))) - + data['ellipses'].append(copy.deepcopy(ellipse+('r',))) + def ImageRecalibrate(G2frame,ImageZ,data,masks,getRingsOnly=False): '''Called to repeat the calibration on an image, usually called after - calibration is done initially to improve the fit, but also - can be used after reading approximate calibration parameters, + calibration is done initially to improve the fit, but also + can be used after reading approximate calibration parameters, if they are close enough that the first ring can be found. :param G2frame: The top-level GSAS-II frame or None, to skip plotting @@ -913,7 +874,7 @@ def ImageRecalibrate(G2frame,ImageZ,data,masks,getRingsOnly=False): data['DetDepth'] /= data['distance'] if not data['calibrant']: G2fil.G2Print ('warning: no calibration material selected') - return [] + return [] skip = data['calibskip'] dmin = data['calibdmin'] if data['calibrant'] not in calFile.Calibrants: @@ -947,7 +908,7 @@ def ImageRecalibrate(G2frame,ImageZ,data,masks,getRingsOnly=False): if frame: tam = ma.mask_or(tam,ma.make_mask(np.abs(polymask(data,frame)-255))) for iH,H in enumerate(HKL): - if debug: print (H) + if debug: print (H) dsp = H[3] tth = 2.0*asind(wave/(2.*dsp)) if tth+abs(data['tilt']) > 90.: @@ -963,15 +924,15 @@ def ImageRecalibrate(G2frame,ImageZ,data,masks,getRingsOnly=False): data['rings'].append(np.array(Ring)) data['ellipses'].append(copy.deepcopy(ellipse+('r',))) Found = True - elif not Found: #skipping inner rings, keep looking until ring found + elif not Found: #skipping inner rings, keep looking until ring found continue else: #no more rings beyond edge of detector data['ellipses'].append([]) continue if not data['rings']: G2fil.G2Print ('no rings found; try lower Min ring I/Ib',mode='warn') - return [] - + return [] + rings = np.concatenate((data['rings']),axis=0) if getRingsOnly: return rings,HKL @@ -987,22 +948,22 @@ def ImageRecalibrate(G2frame,ImageZ,data,masks,getRingsOnly=False): data['ellipses'] = [] #clear away individual ellipse fits for H in HKL[:N]: ellipse = GetEllipse(H[3],data) - data['ellipses'].append(copy.deepcopy(ellipse+('b',))) + data['ellipses'].append(copy.deepcopy(ellipse+('b',))) G2fil.G2Print ('calibration time = %.3f'%(time.time()-time0)) if G2frame: - import GSASIIplot as G2plt - G2plt.PlotImage(G2frame,newImage=True) + from . import GSASIIplot as G2plt + G2plt.PlotImage(G2frame,newImage=True) return [vals,varyList,sigList,parmDict,covar] def ImageCalibrate(G2frame,data): '''Called to perform an initial image calibration after points have been selected for the inner ring. - Called only from ``OnImRelease`` (mouse release) in - :func:`GSASIIplot.PlotImage`, thus expected to be used from GUI + Called only from ``OnImRelease`` (mouse release) in + :func:`GSASIIplot.PlotImage`, thus expected to be used from GUI only (not scripted) ''' - import GSASIIplot as G2plt + from . import GSASIIplot as G2plt G2fil.G2Print ('Image calibration:') time0 = time.time() ring = data['ring'] @@ -1018,7 +979,7 @@ def ImageCalibrate(G2frame,data): if len(ring) < 5: G2fil.G2Print ('ERROR - not enough inner ring points for ellipse') return False - + #fit start points on inner ring data['ellipses'] = [] data['rings'] = [] @@ -1030,7 +991,7 @@ def ImageCalibrate(G2frame,data): ellipse = outE else: return False - + #setup 360 points on that ring for "good" fit data['ellipses'].append(ellipse[:]+('g',)) Ring = makeRing(1.0,ellipse,pixLimit,cutoff,scalex,scaley,G2frame.ImageZ)[0] @@ -1047,13 +1008,13 @@ def ImageCalibrate(G2frame,data): data['ellipses'].append(ellipse[:]+('r',)) data['rings'].append(np.array(Ring)) G2plt.PlotImage(G2frame,newImage=True) - + #setup for calibration data['rings'] = [] if not data['calibrant']: G2fil.G2Print ('Warning: no calibration material selected') return True - + skip = data['calibskip'] dmin = data['calibdmin'] #generate reflection set @@ -1101,7 +1062,7 @@ def ImageCalibrate(G2frame,data): #ellipse to cone axis (x-ray beam); 2 choices depending on sign of tilt zdisp = radii[1]*ttth*tand(tilt) zdism = radii[1]*ttth*tand(-tilt) -#cone axis position; 2 choices. Which is right? +#cone axis position; 2 choices. Which is right? #NB: zdisp is || to major axis & phi is rotation of minor axis #thus shift from beam to ellipse center is [Z*sin(phi),-Z*cos(phi)] centp = [elcent[0]+zdisp*sind(phi),elcent[1]-zdisp*cosd(phi)] @@ -1118,7 +1079,7 @@ def ImageCalibrate(G2frame,data): G2fil.G2Print (fmt%('plus ellipse :',ellipsep[0][0],ellipsep[0][1],ellipsep[1],ellipsep[2][0],ellipsep[2][1])) Ringp = makeRing(dsp,ellipsep,3,cutoff,scalex,scaley,G2frame.ImageZ)[0] parmDict = {'dist':dist,'det-X':centp[0],'det-Y':centp[1], - 'tilt':tilt,'phi':phi,'wave':wave,'dep':0.0} + 'tilt':tilt,'phi':phi,'wave':wave,'dep':0.0} varyList = [item for item in varyDict if varyDict[item]] if len(Ringp) > 10: chip = FitDetector(np.array(Ring0+Ringp),varyList,parmDict,True)[0] @@ -1208,21 +1169,21 @@ def ImageCalibrate(G2frame,data): ellipse = GetEllipse(H[3],data) data['ellipses'].append(copy.deepcopy(ellipse+('b',))) G2fil.G2Print ('calibration time = %.3f'%(time.time()-time0)) - G2plt.PlotImage(G2frame,newImage=True) + G2plt.PlotImage(G2frame,newImage=True) return True - + def Make2ThetaAzimuthMap(data,iLim,jLim): #most expensive part of integration! '''Makes a set of matrices that provide the 2-theta, azimuth and geometric - correction values for each pixel in an image taking into account the - detector orientation. Can be used for the entire image or a rectangular - section of an image (determined by iLim and jLim). + correction values for each pixel in an image taking into account the + detector orientation. Can be used for the entire image or a rectangular + section of an image (determined by iLim and jLim). This is used in two ways. For image integration, the computation is done over blocks of fixed size (typically 128 or 256 pixels) but for pixel mask generation, the two-theta matrix for all pixels is computed. Note that - for integration, this routine will be called to generate sections as needed - or may be called by :func:`MakeUseTA`, which creates all sections at - once, so they can be reused multiple times. + for integration, this routine will be called to generate sections as needed + or may be called by :func:`MakeUseTA`, which creates all sections at + once, so they can be reused multiple times. :param dict data: GSAS-II image data object (describes the image) :param list iLim: boundary along x-pixels @@ -1233,8 +1194,8 @@ def Make2ThetaAzimuthMap(data,iLim,jLim): #most expensive part of integration! scalex = pixelSize[0]/1000. scaley = pixelSize[1]/1000. tay,tax = np.mgrid[iLim[0]+0.5:iLim[1]+.5,jLim[0]+.5:jLim[1]+.5] #bin centers not corners - tax = np.asfarray(tax*scalex,dtype=np.float32).flatten() - tay = np.asfarray(tay*scaley,dtype=np.float32).flatten() + tax = np.asarray(tax*scalex,dtype=np.float32).flatten() + tay = np.asarray(tay*scaley,dtype=np.float32).flatten() nI = iLim[1]-iLim[0] nJ = jLim[1]-jLim[0] TA = np.empty((4,nI,nJ)) @@ -1246,23 +1207,25 @@ def Make2ThetaAzimuthMap(data,iLim,jLim): #most expensive part of integration! TA[3] = G2pwd.Polarization(data['PolaVal'][0],TA[0],TA[1]-90.)[0] return TA #2-theta, azimuth & geom. corr. arrays -def polymask(data,Poly): - ''' Applies polygon & frame masks via calls to matplotlib routines; - should be called only once during image processing. Individual masked blocks - are then pulled from the output array. +def polymask(data,Poly,Spots=[]): + ''' Applies spot(point), polygon & frame masks via calls to matplotlib routines; + should be called only once each during image processing. A separate call is used for a frame. + Individual masked blocks are then pulled from the output array. :param dict data: GSAS-II image data object (describes the image) :param list Poly: list of polygons; if empty, returns None - :returns: Zimg, array[Nx,Ny] size of full image mask for all polygons considered + :param list Spots: list of spots/points; if empty, returns None + :returns: Zimg, array[Nx,Ny] size of full image mask for all polygons/spots or frame considered ''' import matplotlib.figure as mplfig + from matplotlib.patches import Circle try: from matplotlib.backends.backend_agg import FigureCanvasAgg as hcCanvas except ImportError: from matplotlib.backends.backend_agg import FigureCanvas as hcCanvas # standard name - if not Poly: + if not Poly and not Spots: return [] outmask = 'black' inmask = 'white' @@ -1280,21 +1243,25 @@ def polymask(data,Poly): px = np.array(poly).T[0]/scalex py = np.array(poly).T[1]/scaley ax0.fill(px,py,inmask) + for spot in Spots: + px = np.array(spot).T[0]/scalex + py = np.array(spot).T[1]/scaley + rad = 0.5*np.array(spot).T[2]/scaley + psp = Circle((px,py),radius=rad,fc=inmask,ec='none') + ax0.add_artist(psp) ax0.set_xbound(0,Nx) ax0.set_ybound(0,Ny) - agg = canvas.switch_backends(hcCanvas) - agg.draw() - img, (width, height) = agg.print_to_buffer() + img, (width,height) = canvas.print_to_buffer() Zimg = np.frombuffer(img, np.uint8).reshape((height, width, 4)) return Zimg[:,:,0] def MakeMaskMap(data,masks,iLim,jLim): '''Makes a mask array from masking parameters that are not determined by image calibration parameters or the image intensities. Thus this uses - mask Frames, Polygons and Lines settings (but not Thresholds, Rings or - Arcs). Used on a rectangular section of an image (must be 1024x1024 or - smaller, as dictated by module polymask) where the size is determined - by iLim and jLim. + mask Frames, Polygons, Points, and Lines settings (but not Thresholds, Rings or + Arcs). Used on a rectangular section of an image (must be 1024x1024 or + smaller) where the size is determined + by iLim and jLim. :param dict data: GSAS-II image data object (describes the image) :param list iLim: boundary along x-pixels @@ -1309,8 +1276,8 @@ def MakeMaskMap(data,masks,iLim,jLim): if iLim[0] == jLim[0] == 0: if masks['Frames']: frame = np.abs(polymask(data,masks['Frames'])-255) #turn inner to outer mask - if masks['Polygons']: - poly = polymask(data,masks['Polygons']) + if masks['Polygons'] or masks['Points']: + poly = polymask(data,masks['Polygons'],masks['Points']) if len(frame): masks['Pmask'] = frame if len(poly): @@ -1320,19 +1287,15 @@ def MakeMaskMap(data,masks,iLim,jLim): else: masks['Pmask'] = [] tay,tax = np.mgrid[iLim[0]+0.5:iLim[1]+.5,jLim[0]+.5:jLim[1]+.5] #bin centers not corners - tax = np.asfarray(tax*scalex,dtype=np.float32).flatten() - tay = np.asfarray(tay*scaley,dtype=np.float32).flatten() + tax = np.asarray(tax*scalex,dtype=np.float32).flatten() + tay = np.asarray(tay*scaley,dtype=np.float32).flatten() nI = iLim[1]-iLim[0] nJ = jLim[1]-jLim[0] #make position masks here tam = ma.make_mask_none((nI*nJ)) if len(masks['Pmask']): tam = ma.mask_or(tam,ma.make_mask(masks['Pmask'][iLim[0]:iLim[1],jLim[0]:jLim[1]].flatten())) - points = masks['Points'] - if len(points): - for X,Y,rsq in points.T: - tam = ma.mask_or(tam,ma.getmask(ma.masked_less((tax-X)**2+(tay-Y)**2,rsq))) - if tam.shape: + if tam.shape: tam = np.reshape(tam,(nI,nJ)) else: tam = ma.make_mask_none((nI,nJ)) @@ -1341,28 +1304,28 @@ def MakeMaskMap(data,masks,iLim,jLim): tam[xline-iLim[0],:] = True for yline in masks.get('Ylines',[]): #a x pixel position if jLim[0] <= yline <= jLim[1]: - tam[:,yline-jLim[0]] = True + tam[:,yline-jLim[0]] = True return tam #position mask def Fill2ThetaAzimuthMap(masks,TAr,tam,image,ringMask=False): - '''Makes masked intensity correction arrays that depend on image - intensity, 2theta and azimuth. Masking is generated from the - combination of the following: - an array previously generated by :func:`MakeMaskMap` combined with - Thresholds, Rings and Arcs mask input. + '''Makes masked intensity correction arrays that depend on image + intensity, 2theta and azimuth. Masking is generated from the + combination of the following: + an array previously generated by :func:`MakeMaskMap` combined with + Thresholds, Rings and Arcs mask input. These correction arrays are generated for a rectangular section - of an image (must be 1024x1024 or smaller) where the size is + of an image (must be 1024x1024 or smaller) where the size is determined the input arrays. - Note that older, less optimized, code has been left commented out below - in case there are future problems or questions. + Note that older, less optimized, code has been left commented out below + in case there are future problems or questions. :param dict masks: GSAS-II mask settings :param np.array TAr: 2theta/azimuth/correction arrays, reshaped :param np.array tam: mask array from :func:`MakeMaskMap` :param np.array image: image array - :returns: a list of 4 masked arrays with values for: azimuth, 2-theta, + :returns: a list of 4 masked arrays with values for: azimuth, 2-theta, intensity/polarization, dist**2/d0**2 ''' tax,tay,tad,pol = TAr #azimuth, 2-theta, dist**2/d0**2, pol @@ -1373,7 +1336,7 @@ def Fill2ThetaAzimuthMap(masks,TAr,tam,image,ringMask=False): image = image.data else: mask = tam.reshape(image.shape) - + # apply Ring & Arc masks. Note that this could be done in advance # and be cached (like tam & TAr) but it is not clear this is needed # often or that a lot of time is saved. @@ -1390,7 +1353,7 @@ def Fill2ThetaAzimuthMap(masks,TAr,tam,image,ringMask=False): Zlim = masks['Thresholds'][1] #taz = ma.masked_outside(image.flatten(),int(Zlim[0]),Zlim[1]) mask |= (image < Zlim[0]) | (image > Zlim[1]) - + #tam = ma.mask_or(tam.flatten(),ma.getmask(taz)) #tax = ma.compressed(ma.array(tax.flatten(),mask=tam)) #azimuth #tay = ma.compressed(ma.array(tay.flatten(),mask=tam)) #2-theta @@ -1406,7 +1369,7 @@ def Fill2ThetaAzimuthMap(masks,TAr,tam,image,ringMask=False): return tax[mask],tay[mask],image[mask]/pol[mask],tad[mask] def MakeUseTA(data,blkSize=128): - '''Precomputes the set of blocked arrays for 2theta-azimuth mapping from + '''Precomputes the set of blocked arrays for 2theta-azimuth mapping from the controls settings of the current image for image integration. This computation is done optionally, but provides speed as the results from this can be cached to avoid recomputation for a series of images @@ -1415,7 +1378,7 @@ def MakeUseTA(data,blkSize=128): :param np.array data: specifies parameters for an image :param int blkSize: a blocksize that is selected for speed :returns: a list of TA blocks - ''' + ''' Nx,Ny = data['size'] nXBlks = (Nx-1)//blkSize+1 nYBlks = (Ny-1)//blkSize+1 @@ -1444,11 +1407,8 @@ def MakeUseMask(data,masks,blkSize=128): :param np.array data: specifies mask parameters for an image :param int blkSize: a blocksize that is selected for speed :returns: a list of TA blocks - ''' + ''' Masks = copy.deepcopy(masks) - Masks['Points'] = np.array(Masks['Points']).T #get spots as X,Y,R arrays - if np.any(masks['Points']): - Masks['Points'][2] = np.square(Masks['Points'][2]/2.) Nx,Ny = data['size'] nXBlks = (Nx-1)//blkSize+1 nYBlks = (Ny-1)//blkSize+1 @@ -1465,7 +1425,7 @@ def MakeUseMask(data,masks,blkSize=128): useMask.append(useMaskj) return useMask -def MakeGainMap(image,Ix,Iy,data,blkSize=128): +def MakeGainMap(image,Ix,Iy,data,mask,blkSize=128): Iy /= npcosd(Ix[:-1]) #undo parallax Iy *= (1000./data['distance'])**2 #undo r^2 effect Iy /= np.array(G2pwd.Polarization(data['PolaVal'][0],Ix[:-1],0.)[0]) #undo polarization @@ -1473,6 +1433,7 @@ def MakeGainMap(image,Ix,Iy,data,blkSize=128): Iy *= G2pwd.Oblique(data['Oblique'][0],Ix[:-1]) #undo penetration IyInt = scint.interp1d(Ix[:-1],Iy[0],bounds_error=False) GainMap = np.zeros_like(image,dtype=float) + Mask = np.zeros_like(image,dtype=bool) #do interpolation on all points - fills in the empty bins; leaves others the same Nx,Ny = data['size'] nXBlks = (Nx-1)//blkSize+1 @@ -1484,15 +1445,20 @@ def MakeGainMap(image,Ix,Iy,data,blkSize=128): jBeg = jBlk*blkSize jFin = min(jBeg+blkSize,Nx) TA = Make2ThetaAzimuthMap(data,(iBeg,iFin),(jBeg,jFin)) #2-theta & azimuth arrays & create position mask + tam = MakeMaskMap(data,mask,(iBeg,iFin),(jBeg,jFin)) Ipix = IyInt(TA[0]) - GainMap[iBeg:iFin,jBeg:jFin] = image[iBeg:iFin,jBeg:jFin]/(Ipix*TA[3]) + Mask[iBeg:iFin,jBeg:jFin] = tam + GainMap[iBeg:iFin,jBeg:jFin] = np.array(image[iBeg:iFin,jBeg:jFin]/(Ipix*TA[3])) GainMap /= np.nanmedian(GainMap) - return 1./GainMap + return 1./GainMap,Mask def AzimuthIntegrate(image,data,masks,ringId,blkSize=1024): ''' Integrate by azimuth around the ring masked region in 0.5 deg steps ''' - import histogram2d as h2d + if GSASIIpath.binaryPath: + import histogram2d as h2d + else: + from . import histogram2d as h2d LRazm = np.array(data['LRazimuth'],dtype=np.float64) numAzms = 720 ring = masks['Rings'][ringId] @@ -1529,7 +1495,7 @@ def AzimuthIntegrate(image,data,masks,ringId,blkSize=1024): else: tabs = np.ones_like(taz) else: - tabs = np.ones_like(taz) + tabs = np.ones_like(taz) taz = np.array((taz*tad*tabs),dtype='float32') if any([tax.shape[0],tay.shape[0],taz.shape[0]]): NST,H0 = h2d.histogram2d(len(tax),tax,tay,taz, @@ -1539,34 +1505,37 @@ def AzimuthIntegrate(image,data,masks,ringId,blkSize=1024): return np.stack((Azms,H0.T[0])) def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask=None): - '''Integrate an image; called from ``OnIntegrate()`` and - ``OnIntegrateAll()`` inside :func:`GSASIIimgGUI.UpdateImageControls` + '''Integrate an image; called from ``OnIntegrate()`` and + ``OnIntegrateAll()`` inside :func:`GSASIIimgGUI.UpdateImageControls` as well as :meth:`GSASIIscriptable.G2Image.Integrate`. :param np.array image: contains the 2-D image :param np.array data: specifies controls/calibration parameters for an image :param np.array masks: specifies masks parameters for an image :param int blkSize: a blocksize that is selected for speed - :param bool returnN: If True, causes an extra matrix (NST) to be - returned. The default is False. - :param np.array useTA: contains a cached set of blocked - 2theta/azimuth/correction matrices (see :func:`MakeUseTA`) for the - current image. + :param bool returnN: If True, causes an extra matrix (NST) to be + returned. The default is False. + :param np.array useTA: contains a cached set of blocked + 2theta/azimuth/correction matrices (see :func:`MakeUseTA`) for the + current image. The default, None, causes this to be computed as needed. - :param np.array useMask: contains a cached set of blocked masks (see - :func:`MakeUseMask`) for the current image. + :param np.array useMask: contains a cached set of blocked masks (see + :func:`MakeUseMask`) for the current image. The default, None, causes this to be computed as needed. - :returns: list ints, azms, Xvals, cancel (or ints, azms, Xvals, - NST, cancel if returnN is True), where azms is a list of ``M`` azimuth + :returns: list ints, azms, Xvals, cancel (or ints, azms, Xvals, + NST, cancel if returnN is True), where azms is a list of ``M`` azimuth values that were requested for integration, ints is a list ``M`` arrays - of diffraction intensities (where each array of diffraction data is - length ``N``), Xvals is an array of "x" values, 2theta, Q, log(q) - (determined by data['binType']), also of length ``N``. Variable cancel - will always be False, since a status window is no longer supported. + of diffraction intensities (where each array of diffraction data is + length ``N``), Xvals is an array of "x" values, 2theta, Q, log(q) + (determined by data['binType']), also of length ``N``. Variable cancel + will always be False, since a status window is no longer supported. ''' #for q, log(q) bins need data['binType'] - import histogram2d as h2d + if GSASIIpath.binaryPath: + import histogram2d as h2d + else: + from . import histogram2d as h2d G2fil.G2Print ('Beginning image integration; image range: %d %d'%(np.min(image),np.max(image))) CancelPressed = False LUtth = np.array(data['IOtth']) @@ -1575,7 +1544,7 @@ def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask numChans = (data['outChannels']//4)*4 Dazm = (LRazm[1]-LRazm[0])/numAzms if '2-theta' in data.get('binType','2-theta'): - lutth = LUtth + lutth = LUtth elif 'log(q)' in data['binType']: lutth = np.log(4.*np.pi*npsind(LUtth/2.)/data['wavelength']) elif 'q' == data['binType'].lower(): @@ -1587,9 +1556,6 @@ def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask if 'SASD' in data['type']: muT = -np.log(muT)/2. #Transmission to 1/2 thickness muT Masks = copy.deepcopy(masks) - Masks['Points'] = np.array(Masks['Points']).T #get spots as X,Y,R arrays - if np.any(masks['Points']): - Masks['Points'][2] = np.square(Masks['Points'][2]/2.) NST = np.zeros(shape=(numAzms,numChans),order='F',dtype=np.float32) H0 = np.zeros(shape=(numAzms,numChans),order='F',dtype=np.float32) H2 = np.linspace(lutth[0],lutth[1],numChans+1) @@ -1635,7 +1601,7 @@ def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask else: tabs = np.ones_like(taz) else: - tabs = np.ones_like(taz) + tabs = np.ones_like(taz) if 'log(q)' in data.get('binType',''): tay = np.log(4.*np.pi*npsind(tay/2.)/data['wavelength']) elif 'q' == data.get('binType','').lower(): @@ -1667,12 +1633,12 @@ def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask except ValueError: problemEntries.append(i) H0.append(np.zeros(numChans,order='F',dtype=np.float32)) - H0 = np.nan_to_num(np.array(H0)) + H0 = np.nan_to_num(np.array(H0)) if 'log(q)' in data.get('binType',''): H2 = 2.*npasind(np.exp(H2)*data['wavelength']/(4.*np.pi)) elif 'q' == data.get('binType','').lower(): H2 = 2.*npasind(H2*data['wavelength']/(4.*np.pi)) - if Dazm: + if Dazm: H1 = np.array([azm for azm in np.linspace(LRazm[0],LRazm[1],numAzms+1)]) else: H1 = LRazm @@ -1700,7 +1666,7 @@ def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask return H0,H1,H2,NST,CancelPressed else: return H0,H1,H2,CancelPressed - + def MakeStrStaRing(ring,Image,Controls): ellipse = GetEllipse(ring['Dset'],Controls) pixSize = Controls['pixelSize'] @@ -1721,10 +1687,10 @@ def MakeStrStaRing(ring,Image,Controls): ring['ImtaObs'] = [[],[]] ring['ImtaCalc'] = [[],[]] return [],[] #bad ring; no points found - + def FitStrSta(Image,StrSta,Controls): 'Needs a doc string' - + StaControls = copy.deepcopy(Controls) phi = StrSta['Sample phi'] wave = Controls['wavelength'] @@ -1752,7 +1718,7 @@ def FitStrSta(Image,StrSta,Controls): ring['covMat'] = covMat G2fil.G2Print ('Variance in normalized ring intensity: %.3f'%(ring['Ivar'])) CalcStrSta(StrSta,Controls) - + def IntStrSta(Image,StrSta,Controls): StaControls = copy.deepcopy(Controls) pixelSize = Controls['pixelSize'] @@ -1776,7 +1742,7 @@ def IntStrSta(Image,StrSta,Controls): G2fil.G2Print (' %s %.3f %s %.3f %s %d'%('d-spacing',ring['Dcalc'],'sig(MRD):',np.sqrt(np.var(ringint)),'# points:',len(ringint))) RingsAI.append(np.array(list(zip(ringazm,ringint))).T) return RingsAI - + def CalcStrSta(StrSta,Controls): wave = Controls['wavelength'] @@ -1805,7 +1771,7 @@ def CalcStrSta(StrSta,Controls): def calcFij(omg,phi,azm,th): ''' Uses parameters as defined by Bob He & Kingsley Smith, Adv. in X-Ray Anal. 41, 501 (1997) - :param omg: his omega = sample omega rotation; 0 when incident beam || sample surface, + :param omg: his omega = sample omega rotation; 0 when incident beam || sample surface, 90 when perp. to sample surface :param phi: his phi = sample phi rotation; usually = 0, axis rotates with omg. :param azm: his chi = azimuth around incident beam @@ -1839,7 +1805,7 @@ def StrainPrint(ValSig,dset): print (ptlbls) print (ptstr) print (sigstr) - + def strainCalc(p,xyd,dset,wave,phi,StaType): E = np.array([[p[0],p[1],0],[p[1],p[2],0],[0,0,0]]) dspo,azm,dsp = xyd @@ -1850,7 +1816,7 @@ def strainCalc(p,xyd,dset,wave,phi,StaType): else: dspc = dset*(V+1.) return dspo-dspc - + names = ['e11','e12','e22'] fmt = ['%12.2f','%12.2f','%12.2f'] result = leastsq(strainCalc,p0,args=(rings,dset,wave,phi,StaType),full_output=True) @@ -1873,7 +1839,7 @@ def calcMean(nxy,pixSize,img): posx = ma.sum(gdx)/ma.count(gdx) posy = ma.sum(gdy)/ma.count(gdy) return posx,posy - + def calcPeak(values,nxy,pixSize,img): back,mag,px,py,sig = values peak = np.zeros([nxy,nxy])+back @@ -1881,10 +1847,10 @@ def calcPeak(values,nxy,pixSize,img): gdx,gdy = np.mgrid[0:nxy,0:nxy] gdx = (gdx-nxy//2)*pixSize[0]/1000. gdy = (gdy-nxy//2)*pixSize[1]/1000. - arg = (gdx-px)**2+(gdy-py)**2 + arg = (gdx-px)**2+(gdy-py)**2 peak += mag*nor*np.exp(-arg/(2.*sig**2)) return ma.compressed(img-peak)/np.sqrt(ma.compressed(img)) - + def calc2Peak(values,nxy,pixSize,img): back,mag,px,py,sigx,sigy,rho = values peak = np.zeros([nxy,nxy])+back @@ -1893,10 +1859,10 @@ def calc2Peak(values,nxy,pixSize,img): gdx = (gdx-nxy//2)*pixSize[0]/1000. gdy = (gdy-nxy//2)*pixSize[1]/1000. argnor = -1./(2.*(1.-rho**2)) - arg = (gdx-px)**2/sigx**2+(gdy-py)**2/sigy**2-2.*rho*(gdx-px)*(gdy-py)/(sigx*sigy) + arg = (gdx-px)**2/sigx**2+(gdy-py)**2/sigy**2-2.*rho*(gdx-px)*(gdy-py)/(sigx*sigy) peak += mag*nor*np.exp(argnor*arg) - return ma.compressed(img-peak)/np.sqrt(ma.compressed(img)) - + return ma.compressed(img-peak)/np.sqrt(ma.compressed(img)) + nxy2 = nxy//2 ImBox = Image[ind[1]-nxy2:ind[1]+nxy2+1,ind[0]-nxy2:ind[0]+nxy2+1] back = np.min(ImBox) @@ -1925,7 +1891,10 @@ def TestFastPixelMask(): :returns: True if the airxd.mask package can be imported; False otherwise. ''' try: - import fmask + if GSASIIpath.binaryPath: + import fmask + else: + from . import fmask except ModuleNotFoundError: if GSASIIpath.GetConfigValue('debug'): print('fmask not found') return False @@ -1934,28 +1903,31 @@ def TestFastPixelMask(): def FastAutoPixelMask(Image, Masks, Controls, numChans, dlg=None): '''Find "bad" regions on an image and create a pixel mask to remove them. This works by masking pixels that are m*sigma outside the range of the - median at that radial distance using the using the fmask C module (based on the - AIRXD C++ code https://github.com/AdvancedPhotonSource/AIRXD-ML-PUB, developed + median at that radial distance using the using the fmask C module (based on the + AIRXD C++ code https://github.com/AdvancedPhotonSource/AIRXD-ML-PUB, developed by Howard Yanxon, Wenqian Xu and James Weng.) - This is much faster than AutoPixelMask, which does pretty much the + This is much faster than AutoPixelMask, which does pretty much the same computation, but uses pure Python/numpy code. - Called from GSASIIimgGUI.UpdateMasks.OnFindPixelMask (single image) - and GSASIIimgGUI.UpdateMasks.OnAutoFindPixelMask (multiple images) + Called from GSASIIimgGUI.UpdateMasks.OnFindPixelMask (single image) + and GSASIIimgGUI.UpdateMasks.OnAutoFindPixelMask (multiple images) [see :func:`GSASIIimgGUI.UpdateMasks`] :param np.array Image: 2D data structure describing a diffaction image - :param dict Masks: contents of Masks data tree + :param dict Masks: contents of Masks data tree :param dict Controls: diffraction & calibration parameters for image from IMG data tree entry - :param int numChans: number of channels in eventual 2theta pattern + :param int numChans: number of channels in eventual 2theta pattern after integration - :returns: a bool mask array with the same shape as Image + :returns: a bool mask array with the same shape as Image ''' try: - import fmask + if GSASIIpath.binaryPath: + import fmask + else: + from . import fmask if GSASIIpath.GetConfigValue('debug'): print('Loaded fmask from',fmask.__file__) except: return None @@ -1970,45 +1942,45 @@ def FastAutoPixelMask(Image, Masks, Controls, numChans, dlg=None): LUtth = np.array(Controls['IOtth']) TThs = np.linspace(LUtth[0], LUtth[1], numChans, False) - # fp = open('/tmp/maskdump.pickle','wb') # make external test file + # fp = open('/tmp/maskdump.pickle','wb') # make external test file # import pickle # pickle.dump(tam,fp) # frame mask # pickle.dump(TA,fp) # 2theta values - # pickle.dump(Image,fp) # image values + # pickle.dump(Image,fp) # image values # pickle.dump(TThs,fp) # 2theta bins # fp.close() - + print(' Fast mask: Spots greater or less than %.1f of median abs deviation are masked'%esdMul) outMask = np.zeros_like(tam,dtype=bool).ravel() - + if dlg: dlg.Update(10,"Fast scan in progress") try: masked = fmask.mask(esdMul, tam.ravel(), TA.ravel(), Image.ravel(), TThs, outMask, ttmin, ttmax) except Exception as msg: print('Exception in fmask.mask\n\t',msg) return outMask.reshape(Image.shape) - + def AutoPixelMask(Image, Masks, Controls, numChans, dlg=None): '''Find "bad" regions on an image and creata a pixel mask to remove them. This works by masking pixels that are well outside the range of the median at that radial distance. This is ~4x faster than the original version from RBVD. - Developed by Howard Yanxon, Wenqian Xu and James Weng. + Developed by Howard Yanxon, Wenqian Xu and James Weng. - Called from GSASIIimgGUI.UpdateMasks.OnFindPixelMask (single image) - and GSASIIimgGUI.UpdateMasks.OnAutoFindPixelMask (multiple images) + Called from GSASIIimgGUI.UpdateMasks.OnFindPixelMask (single image) + and GSASIIimgGUI.UpdateMasks.OnAutoFindPixelMask (multiple images) [see :func:`GSASIIimgGUI.UpdateMasks`] :param np.array Image: 2D data structure describing a diffaction image - :param dict Masks: contents of Masks data tree + :param dict Masks: contents of Masks data tree :param dict Controls: diffraction & calibration parameters for image from IMG data tree entry - :param int numChans: number of channels in eventual 2theta pattern + :param int numChans: number of channels in eventual 2theta pattern after integration :param wx.Dialog dlg: a widget that can be used to show the status of - the pixel mask scan and can optionally be used to cancel the scan. If + the pixel mask scan and can optionally be used to cancel the scan. If dlg=None then this is ignored (for non-GUI use). - :returns: a mask array with the same shape as Image or None if the + :returns: a mask array with the same shape as Image or None if the the scan is cancelled from the dlg Dialog. ''' #if GSASIIpath.GetConfigValue('debug'): print('faster all-Python AutoPixelMask') @@ -2020,7 +1992,7 @@ def MAD(args,**kwargs): except ImportError: try: from scipy.stats import median_absolute_deviation as MAD - if GSASIIpath.GetConfigValue('debug'): + if GSASIIpath.GetConfigValue('debug'): print('Using deprecated scipy.stats.median_absolute_deviation routine') except: print('Unable to load scipy.stats.median_abs_deviation') @@ -2032,16 +2004,16 @@ def MAD(args,**kwargs): tam = ma.mask_or(tam,ma.make_mask(np.abs(polymask(Controls,frame)-255))) LUtth = np.array(Controls['IOtth']) dtth = (LUtth[1]-LUtth[0])/numChans - esdMul = Masks['SpotMask']['esdMul'] + esdMul = Masks['SpotMask']['esdMul'] print(' Spots greater or less than %.1f of median abs deviation are masked'%esdMul) band = np.array(Image) TA = Make2ThetaAzimuthMap(Controls, (0, Image.shape[0]), (0, Image.shape[1]))[0] TThs = np.linspace(LUtth[0], LUtth[1], numChans, False) mask = (TA >= LUtth[1]) | (TA < LUtth[0]) | tam - + for it in range(len(TThs)): masker = (TA >= TThs[it]) & (TA < TThs[it]+dtth) & ~tam - if (TThs[it] < Masks['SpotMask'].get('SearchMin',0.0) or + if (TThs[it] < Masks['SpotMask'].get('SearchMin',0.0) or TThs[it] > Masks['SpotMask'].get('SearchMax',180.0)): mask |= masker continue @@ -2081,7 +2053,7 @@ def DoPolaCalib(ImageZ,imageData,arcTth): 'Thresholds':imageData['range'],'SpotMask':{'esdMul':3.,'spotMask':None}} AMasks = {'Points':[],'Rings':[],'Arcs':[Arc,],'Polygons':[],'Frames':[], 'Thresholds':imageData['range'],'SpotMask':{'esdMul':3.,'spotMask':None}} - + def func(p): p = min(1.0,max(p,0.0)) data['PolaVal'][0] = p @@ -2094,7 +2066,7 @@ def func(p): res = minimize_scalar(func,bracket=[1.,.999],tol=.0001) print(res) pola = min(1.0,max(res.x,.0)) - + Pola = [] for arc in [75,80,85,90,95]: Arc = [arcTth,[arc,arc+10.],2.0] @@ -2110,4 +2082,3 @@ def func(p): print(' Polarization: %.4f(%d)'%(mean,std)) print(' time: %.2fs'%(time.time()-time0)) imageData['PolaVal'][0] = mean - diff --git a/GSASII/GSASIIimgGUI.py b/GSASII/GSASIIimgGUI.py index 526ccec93..c8e1b7f69 100644 --- a/GSASII/GSASIIimgGUI.py +++ b/GSASII/GSASIIimgGUI.py @@ -16,18 +16,18 @@ import matplotlib as mpl import numpy as np import numpy.ma as ma -import GSASIIpath -import GSASIIimage as G2img -import GSASIImath as G2mth -import GSASIIElem as G2elem -import GSASIIpwdGUI as G2pdG -import GSASIIplot as G2plt -import GSASIImiscGUI as G2IO -import GSASIIfiles as G2fil -import GSASIIdataGUI as G2gd -import GSASIIctrlGUI as G2G -import GSASIIobj as G2obj -import ImageCalibrants as calFile +from . import GSASIIpath +from . import GSASIIimage as G2img +from . import GSASIImath as G2mth +from . import GSASIIElem as G2elem +from . import GSASIIpwdGUI as G2pdG +from . import GSASIIplot as G2plt +from . import GSASIImiscGUI as G2IO +from . import GSASIIfiles as G2fil +from . import GSASIIdataGUI as G2gd +from . import GSASIIctrlGUI as G2G +from . import GSASIIobj as G2obj +from . import ImageCalibrants as calFile # documentation build kludge. This prevents an error with sphinx 1.8.5 (fixed by 2.3) where all mock objects are of type _MockObject if (type(wx.ListCtrl).__name__ == @@ -39,7 +39,7 @@ class Junk1(object): pass class Junk2(object): pass listmix.TextEditMixin = Junk2 -try: +try: #VERY_LIGHT_GREY = wx.Colour(235,235,235) VERY_LIGHT_GREY = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) WACV = wx.ALIGN_CENTER_VERTICAL @@ -61,7 +61,7 @@ class Junk2(object): pass ################################################################################ def GetImageZ(G2frame,data,newRange=False): - '''Gets image & applies dark, background & flat background corrections. + '''Gets image & applies dark, background & flat background corrections. :param wx.Frame G2frame: main GSAS-II frame :param dict data: Image Controls dictionary @@ -69,8 +69,8 @@ def GetImageZ(G2frame,data,newRange=False): :returns: array sumImg: corrected image for background/dark/flat back ''' # Note that routine GSASIIscriptable._getCorrImage is based on this - # so changes made here should be repeated there. - + # so changes made here should be repeated there. + Npix,imagefile,imagetag = G2IO.GetCheckImageFile(G2frame,G2frame.Image) if imagefile is None: return [] formatName = data.get('formatName','') @@ -95,7 +95,7 @@ def GetImageZ(G2frame,data,newRange=False): darkImg)) data['dark image'][0] = darkImg = '' if 'background image' in data: - backImg,backScale = data['background image'] + backImg,backScale = data['background image'] if backImg: #ignores any transmission effect in the background image Bid = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, backImg) if Bid: @@ -112,7 +112,7 @@ def GetImageZ(G2frame,data,newRange=False): if gainMap: GMid = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, gainMap) if GMid: - Npix,gainfile,imagetag = G2IO.GetCheckImageFile(G2frame,GMid) + Npix,gainfile,imagetag = G2IO.GetCheckImageFile(G2frame,GMid) Gdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,GMid,'Image Controls')) gformat = Gdata['formatName'] GMimage = G2fil.GetImageData(G2frame,gainfile,True,ImageTag=imagetag,FormatName=gformat) @@ -126,10 +126,10 @@ def GetImageZ(G2frame,data,newRange=False): return np.array(np.array(np.rint(sumImg),dtype=int),dtype=np.int32) # double-cast removes warning. Why? def UpdateImageData(G2frame,data): - + def OnPixVal(invalid,value,tc): G2plt.PlotExposedImage(G2frame,newPlot=True,event=tc.event) - + def OnPolaCalib(event): if data['IOtth'][1] < 34.: G2G.G2MessageBox(G2frame,'Maximum 2-theta not greater than 34 deg', @@ -138,7 +138,7 @@ def OnPolaCalib(event): IOtth = [32.,data['IOtth'][1]-2.] dlg = G2G.SingleFloatDialog(G2frame,'Polarization test arc mask', ''' Do not use if pattern has uneven absorption - Set 2-theta max in image controls to be fully inside image + Set 2-theta max in image controls to be fully inside image Enter 2-theta position for arc mask (32-%.1f) '''%IOtth[1],IOtth[1],IOtth,fmt='%.2f') if dlg.ShowModal() == wx.ID_OK: arcTth = dlg.GetValue() @@ -253,8 +253,8 @@ def OnPolaCalib(event): ################################################################################ ##### Image Controls -################################################################################ -blkSize = 128 #128 seems to be optimal; will break in polymask if >1024 +################################################################################ +blkSize = 128 #128 seems to be optimal def UpdateImageControls(G2frame,data,masks,useTA=None,useMask=None,IntegrateOnly=False): '''Shows and handles the controls on the "Image Controls" data tree entry @@ -299,14 +299,14 @@ def OnCalibrate(event): return G2frame.GetStatusBar().SetStatusText('Select > 4 points on 1st used ring; LB to pick (shift key to force pick), RB on point to delete else RB to finish',1) G2frame.ifGetRing = True - + def OnRecalibrate(event): '''Use existing calibration values as starting point for a calibration fit ''' G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,data,masks) wx.CallAfter(UpdateImageControls,G2frame,data,masks) - + def OnRecalibAll(event): '''Use existing calibration values as starting point for a calibration fit for a selected series of images @@ -347,14 +347,14 @@ def OnRecalibAll(event): # add setdist to varylist etc. so that it is displayed in Seq Res table varyList.append('setdist') sigList.append(None) - covar = np.lib.pad(covar, (0,1), 'constant') + covar = np.pad(covar, (0,1), 'constant') # vals.append(Data.get('samplechangerpos',Data['samplechangerpos'])) # varyList.append('chgrpos') # sigList.append(None) - + SeqResult[name] = {'variables':vals,'varyList':varyList,'sig':sigList,'Rvals':[], 'covMatrix':covar,'title':name,'parmDict':parmDict} - SeqResult['histNames'] = Names + SeqResult['histNames'] = Names G2frame.GPXtree.SetItemPyData(Id,SeqResult) finally: dlg.Destroy() @@ -378,7 +378,8 @@ def OnMultiGainMap(event): G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name) Npix,imagefile,imagetag = G2IO.GetCheckImageFile(G2frame,G2frame.Image) pth = os.path.split(os.path.abspath(imagefile))[0] - ImDat = copy.deepcopy(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))) + ImDat = copy.deepcopy(G2frame.GPXtree.GetItemPyData( + G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))) sumImg = GetImageZ(G2frame,ImDat) #force defaults for GainMap calc ImDat['IOtth'] = [0.1,60.0] @@ -392,36 +393,25 @@ def OnMultiGainMap(event): G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks'))) Integrate = G2img.ImageIntegrate(sumImg,ImDat,ImMsk,blkSize) Iy,azms,Ix = Integrate[:3] - GainMap = G2img.MakeGainMap(sumImg,Ix,Iy,ImDat,blkSize)*1000. - GainMap = np.where(GainMap > 1200,0,GainMap) - GainMap = np.where(GainMap < 800,0,GainMap) + GainMap,Mask = G2img.MakeGainMap(sumImg,Ix,Iy,ImDat,ImMsk,blkSize) + if ImDat['invert_x']: + Mask = np.flip(Mask,1) + if ImDat['invert_y']: + Mask = np.flip(Mask,0) + GainMap *= 1000 + GainMap = ma.array(GainMap,mask=Mask) + GainMap = ma.where(GainMap > 1200,0,GainMap) + GainMap = ma.where(GainMap < 800,0,GainMap) + Mask = ma.getmask(GainMap) ImDat['formatName'] = 'GSAS-II image' ImDat['range'] = [(500,2000),[800,1200]] if First: First = False - GMsum = np.where(GainMap>0,GainMap,0.0) - pixels = np.where(GainMap>0,1,0) + GMsum = np.where(Mask,0.0,GainMap) + pixels = np.where(Mask,0,1) else: - GMsum += np.where(GainMap>0,GainMap,0.0) - pixels += np.where(GainMap >0,1,0) - #leave for possible diagnostics? - # ImMsk['Thresholds'] = [(500.,2000.),[800.,1200.]] - # GMname = os.path.splitext(imagefile)[0]+'_GM.G2img' - # G2IO.PutG2Image(GMname,[],ImDat,Npix,GainMap) - # Name = 'IMG '+os.path.split(GMname)[1] - # Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Name) - # if not Id: - # Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Name) - # G2frame.GPXtree.SetItemPyData(Id,[Npix,GMname]) - # G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),[]) - # G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Image Controls'),ImDat) - # G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Masks'),ImMsk) - # else: - # G2frame.GPXtree.SetItemPyData(Id,[Npix,GMname]) - # G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Comments'),[]) - # G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'),ImDat) - # G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Masks'),ImMsk) - #end of diagnostic block + GMsum += np.where(Mask,0.0,GainMap) + pixels += np.where(Mask,0,1) finally: dlg.Destroy() GMsum = np.where(pixels>0,GMsum/pixels,0) @@ -434,7 +424,14 @@ def OnMultiGainMap(event): if dlg.ShowModal() == wx.ID_OK: newimagefile = dlg.GetPath() newimagefile = G2IO.FileDlgFixExt(dlg,newimagefile) - ImMsk['Thresholds'] = [(500.,2000.),[800.,1200.]] + ImSize = ImDat['size'] + pixSize = ImDat['pixelSize'] + ImDat.update({'tilt':0.0,'rotation':0.0,'distance':1000.0,'showLines':False, + 'calibrant':' ', + 'center':[500.*ImSize[0]/pixSize[0],500.*ImSize[1]/pixSize[1]]}) + ImMsk = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[], + 'Thresholds':[(500.,2000.),[800.,1200.]], + 'SpotMask':{'esdMul':2.,'spotMask':None}} #remove all masks G2IO.PutG2Image(newimagefile,[],ImDat,Npix,GMsum) Name = 'IMG '+os.path.split(newimagefile)[1] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Name) @@ -545,7 +542,7 @@ def OnDistRecalib(event): covData = {'title':'Multi-distance recalibrate','covMatrix':covar,'varyList':varList,'variables':result[1]} Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance') G2frame.GPXtree.SetItemPyData(Id,covData) - + for item in items: name = Names[item] print ('updating',name) @@ -571,8 +568,8 @@ def OnDistRecalib(event): G2frame.GPXtree.SelectItem(G2frame.root) # there is probably a better way to force the reload of the current page wx.CallAfter(G2frame.GPXtree.SelectItem,startID) #GSASIIpath.IPyBreak() - - + + # create a sequential table? # Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential image calibration results') # if Id: @@ -590,14 +587,14 @@ def OnDistRecalib(event): # # add setdist to varylist etc. so that it is displayed in Seq Res table # varyList.append('setdist') # sigList.append(None) -# covar = np.lib.pad(covar, (0,1), 'constant') +# covar = np.pad(covar, (0,1), 'constant') # vals.append(Data.get('samplechangerpos',Data['samplechangerpos'])) # varyList.append('chgrpos') # sigList.append(None) - + # SeqResult[name] = {'variables':vals,'varyList':varyList,'sig':sigList,'Rvals':[], # 'covMatrix':covar,'title':name,'parmDict':parmDict} -# SeqResult['histNames'] = Names +# SeqResult['histNames'] = Names # G2frame.GPXtree.SetItemPyData(Id,SeqResult) else: wx.BeginBusyCursor() @@ -610,13 +607,13 @@ def OnDistRecalib(event): # G2plt.PlotExposedImage(G2frame,event=None) # G2frame.GPXtree.SelectItem(Id) - + def OnClearCalib(event): data['ring'] = [] data['rings'] = [] data['ellipses'] = [] G2plt.PlotExposedImage(G2frame,event=event) - + def ResetThresholds(): Imin = max(0.,np.min(G2frame.ImageZ)) Imax = np.max(G2frame.ImageZ) @@ -627,13 +624,13 @@ def ResetThresholds(): def OnIntegrate(event,useTA=None,useMask=None): '''Integrate image in response to a menu event or from the AutoIntegrate - dialog. In the latter case, event=None. + dialog. In the latter case, event=None. ''' CleanupMasks(masks) sumImg = GetImageZ(G2frame,data) if masks.get('SpotMask',{'spotMask':None})['spotMask'] is not None: sumImg = ma.array(sumImg,mask=masks['SpotMask']['spotMask']) - G2frame.Integrate = G2img.ImageIntegrate(sumImg,data,masks,blkSize,useTA=useTA,useMask=useMask) + G2frame.Integrate = G2img.ImageIntegrate(sumImg,data,masks,blkSize,useTA=useTA,useMask=useMask) G2frame.PauseIntegration = G2frame.Integrate[-1] del sumImg #force cleanup Id = G2IO.SaveIntegration(G2frame,G2frame.PickId,data,(event is None)) @@ -641,7 +638,7 @@ def OnIntegrate(event,useTA=None,useMask=None): G2frame.GPXtree.SelectItem(Id) G2frame.GPXtree.Expand(Id) for item in G2frame.MakePDF: item.Enable(True) - + def OnIntegrateAll(event): Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',]) dlg = G2G.G2MultiChoiceDialog(G2frame,'Image integration controls','Select images to integrate:',Names) @@ -692,7 +689,7 @@ def OnIntegrateAll(event): del image #force cleanup pId = G2IO.SaveIntegration(G2frame,CId,Data) oldData = Data - finally: + finally: dlgp.Destroy() G2frame.EnablePlot = True if pId: @@ -701,7 +698,7 @@ def OnIntegrateAll(event): G2frame.PatternId = pId finally: dlg.Destroy() - + def OnCopyControls(event): Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',]) if len(Names) == 1: @@ -737,7 +734,7 @@ def OnCopyControls(event): finally: dlg.Destroy() if G2frame.PickId: G2frame.GPXtree.SelectItem(G2frame.PickId) - + def OnCopySelected(event): Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',]) if len(Names) == 1: @@ -770,17 +767,17 @@ def OnCopySelected(event): try: if dlg.ShowModal() == wx.ID_OK: result = dlg.GetSelections() - for i in result: + for i in result: item = Names[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls')) Controls.update(copy.deepcopy(copyDict)) finally: - dlg.Destroy() - + dlg.Destroy() + def OnSaveControls(event): pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', 'image control files (*.imctrl)|*.imctrl',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -790,7 +787,7 @@ def OnSaveControls(event): G2fil.WriteControls(filename,data) finally: dlg.Destroy() - + def OnSaveMultiControls(event): '''Save controls from multiple images ''' @@ -798,8 +795,8 @@ def OnSaveMultiControls(event): item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) while item: name = G2frame.GPXtree.GetItemText(item) - if name.startswith('IMG '): - imglist.append(name) + if name.startswith('IMG '): + imglist.append(name) item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) if not imglist: print('No images!') @@ -837,11 +834,11 @@ def OnSaveMultiControls(event): + '.imctrl') print('writing '+filename) G2fil.WriteControls(filename,data) - + def OnLoadControls(event): pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', 'image control files (*.imctrl)|*.imctrl',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -856,7 +853,7 @@ def OnLoadControls(event): ResetThresholds() G2plt.PlotExposedImage(G2frame,event=event) wx.CallLater(100,UpdateImageControls,G2frame,data,masks) - + def OnLoadMultiControls(event): #TODO: how read in multiple image controls & match them by 'twoth' tag? print('This is not implemented yet, sorry') G2G.G2MessageBox(G2frame,'This is not implemented yet, sorry') @@ -864,7 +861,7 @@ def OnLoadMultiControls(event): #TODO: how read in multiple image contro pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' controlsDict = {} - dlg = wx.FileDialog(G2frame, 'Choose image control files', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose image control files', pth, '', 'image control files (*.imctrl)|*.imctrl',wx.FD_OPEN|wx.FD_MULTIPLE) try: if dlg.ShowModal() == wx.ID_OK: @@ -897,7 +894,7 @@ def OnLoadMultiControls(event): #TODO: how read in multiple image contro G2fil.LoadControls(Slines,imctrls) finally: dlg.Destroy() - + def OnTransferAngles(event): '''Sets the integration range for the selected Images based on the difference in detector distance ''' @@ -955,8 +952,8 @@ def OnTransferAngles(event): .format(dist1,data['IOtth'][0],data['IOtth'][1])) finally: dlg.Destroy() - G2frame.GPXtree.SelectItem(G2frame.PickId) - + G2frame.GPXtree.SelectItem(G2frame.PickId) + def OnResetDist(event): dlg = wx.MessageDialog(G2frame,'Are you sure you want to do this?',caption='Reset dist to set dist',style=wx.YES_NO|wx.ICON_EXCLAMATION) if dlg.ShowModal() != wx.ID_YES: @@ -976,9 +973,9 @@ def OnResetDist(event): finally: dlg.Destroy() wx.CallAfter(UpdateImageControls,G2frame,data,masks) - + # Sizers - Indx = {} + Indx = {} def ComboSizer(): def OnDataType(event): @@ -988,16 +985,16 @@ def OnDataType(event): if data['binType'] == '2-theta': data['binType'] = 'log(q)' #switch default bin type elif 'PWDR' in data['type']: data['SampleAbs'][0] = -np.log(data['SampleAbs'][0]) #switch from trans to muT! - if data['binType'] == 'log(q)': data['binType'] = '2-theta' #switch default bin type + if data['binType'] == 'log(q)': data['binType'] = '2-theta' #switch default bin type wx.CallLater(100,UpdateImageControls,G2frame,data,masks) - + def OnNewColorBar(event): data['color'] = colSel.GetValue() wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event) - + def OnAzmthOff(invalid,value,tc): wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event) - + comboSizer = wx.BoxSizer(wx.HORIZONTAL) comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Type of image data: '),0,WACV) typeSel = wx.ComboBox(parent=G2frame.dataWindow,value=typeDict[data['type']],choices=typeList, @@ -1015,10 +1012,10 @@ def OnAzmthOff(invalid,value,tc): typeHint=float,OnLeave=OnAzmthOff) comboSizer.Add(azmthOff,0,WACV) return comboSizer - + def MaxSizer(): '''Defines a sizer with sliders and TextCtrl widgets for controlling the colormap - for the image, as well as callback routines. + for the image, as well as callback routines. ''' def OnNewVal(invalid,value,tc): '''Called when a Imax or Imin value is typed into a Validated TextCrtl (which puts @@ -1047,8 +1044,8 @@ def OnNewVal(invalid,value,tc): Page.canvas.draw() else: Page.canvas.draw_idle() - - G2frame.prevMaxValue = None + + G2frame.prevMaxValue = None def OnMaxSlider(event): val = maxSel.GetValue() if G2frame.prevMaxValue == val: return # if this val has been processed, no need to repeat @@ -1067,8 +1064,8 @@ def OnMaxSlider(event): Page.canvas.draw() else: Page.canvas.draw_idle() - - G2frame.prevMinValue = None + + G2frame.prevMinValue = None def OnMinSlider(event): val = minSel.GetValue() scaleSel.SetSelection(len(scaleChoices)-1) @@ -1087,7 +1084,7 @@ def OnMinSlider(event): Page.canvas.draw() else: Page.canvas.draw_idle() - + def OnAutoSet(event): '''Responds to a button labeled 95%, etc; Sets the Imax and Imin values for the image so that 95% (etc.) of pixels are inside the color map limits. @@ -1120,15 +1117,15 @@ def OnAutoSet(event): Page.canvas.draw() else: Page.canvas.draw_idle() - + def OnLineScan(event): data['linescan'][0] = linescan.GetValue() wx.CallAfter(UpdateImageControls,G2frame,data,masks) G2plt.PlotExposedImage(G2frame,event=event) - + def OnNewLineScan(invalid,value,tc): G2plt.PlotExposedImage(G2frame,event=None) - + def OnMoveAzm(event): incr = azmSpin.GetValue() if incr == 0: return # ignore SetValue(0) event @@ -1204,21 +1201,21 @@ def OnMoveAzm(event): maxSizer.Add(autoSizer) return maxSizer - + def CalibCoeffSizer(): - + def OnCalRef(event): Obj = event.GetEventObject() name = Indx[Obj] data['varyList'][name] = Obj.GetValue() - + calibSizer = wx.FlexGridSizer(0,2,5,5) calibSizer.SetFlexibleDirection(wx.HORIZONTAL) - calibSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration coefficients'),0,WACV) + calibSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration coefficients'),0,WACV) calibSizer.Add((5,0),0) Names = ['det-X','det-Y','wave','dist','tilt','phi'] if 'PWDR' in data['type']: - Names.append('dep') + Names.append('dep') Parms = {'dist':['Distance',(10,3),data,'distance'],'det-X':['Beam center X',(10,3),data['center'],0], 'det-Y':['Beam center Y',(10,3),data['center'],1],'tilt':['Tilt angle*',(10,3),data,'tilt'], 'phi':['Tilt rotation*',(10,2),data,'rotation'],'dep':['Penetration*',(10,4),data,'DetDepth'], @@ -1240,13 +1237,13 @@ def OnCalRef(event): Parms[name][3],nDig=Parms[name][1],typeHint=float) calibSizer.Add(calVal,0,WACV) return calibSizer - + def IntegrateSizer(): - + def OnNewBinType(event): data['binType'] = binSel.GetValue() wx.CallLater(100,UpdateImageControls,G2frame,data,masks) - + def OnIOtth(invalid,value,tc): '''Respond to a change in integration 2theta range ''' @@ -1260,7 +1257,7 @@ def OnIOtth(invalid,value,tc): else: data['IOtth'] = [Ltth,Utth] wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event) - + def OnLRazim(invalid,value,tc): '''Respond to a change in integration azimuth range ''' @@ -1276,18 +1273,18 @@ def OnLRazim(invalid,value,tc): data['LRazimuth'] = [Lazm,Razm] wx.CallAfter(UpdateImageControls,G2frame,data,masks) wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event) - + def OnNumOutAzms(invalid,value,tc): wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event) - + def OnNumOutBins(invalid,value,tc): # make sure # channels is divisible by 4 data['outChannels'] = (data['outChannels']//4)*4 outChan.ChangeValue(data['outChannels']) - + def OnOblique(event): data['Oblique'][1] = not data['Oblique'][1] - + def OnSampleShape(event): data['SampleShape'] = samShape.GetValue() if 'Cylind' in data['SampleShape']: @@ -1295,15 +1292,15 @@ def OnSampleShape(event): elif 'Fixed' in data['SampleShape']: data['SampleAbs'][0] = 1.0 wx.CallLater(100,UpdateImageControls,G2frame,data,masks) - + def OnSamAbs(event): data['SampleAbs'][1] = not data['SampleAbs'][1] wx.CallLater(100,UpdateImageControls,G2frame,data,masks) - + def OnShowLines(event): data['showLines'] = not data['showLines'] G2plt.PlotExposedImage(G2frame,event=event) - + def OnFullIntegrate(event): Lazm = data['LRazimuth'][0] if data['fullIntegrate']: @@ -1314,7 +1311,7 @@ def OnFullIntegrate(event): data['LRazimuth'] = [Lazm,Lazm+360.] wx.CallLater(100,UpdateImageControls,G2frame,data,masks) G2plt.PlotExposedImage(G2frame,event=event) - + def OnSetDefault(event): if data['setDefault']: G2frame.imageDefault = {} @@ -1324,29 +1321,29 @@ def OnSetDefault(event): G2frame.imageDefault['setDefault'] = False if 'formatName' in G2frame.imageDefault: del G2frame.imageDefault['formatName'] data['setDefault'] = True - + def OnCenterAzm(event): data['centerAzm'] = not data['centerAzm'] wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event) - + def OnApplyPola(event): data['PolaVal'][1] = not data['PolaVal'][1] - + def OnIfPink(event): data['IfPink'] = not data['IfPink'] - + def OnOchoice(event): data['orientation'] = ochoice.GetValue() - + dataSizer = wx.FlexGridSizer(0,2,5,3) - dataSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Integration coefficients'),0,WACV) + dataSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Integration coefficients'),0,WACV) dataSizer.Add((5,0),0) if 'PWDR' in data['type']: binChoice = ['2-theta','Q'] elif 'SASD' in data['type']: binChoice = ['2-theta','Q','log(q)'] dataSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Bin style: Constant step bins in'),0,WACV) - littleSizer = wx.BoxSizer(wx.HORIZONTAL) + littleSizer = wx.BoxSizer(wx.HORIZONTAL) binSel = wx.ComboBox(G2frame.dataWindow,value=data['binType'],choices=binChoice, style=wx.CB_READONLY|wx.CB_DROPDOWN) binSel.Bind(wx.EVT_COMBOBOX, OnNewBinType) @@ -1359,7 +1356,7 @@ def OnOchoice(event): binType = '2-theta' if 'q' in data['binType'].lower(): binType = 'Q' - dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Inner/Outer '+binType),0,WACV) + dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Inner/Outer '+binType),0,WACV) IOtth = data['IOtth'][:] if 'q' in data['binType'].lower(): wave = data['wavelength'] @@ -1461,24 +1458,24 @@ def OnOchoice(event): polaVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['PolaVal'],0,nDig=(10,3),typeHint=float,xmin=0.001,xmax=0.999) littleSizer.Add(polaVal,0,WACV) dataSizer.Add(littleSizer,0,) - + return dataSizer - + def BackSizer(): - + global oldFlat def OnBackImage(event): data['background image'][0] = backImage.GetValue() G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True) ResetThresholds() wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event) - + def OnDarkImage(event): data['dark image'][0] = darkImage.GetValue() G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True) ResetThresholds() wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event) - + def OnFlatBkg(invalid,value,tc): global oldFlat G2frame.ImageZ += int(oldFlat-data['Flat Bkg']) @@ -1489,13 +1486,13 @@ def OnFlatBkg(invalid,value,tc): def OnMult(invalid,value,tc): G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True) wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event) - + def OnGainMap(event): data['Gain map'] = gainMap.GetValue() G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True) ResetThresholds() wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event) - + backSizer = wx.FlexGridSizer(0,6,5,5) oldFlat = data.get('Flat Bkg',0.) @@ -1533,9 +1530,9 @@ def OnGainMap(event): gainMap.Bind(wx.EVT_COMBOBOX,OnGainMap) backSizer.Add(gainMap) return backSizer - + def CalibSizer(): - + def OnNewCalibrant(event): data['calibrant'] = calSel.GetValue().strip() if data['calibrant']: @@ -1553,19 +1550,19 @@ def OnNewCalibrant(event): G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBRATE,enable=False) G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMCALIBRATE,enable=False) G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBALL,enable=False) - + def OnCalibSkip(event): data['calibskip'] = int(calibSkip.GetValue()) - + def OnPixLimit(event): data['pixLimit'] = int(pixLimit.GetValue()) - + def OnSetRings(event): data['setRings'] = not data['setRings'] G2plt.PlotExposedImage(G2frame,event=event) - + calibSizer = wx.FlexGridSizer(0,3,5,5) - comboSizer = wx.BoxSizer(wx.HORIZONTAL) + comboSizer = wx.BoxSizer(wx.HORIZONTAL) comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibrant '),0,WACV) if (GSASIIpath.GetConfigValue('Image_calibrant') and GSASIIpath.GetConfigValue('Image_calibrant') in calList and @@ -1576,27 +1573,27 @@ def OnSetRings(event): calSel.Bind(wx.EVT_COMBOBOX, OnNewCalibrant) comboSizer.Add(calSel,0,WACV) calibSizer.Add(comboSizer,0) - - comboSizer = wx.BoxSizer(wx.HORIZONTAL) + + comboSizer = wx.BoxSizer(wx.HORIZONTAL) comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calib lines to skip '),0,WACV) calibSkip = wx.ComboBox(parent=G2frame.dataWindow,value=str(data['calibskip']),choices=[str(i) for i in range(25)], style=wx.CB_READONLY|wx.CB_DROPDOWN) calibSkip.Bind(wx.EVT_COMBOBOX, OnCalibSkip) comboSizer.Add(calibSkip,0,WACV) calibSizer.Add(comboSizer,0) - - comboSizer = wx.BoxSizer(wx.HORIZONTAL) + + comboSizer = wx.BoxSizer(wx.HORIZONTAL) comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min calib d-spacing '),0,WACV) G2frame.calibDmin = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'calibdmin',nDig=(10,2),typeHint=float,xmin=0.25) comboSizer.Add(G2frame.calibDmin,0,WACV) calibSizer.Add(comboSizer,0) - + comboSizer = wx.BoxSizer(wx.HORIZONTAL) comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min ring I/Ib '),0,WACV) cutOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'cutoff',nDig=(10,2),xmin=0.1) comboSizer.Add(cutOff,0,WACV) calibSizer.Add(comboSizer,0) - + comboSizer = wx.BoxSizer(wx.HORIZONTAL) comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Pixel search range '),0,WACV) pixLimit = wx.ComboBox(parent=G2frame.dataWindow,value=str(data['pixLimit']),choices=['1','2','5','10','15','20'], @@ -1604,7 +1601,7 @@ def OnSetRings(event): pixLimit.Bind(wx.EVT_COMBOBOX, OnPixLimit) comboSizer.Add(pixLimit,0,WACV) calibSizer.Add(comboSizer,0) - + comboSizer = wx.BoxSizer(wx.HORIZONTAL) setRings = wx.CheckBox(parent=G2frame.dataWindow,label='Show ring picks?') comboSizer.Add(setRings,0) @@ -1612,9 +1609,9 @@ def OnSetRings(event): setRings.SetValue(data['setRings']) calibSizer.Add(comboSizer,0) return calibSizer - + def GonioSizer(): - + def OnGlobalEdit(event): Names = [] Items = [] @@ -1645,7 +1642,7 @@ def OnGlobalEdit(event): finally: dlg.Destroy() G2frame.GPXtree.SelectItem(G2frame.PickId) - + gonioSizer = wx.BoxSizer(wx.HORIZONTAL) names = ['Omega','Chi','Phi'] gonioSizer.Add(wx.StaticText(G2frame.dataWindow,-1,'Sample goniometer angles: '),0,WACV) @@ -1677,9 +1674,9 @@ def OnSelectPhases(event): def computePhaseRings(): 'generate the powder reflections for the selected phase/calibrants' - import GSASIIlattice as G2lat - import GSASIIspc as G2spc - import GSASIIpwd as G2pwd + from . import GSASIIlattice as G2lat + from . import GSASIIspc as G2spc + from . import GSASIIpwd as G2pwd dmin = data['calibdmin'] G2frame.PhaseRing2Th = [] for p in phaseOpts['selList']: @@ -1724,8 +1721,8 @@ def computePhaseRings(): tth = 2.0*asind(data['wavelength']/(2.*H[3])) G2frame.PhaseRing2Th.append((tth,rColor,rWid,rStyle)) G2plt.PlotExposedImage(G2frame,event=None) - - # UpdateImageControls starts here: Image Controls main code + + # UpdateImageControls starts here: Image Controls main code G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ImageMenu) #patch: fix for old files: if 'azmthOff' not in data: @@ -1743,7 +1740,7 @@ def computePhaseRings(): if 'IfPink' not in data: data['IfPink'] = False #end fix - + if IntegrateOnly: Masks = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks')) @@ -1755,7 +1752,7 @@ def computePhaseRings(): # oldMhash = Mhash OnIntegrate(None,useTA=useTA,useMask=useMask) return - + G2frame.GetStatusBar().SetStatusText('* Global parameters in Multi-dist recalib.',1) colorList = sorted([m for m in mpl.cm.datad.keys() ]+['GSPaired','GSPaired_r',],key=lambda s: s.lower()) #if not m.endswith("_r") calList = sorted([m for m in calFile.Calibrants.keys()],key=lambda s: s.lower()) @@ -1827,27 +1824,27 @@ def OnIntPDFtool(event): wx.CallAfter(G2frame.dataWindow.SetDataSize) mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) - mainSizer.Add((5,10),0) + mainSizer.Add((5,10),0) mainSizer.Add(ComboSizer(),0,wx.ALIGN_LEFT) mainSizer.Add((5,5),0) Range = data['range'] # allows code to be same in Masks - MaxSizer = MaxSizer() #keep this so it can be changed in BackSizer + MaxSizer = MaxSizer() #keep this so it can be changed in BackSizer mainSizer.Add(MaxSizer,0,wx.ALIGN_LEFT|wx.EXPAND|wx.ALL) - + mainSizer.Add((5,5),0) DataSizer = wx.FlexGridSizer(0,2,5,0) DataSizer.Add(CalibCoeffSizer(),0) - DataSizer.Add(IntegrateSizer(),0) + DataSizer.Add(IntegrateSizer(),0) mainSizer.Add(DataSizer,0) - mainSizer.Add((5,5),0) + mainSizer.Add((5,5),0) mainSizer.Add(BackSizer(),0) mainSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration controls:'),0) mainSizer.Add((5,5),0) mainSizer.Add(CalibSizer(),0) mainSizer.Add((5,5),0) - mainSizer.Add(GonioSizer(),0) + mainSizer.Add(GonioSizer(),0) G2frame.dataWindow.SetDataSize() - + ################################################################################ ##### Masks ################################################################################ @@ -1864,11 +1861,11 @@ def CleanupMasks(data): l2 = len(data[key]) if GSASIIpath.GetConfigValue('debug') and l1 != l2: print ('DBG_Mask Cleanup: %s was %d entries, now %d'%(key,l1,l2)) - + def UpdateMasks(G2frame,data): '''Shows and handles the controls on the "Masks" data tree entry ''' - + def OnTextMsg(event): Obj = event.GetEventObject() Obj.SetToolTip('Drag this mask on 2D Powder Image with mouse to change ') @@ -1886,7 +1883,7 @@ def onDeleteMask(event): del(data[typ][num]) wx.CallAfter(UpdateMasks,G2frame,data) G2plt.PlotExposedImage(G2frame,event=event) - + def OnSpotChange(event): r,c = event.GetRow(),event.GetCol() if c == 2: @@ -1923,7 +1920,7 @@ def OnCopyMask(event): MId = G2gd.GetGPXtreeItemId(G2frame,Id,'Masks') Mask = G2frame.GPXtree.GetItemPyData(MId) Mask.update(copy.deepcopy(Data)) - Mask['Thresholds'][1][0] = Thresh[1][0] #copy only lower threshold + Mask['Thresholds'][1][0] = Thresh[1][0] #copy only lower threshold G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'),Mask) finally: dlg.Destroy() @@ -1967,7 +1964,7 @@ def OnCopySelected(event): def OnSaveMask(event): CleanupMasks(data) pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', 'image mask files (*.immask)|*.immask',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -1980,7 +1977,7 @@ def OnSaveMask(event): File.close() finally: dlg.Destroy() - + def OnLoadMask(event): if event.Id == G2G.wxID_MASKLOADNOT: ignoreThreshold = True @@ -1988,23 +1985,23 @@ def OnLoadMask(event): ignoreThreshold = False pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', 'image mask files (*.immask)|*.immask',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetPath() - + G2fil.readMasks(filename,data,ignoreThreshold) wx.CallAfter(UpdateMasks,G2frame,data) - G2plt.PlotExposedImage(G2frame,event=event) + G2plt.PlotExposedImage(G2frame,event=event) finally: dlg.Destroy() - + def OnFindPixelMask(event): '''Do auto search for pixels to mask Called from (Masks) Operations->"Pixel mask search" ''' - Controls = G2frame.GPXtree.GetItemPyData( + Controls = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls')) try: wave = Controls['wavelength'] @@ -2036,7 +2033,7 @@ def OnFindPixelMask(event): dlg.Destroy() wx.EndBusyCursor() return - + # since we now have a Cancel button, we really don't need to ask anymore # dlg = wx.MessageDialog(G2frame.dataWindow, # 'NB: This can be slow (0.5 to 2 min)', @@ -2056,7 +2053,7 @@ def OnFindPixelMask(event): print(' Pixel mask search time: %.2f m'%((time.time()-time0)/60.)) wx.CallAfter(UpdateMasks,G2frame,data) wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event) - + def OnAutoFindPixelMask(event): Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',]) fast = G2img.TestFastPixelMask() @@ -2125,44 +2122,44 @@ def OnAutoFindPixelMask(event): def OnDeleteSpotMask(event): data['Points'] = [] wx.CallAfter(UpdateMasks,G2frame,data) - G2plt.PlotExposedImage(G2frame,newPlot=True,event=event) - + G2plt.PlotExposedImage(G2frame,newPlot=True,event=event) + def ToggleSpotMaskMode(event): G2plt.ToggleMultiSpotMask(G2frame) - + def OnNewArcMask(event): 'Start a new arc mask' G2frame.MaskKey = 'a' G2plt.OnStartMask(G2frame) - + def OnNewRingMask(event): 'Start a new ring mask' G2frame.MaskKey = 'r' G2plt.OnStartMask(G2frame) - + def OnNewXlineMask(event): 'Start a new x-line mask' G2frame.MaskKey = 'x' G2plt.OnStartMask(G2frame) - + def OnNewYlineMask(event): 'Start a new y-line mask' G2frame.MaskKey = 'y' G2plt.OnStartMask(G2frame) - + def OnNewPolyMask(event): 'Start a new polygon mask' G2frame.MaskKey = 'p' G2plt.OnStartMask(G2frame) - + def OnNewFrameMask(event): 'Start a new Frame mask' G2frame.MaskKey = 'f' G2plt.OnStartMask(G2frame) - + def MaxSizer(): '''Defines a sizer with sliders and TextCtrl widgets for controlling the colormap - for the image, as well as callback routines. + for the image, as well as callback routines. ''' def OnNewVal(invalid,value,tc): '''Called when a Imax or Imin value is typed into a Validated TextCrtl (which puts @@ -2191,8 +2188,8 @@ def OnNewVal(invalid,value,tc): Page.canvas.draw() else: Page.canvas.draw_idle() - - G2frame.prevMaxValue = None + + G2frame.prevMaxValue = None def OnMaxSlider(event): val = maxSel.GetValue() if G2frame.prevMaxValue == val: return # if this val has been processed, no need to repeat @@ -2211,8 +2208,8 @@ def OnMaxSlider(event): Page.canvas.draw() else: Page.canvas.draw_idle() - - G2frame.prevMinValue = None + + G2frame.prevMinValue = None def OnMinSlider(event): val = minSel.GetValue() scaleSel.SetSelection(len(scaleChoices)-1) @@ -2231,7 +2228,7 @@ def OnMinSlider(event): Page.canvas.draw() else: Page.canvas.draw_idle() - + def OnAutoSet(event): '''Responds to a button labeled 95%, etc; Sets the Imax and Imin values for the image so that 95% (etc.) of pixels are inside the color map limits. @@ -2270,7 +2267,7 @@ def OnAutoSet(event): # Plot color scaling uses limits as below: # (Imin0, Imax0) => Range[0] = data['range'][0] # lowest to highest pixel intensity # [Imin, Imax] => Range[1] = data['range'][1] # lowest to highest pixel intensity on cmap scale - + maxSizer = wx.BoxSizer(wx.VERTICAL) slideSizer = wx.FlexGridSizer(2,3,5,5) slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Max intensity'),0,WACV) @@ -2313,12 +2310,12 @@ def OnAutoSet(event): autoSizer.Add(scaleSel,0,WACV) maxSizer.Add(autoSizer) return maxSizer - + def OnDelPixMask(event): data['SpotMask'] = {'esdMul':3.,'spotMask':None} wx.CallAfter(UpdateMasks,G2frame,data) G2plt.PlotExposedImage(G2frame,event=event) - + def OnAzimuthPlot(event): GkTheta = chr(0x03f4) Obj = event.GetEventObject() @@ -2403,7 +2400,7 @@ def OnAzimuthPlot(event): CId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls') controlData = G2frame.GPXtree.GetItemPyData(CId) Range = controlData['range'] - MaxSizer = MaxSizer() #keep this so it can be changed in BackSizer + MaxSizer = MaxSizer() #keep this so it can be changed in BackSizer mainSizer.Add(MaxSizer,0,wx.ALIGN_LEFT|wx.EXPAND|wx.ALL) littleSizer = wx.FlexGridSizer(0,3,0,5) @@ -2548,7 +2545,7 @@ def OnAzimuthPlot(event): locationcode=code,handler=onDeleteMask) littleSizer.Add(arcDelete,0,WACV) mainSizer.Add(littleSizer,0,) - + if Xlines: lbl = wx.StaticText(parent=G2frame.dataWindow,label=' X line masks') lbl.SetBackgroundColour(wx.Colour(200,200,210)) @@ -2563,7 +2560,7 @@ def OnAzimuthPlot(event): locationcode=code,handler=onDeleteMask) littleSizer.Add(xlineDelete,0,WACV) mainSizer.Add(littleSizer,0,) - + if Ylines: lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Y line masks') lbl.SetBackgroundColour(wx.Colour(200,200,210)) @@ -2578,7 +2575,7 @@ def OnAzimuthPlot(event): locationcode=code,handler=onDeleteMask) littleSizer.Add(ylineDelete,0,WACV) mainSizer.Add(littleSizer,0,) - + if Polygons: lbl = wx.StaticText(parent=G2frame.dataWindow, label=' Polygon masks (on plot LB vertex drag to move, RB vertex drag to insert)') @@ -2628,18 +2625,18 @@ def UpdateStressStrain(G2frame,data): '''Shows and handles the controls on the "Stress/Strain" data tree entry ''' - + def OnAppendDzero(event): data['d-zero'].append({'Dset':1.0,'Dcalc':0.0,'pixLimit':10,'cutoff':1.0, 'ImxyObs':[[],[]],'ImtaObs':[[],[]],'ImtaCalc':[[],[]],'Emat':[1.0,1.0,1.0],'fixDset':False,'Ivar':0}) UpdateStressStrain(G2frame,data) - + def OnUpdateDzero(event): for item in data['d-zero']: if item['Dcalc']: #skip unrefined ones item['Dset'] = item['Dcalc'] UpdateStressStrain(G2frame,data) - + def OnCopyStrSta(event): Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',]) if len(Names) == 1: @@ -2667,7 +2664,7 @@ def OnCopyStrSta(event): def OnLoadStrSta(event): pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', 'image control files (*.strsta)|*.strsta',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -2687,7 +2684,7 @@ def OnLoadStrSta(event): def OnSaveStrSta(event): pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', 'image control files (*.strsta)|*.strsta',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -2711,12 +2708,12 @@ def OnSaveStrSta(event): File.close() finally: dlg.Destroy() - + def OnStrStaSample(event): filename = '' pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose multihistogram metadata text file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose multihistogram metadata text file', pth, '', 'metadata file (*.*)|*.*',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -2735,7 +2732,7 @@ def OnStrStaSample(event): Stuff = S[:-1].split() itemNames.append(Stuff[0]) newItems.append(Stuff[1:]) - S = File.readline() + S = File.readline() File.close() finally: dlg.Destroy() @@ -2760,7 +2757,7 @@ def OnStrStaSample(event): G2frame.ErrorDialog('Nothing to do','No columns identified') return histList = [] - item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) + item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) while item: name = G2frame.GPXtree.GetItemText(item) if name.startswith('IMG'): @@ -2778,9 +2775,9 @@ def OnStrStaSample(event): newItems[item] = float(dataDict[name][colIds[item]]) Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist) stsrData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain')) - stsrData.update(newItems) - UpdateStressStrain(G2frame,data) - + stsrData.update(newItems) + UpdateStressStrain(G2frame,data) + def OnPlotStrSta(event): Controls = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls')) @@ -2791,14 +2788,14 @@ def OnPlotStrSta(event): G2plt.PlotXY(G2frame,RingInt,labelX='Azimuth', labelY='MRD',newPlot=True,Title='Ring Intensities', names=Names,lines=True) - + def OnSaveStrRing(event): Controls = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls')) RingInt = G2img.IntStrSta(G2frame.ImageZ,data,Controls) Names = ['d=%.3f'%(ring['Dcalc']) for ring in data['d-zero']] pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose strain ring intensity file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose strain ring intensity file', pth, '', 'ring intensity file (*.txt)|*.txt',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -2813,8 +2810,8 @@ def OnSaveStrRing(event): File.close() finally: dlg.Destroy() - - + + def OnFitStrSta(event): Controls = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls')) @@ -2823,7 +2820,7 @@ def OnFitStrSta(event): UpdateStressStrain(G2frame,data) G2plt.PlotExposedImage(G2frame,event=event) G2plt.PlotStrain(G2frame,data,newPlot=True) - + def OnFitAllStrSta(event): choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',]) od = {'label_1':'Copy to next','value_1':False,'label_2':'Reverse order','value_2':False} @@ -2845,8 +2842,8 @@ def OnFitAllStrSta(event): dlg.Destroy() if not names: return - dlg = wx.ProgressDialog('Sequential IMG Strain fit','Data set name = '+names[0],len(names), - style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT) + dlg = wx.ProgressDialog('Sequential IMG Strain fit','Data set name = '+names[0],len(names), + style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT) wx.BeginBusyCursor() goodnames = [] if od['value_2']: @@ -2905,17 +2902,17 @@ def OnFitAllStrSta(event): dlg.Destroy() print (' ***** Sequential strain refinement successful *****') finally: - wx.EndBusyCursor() + wx.EndBusyCursor() SeqResult['histNames'] = choices G2frame.GPXtree.SetItemPyData(Id,SeqResult) G2frame.GPXtree.SelectItem(Id) print ('All images fitted') - + def SamSizer(): - + def OnStrainType(event): data['Type'] = strType.GetValue() - + samSizer = wx.FlexGridSizer(0,4,0,5) samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Strain type: '),0,WACV) strType = wx.ComboBox(G2frame.dataWindow,value=data['Type'],choices=['True','Conventional'], @@ -2935,9 +2932,9 @@ def OnStrainType(event): samSizer.Add(samLoad,0,WACV) return samSizer - + def DzeroSizer(): - + def OnStrainChange(event): # print (event) r,c = event.GetRow(),event.GetCol() @@ -2955,7 +2952,7 @@ def OnStrainChange(event): wx.CallAfter(UpdateStressStrain,G2frame,data) G2plt.PlotExposedImage(G2frame,event=event) G2plt.PlotStrain(G2frame,data,newPlot=True) - + def OnSetCol(event): c = event.GetCol() if c == 1: @@ -2972,12 +2969,12 @@ def OnSetCol(event): else: for row in range(StrainGrid.GetNumberRows()): data['d-zero'][row]['fixDset']=False wx.CallAfter(UpdateStressStrain,G2frame,data) - + colTypes = [wg.GRID_VALUE_FLOAT+':10,5',wg.GRID_VALUE_BOOL,wg.GRID_VALUE_FLOAT+':10,5',wg.GRID_VALUE_FLOAT+':10,1', wg.GRID_VALUE_CHOICE+':1,2,5,10,15,20',]+3*[wg.GRID_VALUE_FLOAT+':10,2',]+[wg.GRID_VALUE_BOOL,]+2*[wg.GRID_VALUE_FLOAT+':10,2',] colIds = ['d-zero','Poisson\n mean?','d-zero ave','I/Ib','nPix','e11','e12','e22','Delete?','h-mustrain','Ivar'] rowIds = [str(i) for i in range(len(data['d-zero']))] - table = [[item['Dset'],item.get('fixDset',False),item['Dcalc'],item['cutoff'],item['pixLimit'], + table = [[item['Dset'],item.get('fixDset',False),item['Dcalc'],item['cutoff'],item['pixLimit'], item['Emat'][0],item['Emat'][1],item['Emat'][2],False,1.e6*(item['Dcalc']/item['Dset']-1.),item['Ivar']] for item in data['d-zero']] StrainTable = G2G.Table(table,rowLabels=rowIds,colLabels=colIds,types=colTypes) StrainGrid = G2G.GSGrid(G2frame.dataWindow) @@ -3000,25 +2997,25 @@ def OnSetCol(event): if 'Sample load' not in data: data['Sample load'] = 0.0 # end patches - + # UpdateStressStrain starts here G2frame.dataWindow.ClearData() G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.StrStaMenu) G2frame.Bind(wx.EVT_MENU, OnAppendDzero, id=G2G.wxID_APPENDDZERO) G2frame.Bind(wx.EVT_MENU, OnUpdateDzero, id=G2G.wxID_UPDATEDZERO) G2frame.Bind(wx.EVT_MENU, OnFitStrSta, id=G2G.wxID_STRSTAFIT) - G2frame.Bind(wx.EVT_MENU, OnPlotStrSta, id=G2G.wxID_STRSTAPLOT) - G2frame.Bind(wx.EVT_MENU, OnSaveStrRing, id=G2G.wxID_STRRINGSAVE) + G2frame.Bind(wx.EVT_MENU, OnPlotStrSta, id=G2G.wxID_STRSTAPLOT) + G2frame.Bind(wx.EVT_MENU, OnSaveStrRing, id=G2G.wxID_STRRINGSAVE) G2frame.Bind(wx.EVT_MENU, OnFitAllStrSta, id=G2G.wxID_STRSTAALLFIT) G2frame.Bind(wx.EVT_MENU, OnCopyStrSta, id=G2G.wxID_STRSTACOPY) G2frame.Bind(wx.EVT_MENU, OnLoadStrSta, id=G2G.wxID_STRSTALOAD) G2frame.Bind(wx.EVT_MENU, OnSaveStrSta, id=G2G.wxID_STRSTASAVE) - G2frame.Bind(wx.EVT_MENU, OnStrStaSample, id=G2G.wxID_STRSTSAMPLE) + G2frame.Bind(wx.EVT_MENU, OnStrStaSample, id=G2G.wxID_STRSTSAMPLE) if G2frame.StrainKey == 'a': #probably doesn't happen G2frame.GetStatusBar().SetStatusText('Add strain ring active - LB pick d-zero value',1) else: G2frame.GetStatusBar().SetStatusText("To add strain data: On 2D Powder Image, key a:add ring",1) - + topSizer = G2frame.dataWindow.topBox topSizer.Clear(True) parent = G2frame.dataWindow.topPanel @@ -3034,7 +3031,7 @@ def OnSetCol(event): mainSizer.Add((5,10),0) mainSizer.Add(DzeroSizer()) G2frame.dataWindow.SetDataSize() - + ########################################################################### # Autointegration ########################################################################### @@ -3078,7 +3075,7 @@ def ReadControls(filename): save[key] = eval(val) else: vals = val.strip('[] ').split() - save[key] = [float(vals[0]),float(vals[1])] + save[key] = [float(vals[0]),float(vals[1])] elif key in cntlList: save[key] = eval(val) S = File.readline() @@ -3129,7 +3126,7 @@ def Read_imctrl(imctrl_file): vals = eval(val) else: vals = val.strip('[] ').split() - vals = [float(vals[0]),float(vals[1])] + vals = [float(vals[0]),float(vals[1])] save['center_x'],save['center_y'] = vals[0:2] elif key in cntlList: save[key] = eval(val) @@ -3138,11 +3135,11 @@ def Read_imctrl(imctrl_file): File.close() if fullIntegrate: save['LRazimuth_min'],save['LRazimuth_max'] = 0.,360. return save - + class AutoIntFrame(wx.Frame): '''Creates a wx.Frame window for the Image AutoIntegration. The intent is that this will be used as a non-modal dialog window. - + Implements a Start button that morphs into a pause and resume button. This button starts a processing loop that is repeated every :meth:`PollTime` seconds. @@ -3153,10 +3150,10 @@ class AutoIntFrame(wx.Frame): ''' def __init__(self,G2frame,PollTime=30.0): def OnStart(event): - '''Called when the start button is pressed. Changes button label + '''Called when the start button is pressed. Changes button label to Pause. When Pause is pressed the label changes to Resume. When either Start or Resume is pressed, the processing loop - is started. When Pause is pressed, the loop is stopped. + is started. When Pause is pressed, the loop is stopped. ''' # check inputs for errors before starting #err = '' @@ -3182,24 +3179,24 @@ def OnStart(event): return # we will get to this point if Paused self.OnPause() - + def OnReset(event): '''Called when Reset button is pressed. This stops the processing loop and resets the list of integrated files so - all images can be reintegrated. + all images can be reintegrated. ''' self.btnstart.SetLabel('Restart') self.Status.SetStatusText('Press Restart to reload and re-integrate images matching filter') if self.timer.IsRunning(): self.timer.Stop() self.Reset = True self.Pause = True - + def OnQuit(event): '''Stop the processing loop and close the Frame ''' if self.timer.IsRunning(): self.timer.Stop() # make sure we stop first wx.CallAfter(self.Destroy) - + def OnBrowse(event): '''Responds when the Browse button is pressed to load a file. The routine determines which button was pressed and gets the @@ -3223,7 +3220,7 @@ def OnBrowse(event): pth = G2G.GetExportPath(G2frame) dlg = wx.FileDialog( self, 'Select a PDF parameter file', - pth, self.params['pdfprm'], + pth, self.params['pdfprm'], "PDF controls file (*.pdfprm)|*.pdfprm", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) dlg.CenterOnParent() @@ -3243,10 +3240,10 @@ def OnBrowse(event): lbl = 'PDFPRM information' G2G.G2MessageBox(self,msg,lbl) return - + def OnRadioSelect(event): '''Respond to a radiobutton selection and when in table - mode, get distance-dependent parameters from user. + mode, get distance-dependent parameters from user. ''' self.Evaluator = None if self.useTable.GetValue(): @@ -3296,7 +3293,7 @@ def OnEditTable(event): self.editTable.Enable(False) finally: if dlg: dlg.Destroy() - + def showPDFctrls(event): '''Called to show or hide AutoPDF widgets. Note that fInp4 must be included in the sizer layout with .Show(True) before .Show(False) will work properly. @@ -3312,19 +3309,19 @@ def showPDFctrls(event): else: lbl4.SetForegroundColour("gray") lbl4a.SetForegroundColour("gray") - + def scanPDFprm(**kw): fInp4.invalid = not os.path.exists(fInp4.GetValue()) fInp4._IndicateValidity() - + def OnAutoScale(event): self.AutoScale = autoscale.GetValue() - + def OnAutoScaleName(event): self.AutoScaleName = scalename.GetValue() self.Scale[0] = self.AutoScales[self.AutoScaleName] scaleval.SetValue(self.Scale[0]) - + ################################################## # beginning of __init__ processing ################################################## @@ -3369,7 +3366,7 @@ def OnAutoScaleName(event): self.Scale = [1.0,] G2frame.GPXtree.GetSelection() - size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(self.imageBase) + size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(self.imageBase) self.params['readdir'],fileroot = os.path.split(imagefile) self.params['filter'] = '*'+os.path.splitext(fileroot)[1] self.params['outdir'] = os.path.abspath(self.params['readdir']) @@ -3381,7 +3378,7 @@ def OnAutoScaleName(event): if DefaultAutoScaleNames is not None: for comment in Comments: if '=' in comment: - name,val = comment.split('=',1) + name,val = comment.split('=',1) if name in DefaultAutoScaleNames: try: self.AutoScales[name] = float(val) @@ -3439,7 +3436,7 @@ def OnAutoScaleName(event): OnLeave=self.ShowMatchingFiles) sizer.Add(flterInp) mnsizer.Add(sizer,0,wx.EXPAND,0) - + self.ListBox = wx.ListBox(mnpnl,size=(-1,100)) mnsizer.Add(self.ListBox,1,wx.EXPAND,1) self.ShowMatchingFiles(self.params['filter']) @@ -3535,13 +3532,13 @@ def OnAutoScaleName(event): self.CenterOnParent() self.Show() showPDFctrls(None) - + def TestInput(self,event): for fmt in self.multipleFmtChoices: if not self.params[fmt]: continue if self.multipleFmtChoices[fmt]: continue choices = [] - for f,l in zip(self.fmtlist[0],self.fmtlist[1]): + for f,l in zip(self.fmtlist[0],self.fmtlist[1]): if f[1:] == fmt: choices.append(l) if len(choices) < 2: print('Error: why no choices in TestInput?') @@ -3563,7 +3560,7 @@ def checkPDFprm(self,ShowContents=False): '''Read in the PDF (.pdfprm) parameter file and check for problems. If ShowContents is True, a formatted text version of some of the file contents is returned. If errors are found, the return string will contain - the string "Error:" at least once. + the string "Error:" at least once. ''' self.pdfControls = {} msg = '' @@ -3612,7 +3609,7 @@ def checkPDFprm(self,ShowContents=False): if not G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,name): msg += ' *** missing ***' return msg - + def SetSourceDir(self,event): '''Use a dialog to get a directory for image files ''' @@ -3627,7 +3624,7 @@ def SetSourceDir(self,event): finally: dlg.Destroy() return - + def ShowMatchingFiles(self,value,invalid=False,**kwargs): '''Find and show images in the tree and the image files matching the image file directory (self.params['readdir']) and the image file filter @@ -3649,7 +3646,7 @@ def ShowMatchingFiles(self,value,invalid=False,**kwargs): if msg: msg = "Loaded images to integrate:\n" + msg + "\n" msg1 = "" try: - if os.path.exists(self.params['readdir']): + if os.path.exists(self.params['readdir']): imageList = sorted( glob.glob(os.path.join(self.params['readdir'],self.params['filter']))) if not imageList: @@ -3669,7 +3666,7 @@ def ShowMatchingFiles(self,value,invalid=False,**kwargs): self.ListBox.AppendItems(msg.split('\n')) self.PreventReEntryShowMatch = False return - + def OnPause(self): '''Respond to Pause, changes text on button/Status line, if needed Stops timer @@ -3684,7 +3681,7 @@ def OnPause(self): self.Status.SetStatusText( 'Press Resume to continue integration or Reset to prepare to reintegrate all images') self.Pause = True - + def IntegrateImage(self,img,useTA=None,useMask=None): '''Integrates a single image. Ids for created PWDR entries (more than one is possible) are placed in G2frame.IntgOutList @@ -3700,7 +3697,7 @@ def IntegrateImage(self,img,useTA=None,useMask=None): G2frame,imgId, 'Comments')) for comment in Comments: if '=' in comment: - name,val = comment.split('=',1) + name,val = comment.split('=',1) if name == self.AutoScaleName: val = float(val) if val > 0.: @@ -3741,7 +3738,7 @@ def IntegrateImage(self,img,useTA=None,useMask=None): fileroot += "_AZM" if 'Azimuth' in Sdata: fileroot += str(int(10*Sdata['Azimuth'])) - fileroot += "_" + fileroot += "_" fileroot += namenum # loop over selected formats for fmt in self.params: @@ -3775,7 +3772,7 @@ def IntegrateImage(self,img,useTA=None,useMask=None): print(f'{fmt} not found: unexpected error') else: G2IO.ExportPowder(G2frame,treename,fil+'.x','.'+fmt,hint=hint) # dummy extension (.x) is replaced before write) - + def EnableButtons(self,flag): '''Relabels and enable/disables the buttons at window bottom when auto-integration is running ''' @@ -3785,7 +3782,7 @@ def EnableButtons(self,flag): for item in (self.btnstart,self.btnreset,self.btnclose): item.Enable(flag) self.btnstart.SetLabel('Pause') wx.Yield() - + def ResetFromTable(self,dist): '''Sets integration parameters based on values from the lookup table @@ -3812,9 +3809,9 @@ def ResetFromTable(self,dist): else: self.ImageMasks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[], 'SpotMask':{'esdMul':3.,'spotMask':None},} - + def StartLoop(self): - '''Prepare to start autointegration timer loop. + '''Prepare to start autointegration timer loop. Save current Image params for use in future integrations also label the window so users understand what is being used ''' @@ -3843,7 +3840,7 @@ def StartLoop(self): G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Masks'))) self.Thresholds = self.ImageMasks['Thresholds'][:] if list(self.Thresholds[0]) == self.Thresholds[1]: #avoid copy of unchanged thresholds - del self.ImageMasks['Thresholds'] + del self.ImageMasks['Thresholds'] # make sure all output directories exist if self.params['SeparateDir']: for dfmt in self.fmtlist[0]: @@ -3855,7 +3852,7 @@ def StartLoop(self): os.makedirs(self.params['outdir']) if self.Reset: # special things to do after Reset has been pressed self.G2frame.IntegratedList = [] - + if self.params['Mode'] != 'table': # reset controls and masks for all IMG items in tree to master for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']): # update controls from master @@ -3866,7 +3863,7 @@ def StartLoop(self): ImageMasks = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Masks')) ImageMasks.update(self.ImageMasks) - # delete all PWDR items created after last Start was pressed + # delete all PWDR items created after last Start was pressed idlist = [] item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) while item: @@ -3908,16 +3905,16 @@ def StartLoop(self): return True self.pdfControls[key]['Name'] = fileList[indx] return False - + def OnTimerLoop(self,event): '''A method that is called every :meth:`PollTime` seconds that is used to check for new files and process them. Integrates new images. - Also optionally sets up and computes PDF. + Also optionally sets up and computes PDF. This is called only after the "Start" button is pressed (then its label reads "Pause"). ''' def AutoIntegrateImage(imgId,useTA=None,useMask=None): '''Integrates an image that has been read into the data tree and updates the - AutoInt window. + AutoInt window. ''' img = G2frame.GPXtree.GetItemText(imgId) controlsDict = G2frame.GPXtree.GetItemPyData( @@ -3940,7 +3937,7 @@ def AutoIntegrateImage(imgId,useTA=None,useMask=None): wx.Yield() self.ShowMatchingFiles(self.params['filter']) wx.Yield() - + def AutoComputePDF(imgId): '''Computes a PDF for a PWDR data tree tree item ''' @@ -4004,7 +4001,7 @@ def AutoComputePDF(imgId): G2frame.AutointPDFnames.append(pwdr) # save names of PDF entry to be deleted later if needed G2frame.AutointPWDRnames.append(G2frame.GPXtree.GetItemText(PDFid)) - + G2frame = self.G2frame try: self.currImageList = sorted( @@ -4018,7 +4015,7 @@ def AutoComputePDF(imgId): self.PreventReEntryTimer = True imageFileList = [] # integrate the images that have already been read in, but - # have not yet been processed + # have not yet been processed oldData = {'tilt':0.,'distance':0.,'rotation':0.,'center':[0.,0.],'DetDepth':0.,'azmthOff':0.} oldMhash = 0 if 'useTA' not in dir(self): #initial definition; reuse if after Resume @@ -4093,7 +4090,7 @@ def Evaluator(dist): * a dict with interpolated parameter values, * the closest imctrl and * the closest maskfile (or None) - ''' + ''' x = np.array([float(i) for i in parms[0]]) closest = abs(x-dist).argmin() D = {'setdist':dist} @@ -4188,10 +4185,10 @@ def __init__(self,parent,parms=None,IMfileList=None,readFileList=None): btn = wx.Button(self, wx.ID_CLOSE,'Quit') btn.Bind(wx.EVT_BUTTON,self._onClose) btnsizer.Add(btn) - mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) + mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) self.SetSizer(mainSizer) self.list.FillList(self.parms) - + def ReadFiles(self,files): '''Reads a list of .imctrl files or a single .imtbl file ''' @@ -4261,7 +4258,7 @@ def ReadFiles(self,files): val = str(val) parms[i].append(val) return parms,fileList - + def ReadImageParmTable(self): '''Reads possibly edited values from the ListCtrl table and returns a list of values for each column. @@ -4278,7 +4275,7 @@ def ReadImageParmTable(self): def _onClose(self,event): 'Called when Cancel button is pressed' self.EndModal(wx.ID_CANCEL) - + def _onSave(self,event): 'Called when save button is pressed; creates a .imtbl file' fil = '' @@ -4295,7 +4292,7 @@ def _onSave(self,event): fil = dlg.GetPath() fil = os.path.splitext(fil)[0]+'.imtbl' finally: - dlg.Destroy() + dlg.Destroy() parms = self.ReadImageParmTable() print('Writing image parameter table as '+fil) fp = open(fil,'w') @@ -4318,7 +4315,7 @@ def __init__(self, parent, ID, pos=wx.DefaultPosition,size=(1000,200),style=0): listmix.TextEditMixin.__init__(self) self.Bind(wx.EVT_LEFT_DCLICK, self.OnDouble) #self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) - + def FillList(self,parms): 'Places the current parms into the table' # the use of InsertStringItem and SetStringItem are depricated in 4.0 but @@ -4393,12 +4390,12 @@ def OnDouble(self,evt): def testColumnMetadata(G2frame): '''Test the column-oriented metadata parsing, as implemented at 1-ID, by showing results when using a .par and .lbls pair. - + * Select a .par file, if more than one in selected dir. - * Select the .*lbls file, if more than one matching .par file. + * Select the .*lbls file, if more than one matching .par file. * Parse the .lbls file, showing errors if encountered; loop until errors are fixed. * Search for an image or a line in the .par file and show the results when interpreted - + See :func:`GSASIIfiles.readColMetadata` for more details. ''' if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): @@ -4407,7 +4404,7 @@ def testColumnMetadata(G2frame): 'Warning') return parFiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par')) - if not parFiles: + if not parFiles: G2G.G2MessageBox(G2frame,'No .par files found in directory {}. ' .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+ '\nThis is set by config variable Column_Metadata_directory '+ @@ -4474,7 +4471,7 @@ def testColumnMetadata(G2frame): t += "\n\nPlease edit the file and press OK (or Cancel to quit)" dlg = wx.MessageDialog(G2frame,message=t, caption="Read Error",style=wx.ICON_ERROR| wx.OK|wx.STAY_ON_TOP|wx.CANCEL) - if dlg.ShowModal() != wx.ID_OK: return + if dlg.ShowModal() != wx.ID_OK: return # request a line number, read that line dlg = G2G.SingleStringDialog(G2frame,'Read what', 'Enter a line number or an image file name (-1=last line)', @@ -4559,7 +4556,7 @@ def testColumnMetadata(G2frame): # GUI code to select phase(s) to superimpose on an image as well as # phase display options phaseOpts = {'phaseColorCount': 0} # for phase superposition plotting options -# phaseOpts['phaseColorCount'] counts number of phases that have been selected +# phaseOpts['phaseColorCount'] counts number of phases that have been selected def selectPhase(G2frame,calList,RefreshPlot): '''Display a dialog with a list of avaliable phases @@ -4643,7 +4640,7 @@ def Refresh(*args): ''' displayPhase(G2frame,phList,panel,gsizer,headers,RefreshPlot) RefreshPlot() - + def OnSelectColor(event): '''Respond to a change in color ''' @@ -4652,11 +4649,11 @@ def OnSelectColor(event): # convert wx.Colour to MPL color string phaseOpts[p]['color'] = mpl.colors.to_hex(np.array(c.Get())/255) Refresh() - + def StyleChange(*args): 'define MPL line style from line style index' for p in phaseOpts['selList']: - try: + try: phaseOpts[p]['MPLstyle'] = ltypeMPLname[phaseOpts[p]['style']] except: phaseOpts[p]['MPLstyle'] = '--' @@ -4706,8 +4703,8 @@ def StyleChange(*args): gsizer.Add(b,0,wx.ALL|wx.ALIGN_CENTER,3) gsizer.Add(b,0,wx.ALL|wx.ALIGN_CENTER,3) panel.SendSizeEvent() -#=========================================================================== - +#=========================================================================== + if __name__ == '__main__': app = wx.App() GSASIIpath.InvokeDebugOpts() @@ -4716,9 +4713,9 @@ def StyleChange(*args): text = 'this is a long string that will be scrolled' ms.Add(G2G.ScrolledStaticText(frm,label=text)) frm.SetSizer(ms) - frm.Show(True) + frm.Show(True) G2frame = frm - + # make a list of phases & calibrants phList = sorted(calFile.Calibrants.keys(),key=lambda s: s.lower()) def RefreshPlot(*args): print('plot refresh') diff --git a/GSASII/GSASIIindex.py b/GSASII/GSASIIindex.py index efa9e6248..1df61b520 100644 --- a/GSASII/GSASIIindex.py +++ b/GSASII/GSASIIindex.py @@ -9,10 +9,10 @@ import math import time import numpy as np -import GSASIIlattice as G2lat -import GSASIIpwd as G2pwd -import GSASIIspc as G2spc -import GSASIImath as G2mth +from . import GSASIIlattice as G2lat +from . import GSASIIpwd as G2pwd +from . import GSASIIspc as G2spc +from . import GSASIImath as G2mth import scipy.optimize as so # trig functions in degrees diff --git a/GSASII/GSASIIlattice.py b/GSASII/GSASIIlattice.py index 43b0cd982..dfba6abd9 100644 --- a/GSASII/GSASIIlattice.py +++ b/GSASII/GSASIIlattice.py @@ -10,9 +10,19 @@ import numpy as np import numpy.linalg as nl import scipy.special as spsp -import GSASIImath as G2mth -import GSASIIspc as G2spc -import GSASIIElem as G2elem +from . import GSASIImath as G2mth +from . import GSASIIspc as G2spc +from . import GSASIIElem as G2elem +from . import GSASIIpath as GSASIIpath +try: + if GSASIIpath.binaryPath: + import pytexture as ptx + else: + from . import pytexture as ptx + #import GSASII.pytexture as ptx +except ImportError: # ignore; will report this as an error in GSASIIplot import + pass + # trig functions in degrees sind = lambda x: np.sin(x*np.pi/180.) asind = lambda x: 180.*np.arcsin(x)/np.pi @@ -36,16 +46,16 @@ def sec2HMS(sec): """Convert time in sec to H:M:S string - + :param sec: time in seconds :return: H:M:S string (to nearest 100th second) - + """ H = int(sec//3600) M = int(sec//60-H*60) S = sec-3600*H-60*M return '%d:%2d:%.2f'%(H,M,S) - + def rotdMat(angle,axis=0): """Prepare rotation matrix for angle in degrees about axis(=0,1,2) @@ -60,9 +70,9 @@ def rotdMat(angle,axis=0): return np.array([[cosd(angle),0,-sind(angle)],[0,1,0],[sind(angle),0,cosd(angle)]]) else: return np.array([[1,0,0],[0,cosd(angle),-sind(angle)],[0,sind(angle),cosd(angle)]]) - + def rotdMat4(angle,axis=0): - """Prepare rotation matrix for angle in degrees about axis(=0,1,2) with scaling for OpenGL + """Prepare rotation matrix for angle in degrees about axis(=0,1,2) with scaling for OpenGL :param angle: angle in degrees :param axis: axis (0,1,2 = x,y,z) about which for the rotation @@ -71,7 +81,7 @@ def rotdMat4(angle,axis=0): """ Mat = rotdMat(angle,axis) return np.concatenate((np.concatenate((Mat,[[0],[0],[0]]),axis=1),[[0,0,0,1],]),axis=0) - + def fillgmat(cell): """Compute lattice metric tensor from unit cell constants @@ -85,7 +95,7 @@ def fillgmat(cell): [a*b*cosd(gam), b*b, b*c*cosd(alp)], [a*c*cosd(bet) ,b*c*cosd(alp), c*c]]) return g - + def cell2Gmat(cell): """Compute real and reciprocal lattice metric tensor from unit cell constants @@ -94,7 +104,7 @@ def cell2Gmat(cell): """ g = fillgmat(cell) - G = nl.inv(g) + G = nl.inv(g) return G,g def A2Gmat(A,inverse=True): @@ -106,8 +116,8 @@ def A2Gmat(A,inverse=True): """ G = np.array([ - [A[0], A[3]/2., A[4]/2.], - [A[3]/2.,A[1], A[5]/2.], + [A[0], A[3]/2., A[4]/2.], + [A[3]/2.,A[1], A[5]/2.], [A[4]/2.,A[5]/2., A[2]]]) if inverse: g = nl.inv(G) @@ -123,7 +133,7 @@ def Gmat2A(G): """ return [G[0][0],G[1][1],G[2][2],2.*G[0][1],2.*G[0][2],2.*G[1][2]] - + def cell2A(cell): """Obtain A = [G11,G22,G33,2*G12,2*G13,2*G23] from lattice parameters @@ -163,9 +173,9 @@ def Gmat2cell(g): return a,b,c,alp,bet,gam def invcell2Gmat(invcell): - """Compute real and reciprocal lattice metric tensor from reciprocal + """Compute real and reciprocal lattice metric tensor from reciprocal unit cell constants - + :param invcell: [a*,b*,c*,alpha*, beta*, gamma*] (degrees) :return: reciprocal (G) & real (g) metric tensors (list of two 3x3 arrays) @@ -174,8 +184,8 @@ def invcell2Gmat(invcell): g = nl.inv(G) return G,g -def cellDijFill(pfx,phfx,SGData,parmDict): - '''Returns the filled-out reciprocal cell (A) terms +def cellDijFill(pfx,phfx,SGData,parmDict): + '''Returns the filled-out reciprocal cell (A) terms from the parameter dictionaries corrected for Dij. :param str pfx: parameter prefix ("n::", where n is a phase index) @@ -215,8 +225,8 @@ def cellDijFill(pfx,phfx,SGData,parmDict): elif SGData['SGLaue'] in ['3R', '3mR']: A = [parmDict[pfx+'A0']+parmDict[phfx+'D11'],parmDict[pfx+'A0']+parmDict[phfx+'D11'], parmDict[pfx+'A0']+parmDict[phfx+'D11'], - parmDict[pfx+'A3']+parmDict[phfx+'D23'],parmDict[pfx+'A3']+parmDict[phfx+'D23'], - parmDict[pfx+'A3']+parmDict[phfx+'D23']] + parmDict[pfx+'A3']+parmDict[phfx+'D12'],parmDict[pfx+'A3']+parmDict[phfx+'D12'], + parmDict[pfx+'A3']+parmDict[phfx+'D12']] elif SGData['SGLaue'] in ['m3m','m3']: A = [parmDict[pfx+'A0']+parmDict[phfx+'D11'],parmDict[pfx+'A0']+parmDict[phfx+'D11'], parmDict[pfx+'A0']+parmDict[phfx+'D11'],0,0,0] @@ -422,14 +432,14 @@ def getCellEsd(pfx,SGData,A,covData,unique=False): return [CS[0],CS[1],CS[2],CS[5],CS[4],CS[3],sigVol] def CellDijCorr(Cell,SGData,Data,hist): - '''Returns the cell corrected for Dij values. + '''Returns the cell corrected for Dij values. :param list Cell: lattice parameters :param dict SGdata: a symmetry object :param dict Data: phase data structure; contains set of Dij values :param str hist: histogram name - :returns: cell corrected for Dij values + :returns: cell corrected for Dij values ''' A = cell2A(Cell) Dij = Data[hist]['HStrain'][0] @@ -438,11 +448,11 @@ def CellDijCorr(Cell,SGData,Data,hist): def AplusDij(A,Dij,SGData): ''' returns the A corrected by Dij - + :param list A: reciprocal metric terms A0-A5 :param array Dij: unique Dij values as stored in Hstrain :param dict SGdata: a symmetry object - + :returns list newA: A corrected by Dij ''' if SGData['SGLaue'] in ['-1',]: @@ -464,29 +474,29 @@ def AplusDij(A,Dij,SGData): newA = [A[0]+Dij[0],A[0]+Dij[0],A[0]+Dij[0],A[3]+Dij[1],A[3]+Dij[1],A[3]+Dij[1]] elif SGData['SGLaue'] in ['m3m','m3']: newA = [A[0]+Dij[0],A[0]+Dij[0],A[0]+Dij[0],0,0,0] - + return newA - + def prodMGMT(G,Mat): '''Transform metric tensor by matrix - + :param G: array metric tensor :param Mat: array transformation matrix :return: array new metric tensor - + ''' return np.inner(np.inner(Mat,G),Mat) #right # return np.inner(Mat,np.inner(Mat,G)) #right # return np.inner(np.inner(G,Mat).T,Mat) #right # return np.inner(Mat,np.inner(G,Mat).T) #right - + def TransformCell(cell,Trans): '''Transform lattice parameters by matrix - + :param cell: list a,b,c,alpha,beta,gamma,(volume) :param Trans: array transformation matrix :return: array transformed a,b,c,alpha,beta,gamma,volume - + ''' newCell = np.zeros(7) g = cell2Gmat(cell)[1] @@ -502,10 +512,10 @@ def symInner(M1,M2): '''Compute inner product of two square matrices with symbolic processing Use dot product because sympy does not define an inner product primitive - This requires that M1 & M2 be two sympy objects, as created in - GenerateCellConstraints(). + This requires that M1 & M2 be two sympy objects, as created in + GenerateCellConstraints(). - Note that this is only used to do the symbolic math needed to generate + Note that this is only used to do the symbolic math needed to generate cell relationships. It is not used normally in GSAS-II. ''' import sympy as sym @@ -521,20 +531,20 @@ def GenerateCellConstraints(): '''Generate unit cell constraints for transforming one set of A tensor values to another using symbolic math (requires the sympy package) - Note that this is only used to do the symbolic math needed to generate + Note that this is only used to do the symbolic math needed to generate cell relationships. It is not used normally in GSAS-II. ''' import sympy as sym # define A tensor for starting cell - A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5') + A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5') G = sym.Matrix([ [A0, A3/2., A4/2.], [A3/2., A1, A5/2.], [A4/2., A5/2., A2 ]] ) # define transformation matrix T00, T10, T20, T01, T11, T21, T02, T12, T22 = sym.symbols( - 'T00, T10, T20, T01, T11, T21, T02, T12, T22') + 'T00, T10, T20, T01, T11, T21, T02, T12, T22') Tr = sym.Matrix([ [T00, T10, T20], [T01, T11, T21], [T02, T12, T22],]) - # apply transform + # apply transform newG = symInner(symInner(Tr,G),Tr).expand() # define A tensor for converted cell return [newG[0,0],newG[1,1],newG[2,2],2.*newG[0,1],2.*newG[0,2],2.*newG[1,2]] @@ -542,7 +552,7 @@ def GenerateCellConstraints(): def subVals(expr,A,T): '''Evaluate the symbolic expressions by substituting for A0-A5 & Tij - This can be used on the cell relationships created in + This can be used on the cell relationships created in :func:`GenerateCellConstraints` like this:: Trans = np.array([ [2/3, 4/3, 1/3], [-1, 0, 0], [-1/3, -2/3, 1/3] ]) @@ -550,20 +560,20 @@ def subVals(expr,A,T): print([subVals(i,Aold,T) for i in GenerateCellConstraints()]) :param list expr: a list of sympy expressions. - :param list A: This is the A* tensor as defined above. + :param list A: This is the A* tensor as defined above. :param np.array T: a 3x3 transformation matrix where, Trans = np.array([ [2/3, 4/3, 1/3], [-1, 0, 0], [-1/3, -2/3, 1/3] ]) (for a' = 2/3a + 4/3b + 1/3c; b' = -a; c' = -1/3, -2/3, 1/3) then T = np.linalg.inv(Trans).T - Note that this is only used to do the symbolic math needed to generate + Note that this is only used to do the symbolic math needed to generate cell relationships. It is not used normally in GSAS-II. ''' import sympy as sym - A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5') + A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5') # transformation matrix T00, T10, T20, T01, T11, T21, T02, T12, T22 = sym.symbols( - 'T00, T10, T20, T01, T11, T21, T02, T12, T22') + 'T00, T10, T20, T01, T11, T21, T02, T12, T22') vals = dict(zip([T00, T10, T20, T01, T11, T21, T02, T12, T22],T.ravel())) vals.update(dict(zip([A0, A1, A2, A3, A4, A5],A))) return float(expr.subs(vals)) @@ -586,13 +596,13 @@ def subVals(expr,A,T): def fmtCellConstraints(cellConstr): '''Format the cell relationships created in :func:`GenerateCellConstraints` - in a format that can be used to generate constraints. + in a format that can be used to generate constraints. Use:: cXforms = G2lat.fmtCellConstraints(G2lat.GenerateCellConstraints()) - Note that this is only used to do the symbolic math needed to generate + Note that this is only used to do the symbolic math needed to generate cell relationships. It is not used normally in GSAS-II. ''' import re @@ -649,7 +659,7 @@ def fmtCellConstraints(cellConstr): '1.0*A4*(T[1,0]*T[2,2] + T[2,0]*T[1,2])', '1.0*A5*(T[1,1]*T[2,2] + T[2,1]*T[1,2])']} -'''cellXformRelations provide the constraints on newA[i] values for a new +'''cellXformRelations provide the constraints on newA[i] values for a new cell generated from oldA[i] values. ''' # cellXformRelations values were generated using:: @@ -657,22 +667,22 @@ def fmtCellConstraints(cellConstr): # cellXformRelations = fmtCellConstraints(GenerateCellConstraints()) def GenCellConstraints(Trans,origPhase,newPhase,origA,oSGLaue,nSGLaue,debug=False): - '''Generate the constraints between two unit cells constants for a phase transformed - by matrix Trans. + '''Generate the constraints between two unit cells constants for a phase transformed + by matrix Trans. :param np.array Trans: a 3x3 direct cell transformation matrix where, Trans = np.array([ [2/3, 4/3, 1/3], [-1, 0, 0], [-1/3, -2/3, 1/3] ]) (for a' = 2/3a + 4/3b + 1/3c; b' = -a; c' = -1/3, -2/3, 1/3) :param int origPhase: phase id (pId) for original phase - :param int newPhase: phase id for the transformed phase to be constrained from + :param int newPhase: phase id for the transformed phase to be constrained from original phase :param list origA: reciprocal cell ("A*") tensor (used for debug only) :param dict oSGLaue: space group info for original phase :param dict nSGLaue: space group info for transformed phase - :param bool debug: If true, the constraint input is used to compute and print A* + :param bool debug: If true, the constraint input is used to compute and print A* and from that the direct cell for the transformed phase. ''' - import GSASIIobj as G2obj + from . import GSASIIobj as G2obj Anew = [] constrList = [] uniqueAnew = cellUnique(nSGLaue) @@ -694,14 +704,14 @@ def GenCellConstraints(Trans,origPhase,newPhase,origA,oSGLaue,nSGLaue,debug=Fals constr.append([const,G2obj.G2VarObj('{}::{}'.format(origPhase,aTerm))]) if i in uniqueAnew: constrList.append(constr + [0.0,None,'c']) - if debug: Anew.append(np.dot(origA,mult)) + if debug: Anew.append(np.dot(origA,mult)) if debug: print('xformed A* ',Anew) print('xformed cell',A2cell(Anew)) return constrList def cellUnique(SGData): - '''Returns the indices for the unique A tensor terms + '''Returns the indices for the unique A tensor terms based on the Laue class. Any terms that are determined from others or are zero are not included. @@ -728,11 +738,11 @@ def cellUnique(SGData): elif SGData['SGLaue'] in ['m3m','m3']: return [0,] -def cellZeros(SGData): +def cellZeros(SGData): '''Returns a list with the A terms required to be zero based on Laue symmetry :param dict SGdata: a symmetry object - :returns: A list of six terms where the values are True if the + :returns: A list of six terms where the values are True if the A term must be zero, False otherwise. ''' if SGData['SGLaue'] in ['-1',]: @@ -757,7 +767,7 @@ def cellZeros(SGData): def TransformXYZ(XYZ,Trans,Vec): return np.inner(XYZ,Trans)+Vec - + def TransformU6(U6,Trans): Uij = np.inner(Trans,np.inner(U6toUij(U6),Trans).T)/nl.det(Trans) return UijtoU6(Uij) @@ -778,25 +788,25 @@ def ExpandCell(Atoms,atCodes,cx,Trans): moreCodes = [code+'+%d,%d,%d'%(unit[0],unit[1],unit[2]) for code in codes] Codes += moreCodes return newAtoms,Codes - + def TransformPhase(oldPhase,newPhase,Trans,Uvec,Vvec,ifMag,Force=True): '''Transform atoms from oldPhase to newPhase M' is inv(M) does X' = M(X-U)+V transformation for coordinates and U' = MUM/det(M) for anisotropic thermal parameters - + :param oldPhase: dict G2 phase info for old phase :param newPhase: dict G2 phase info for new phase; with new cell & space group atoms are from oldPhase & will be transformed :param Trans: lattice transformation matrix M :param Uvec: array parent coordinates transformation vector U :param Vvec: array child coordinate transformation vector V - :param ifMag: bool True if convert to magnetic phase; + :param ifMag: bool True if convert to magnetic phase; if True all nonmagnetic atoms will be removed - + :return: newPhase dict modified G2 phase info :return: atCodes list atom transformation codes - + ''' cx,ct,cs,cia = oldPhase['General']['AtomPtrs'] cm = 0 @@ -846,22 +856,22 @@ def TransformPhase(oldPhase,newPhase,Trans,Uvec,Vvec,ifMag,Force=True): newPhase['Drawing'] = [] newPhase['ranId'] = ran.randint(0,sys.maxsize) return newPhase,atCodes - + def FindNonstandard(controls,Phase): ''' Find nonstandard setting of magnetic cell that aligns with parent nuclear cell - + :param controls: list unit cell indexing controls :param Phase: dict new magnetic phase data (NB:not G2 phase construction); modified here :return: None - + ''' abc = np.eye(3) cba = np.rot90(np.eye(3)) cba[1,1] *= -1 #makes c-ba Mats = {'abc':abc,'cab':np.roll(abc,2,1),'bca':np.roll(abc,1,1), 'acb':np.roll(cba,1,1),'bac':np.roll(cba,2,1),'cba':cba} #ok - BNS = {'A':{'abc':'A','cab':'C','bca':'B','acb':'A','bac':'B','cba':'C'}, + BNS = {'A':{'abc':'A','cab':'C','bca':'B','acb':'A','bac':'B','cba':'C'}, 'B':{'abc':'B','cab':'A','bca':'C','acb':'C','bac':'A','cba':'B'}, 'C':{'abc':'C','cab':'B','bca':'A','acb':'B','bac':'C','cba':'A'}, 'a':{'abc':'a','cab':'c','bca':'b','acb':'a','bac':'b','cba':'c'}, #Ok @@ -885,7 +895,7 @@ def FindNonstandard(controls,Phase): NTrans = np.inner(Mats[lattSym].T,Trans.T) #ok if len(spn): spn[1:4] = np.inner(np.abs(nl.inv(Mats[lattSym])),spn[1:4]) #ok SGsym = G2spc.getlattSym(nl.inv(Mats[lattSym])) - + if lattSym != 'abc': NSG = G2spc.altSettingOrtho[SpGrp][SGsym].replace("'",'').split(' ') if ' '.join(NSG) in ['P 2 21 2',]: @@ -1009,11 +1019,11 @@ def FillUnitCell(Phase,Force=True): atom[cm:cm+3] = np.inner(np.inner(mom,M),Amat)*nl.det(M)*SpnFlp[opNum-1] atCodes.append('%d:%s'%(iat,str(item[1]))) atomData.append(atom[:cia+9]) #not SS stuff - + return atomData,atCodes - + def GetUnique(Phase,atCodes): - + def noDuplicate(xyzA,XYZ): if True in [np.allclose(xyzA%1.,xyzB%1.,atol=0.0002) for xyzB in XYZ]: return False @@ -1035,7 +1045,7 @@ def noDuplicate(xyzA,XYZ): xyz = XYZ[ind] for jnd in range(Ind): if Atoms[ind][ct-1] == Atoms[jnd][ct-1]: - if ind != jnd and Indx[jnd]: + if ind != jnd and Indx[jnd]: Equiv = G2spc.GenAtom(XYZ[jnd],SGData,Move=True) xyzs = np.array([equiv[0] for equiv in Equiv]) Indx[jnd] = noDuplicate(xyz,xyzs) @@ -1045,7 +1055,7 @@ def noDuplicate(xyzA,XYZ): newAtoms.append(Atoms[ind]) newAtCodes.append(atCodes[ind]) return newAtoms,newAtCodes - + def calc_rVsq(A): """Compute the square of the reciprocal lattice volume (1/V**2) from A' @@ -1055,12 +1065,12 @@ def calc_rVsq(A): if rVsq < 0: return 1 return rVsq - + def calc_rV(A): """Compute the reciprocal lattice volume (V*) from A """ return np.sqrt(calc_rVsq(A)) - + def calc_V(A): """Compute the real lattice volume (V) from A """ @@ -1072,7 +1082,7 @@ def A2invcell(A): """ G,g = A2Gmat(A) return Gmat2cell(G) - + def Gmat2AB(G): """Computes orthogonalization matrix from reciprocal metric tensor G @@ -1096,17 +1106,17 @@ def Gmat2AB(G): # A[2][2] = 1./cellstar[2] # 1/c* # B = nl.inv(A) # return A,B - + def cell2AB(cell,alt=False): """Computes orthogonalization matrix from unit cell constants :param tuple cell: a,b,c, alpha, beta, gamma (degrees) :returns: tuple of two 3x3 numpy arrays (A,B) - A for crystal to Cartesian transformations A*x = np.inner(A,x) = X + A for crystal to Cartesian transformations A*x = np.inner(A,x) = X B (= inverse of A) for Cartesian to crystal transformation B*X = np.inner(B,X) = x both rounded to 12 places (typically zero terms = +/-10e-6 otherwise) """ - G,g = cell2Gmat(cell) + G,g = cell2Gmat(cell) cellstar = Gmat2cell(G) A = np.zeros(shape=(3,3)) if alt: #as used in RMCProfile!! @@ -1129,14 +1139,14 @@ def cell2AB(cell,alt=False): A = np.around(A,12) B = nl.inv(A) return A,B - + def HKL2SpAng(H,cell,SGData): """Computes spherical coords for hkls; view along 001 :param array H: arrays of hkl :param tuple cell: a,b,c, alpha, beta, gamma (degrees) :param dict SGData: space group dictionary - :returns: arrays of r,phi,psi (radius,inclination,azimuth) about 001 + :returns: arrays of r,phi,psi (radius,inclination,azimuth) about 001 """ A,B = cell2AB(cell) xH = np.inner(B.T,H) @@ -1145,7 +1155,7 @@ def HKL2SpAng(H,cell,SGData): psi = atan2d(xH[1],xH[0]) phi = np.where(phi>90.,180.-phi,phi) return r,phi,psi - + def U6toUij(U6): """Fill matrix (Uij) from U6 = [U11,U22,U33,U12,U13,U23] NB: there is a non numpy version in GSASIIspc: U2Uij @@ -1155,13 +1165,13 @@ def U6toUij(U6): Uij - numpy [3][3] array of uij """ U = np.array([ - [U6[0], U6[3], U6[4]], - [U6[3], U6[1], U6[5]], + [U6[0], U6[3], U6[4]], + [U6[3], U6[1], U6[5]], [U6[4], U6[5], U6[2]]]) return U def UijtoU6(U): - """Fill vector [U11,U22,U33,U12,U13,U23] from Uij + """Fill vector [U11,U22,U33,U12,U13,U23] from Uij NB: there is a non numpy version in GSASIIspc: Uij2U """ U6 = np.array([U[0][0],U[1][1],U[2][2],U[0][1],U[0][2],U[1][2]]) @@ -1170,25 +1180,25 @@ def UijtoU6(U): def betaij2Uij(betaij,G): """ Convert beta-ij to Uij tensors - + :param beta-ij - numpy array [beta-ij] :param G: reciprocal metric tensor :returns: Uij: numpy array [Uij] """ ast = np.sqrt(np.diag(G)) #a*, b*, c* - Mast = np.multiply.outer(ast,ast) + Mast = np.multiply.outer(ast,ast) return R2pisq*UijtoU6(U6toUij(betaij)/Mast) - + def Uij2betaij(Uij,G): """ Convert Uij to beta-ij tensors -- stub for eventual completion - + :param Uij: numpy array [Uij] :param G: reciprocal metric tensor :returns: beta-ij - numpy array [beta-ij] """ pass - + def cell2GS(cell): ''' returns Uij to betaij conversion matrix''' G,g = cell2Gmat(cell) @@ -1196,8 +1206,8 @@ def cell2GS(cell): GS[0][1] = GS[1][0] = math.sqrt(GS[0][0]*GS[1][1]) GS[0][2] = GS[2][0] = math.sqrt(GS[0][0]*GS[2][2]) GS[1][2] = GS[2][1] = math.sqrt(GS[1][1]*GS[2][2]) - return GS - + return GS + def Uij2Ueqv(Uij,GS,Amat): ''' returns 1/3 trace of diagonalized U matrix :param Uij: numpy array [Uij] @@ -1210,9 +1220,9 @@ def Uij2Ueqv(Uij,GS,Amat): U = np.inner(Amat,np.inner(U,Amat).T) E,R = nl.eigh(U) return np.sum(E)/3.,E[0] < 0. - + def CosAngle(U,V,G): - """ calculate cos of angle between U & V in generalized coordinates + """ calculate cos of angle between U & V in generalized coordinates defined by metric tensor G :param U: 3-vectors assume numpy arrays, can be multiple reflections as (N,3) array @@ -1227,7 +1237,7 @@ def CosAngle(U,V,G): return cosP def CosSinAngle(U,V,G): - """ calculate sin & cos of angle between U & V in generalized coordinates + """ calculate sin & cos of angle between U & V in generalized coordinates defined by metric tensor G :param U: 3-vectors assume numpy arrays @@ -1241,18 +1251,18 @@ def CosSinAngle(U,V,G): cosP = np.inner(u,np.inner(G,v)) sinP = np.sqrt(max(0.0,1.0-cosP**2)) return cosP,sinP - + def criticalEllipse(prob): """ Calculate critical values for probability ellipsoids from probability """ if not ( 0.01 <= prob < 1.0): - return 1.54 + return 1.54 coeff = np.array([6.44988E-09,4.16479E-07,1.11172E-05,1.58767E-04,0.00130554, 0.00604091,0.0114921,-0.040301,-0.6337203,1.311582]) llpr = math.log(-math.log(prob)) return np.polyval(coeff,llpr) - + def CellBlock(nCells): """ Generate block of unit cells n*n*n on a side; [0,0,0] centered, n = 2*nCells+1 @@ -1270,7 +1280,7 @@ def CellBlock(nCells): return cellArray else: return [0,0,0] - + def CellAbsorption(ElList,Volume): '''Compute unit cell absorption @@ -1283,11 +1293,11 @@ def CellAbsorption(ElList,Volume): for El in ElList: muT += ElList[El]['mu']*ElList[El]['FormulaNo'] return muT/Volume - + #Permutations and Combinations # Four routines: combinations,uniqueCombinations, selections & permutations #These taken from Python Cookbook, 2nd Edition. 19.15 p724-726 -# +# def _combinators(_handle, items, n): """ factored-out common structure of all following combinators """ if n==0: @@ -1317,9 +1327,9 @@ def permutations(items): return combinations(items, len(items)) #reflection generation routines -#for these: H = [h,k,l]; A is as used in calc_rDsq; G - inv metric tensor, g - metric tensor; +#for these: H = [h,k,l]; A is as used in calc_rDsq; G - inv metric tensor, g - metric tensor; # cell - a,b,c,alp,bet,gam in A & deg - + def Pos2dsp(Inst,pos): ''' convert powder pattern position (2-theta or TOF, musec) to d-spacing is currently only approximate for EDX data; accurate for others. @@ -1331,12 +1341,12 @@ def Pos2dsp(Inst,pos): else: #'PKS', 'C' or 'B' wave = G2mth.getWave(Inst) return wave/(2.0*sind((pos-Inst.get('Zero',[0,0])[1])/2.0)) - + def TOF2dsp(Inst,Pos): ''' convert powder pattern TOF, musec to d-spacing by successive approximation Pos can be numpy array ''' - def func(d,pos,Inst): + def func(d,pos,Inst): return (pos-Inst['difA'][1]*d**2-Inst['Zero'][1]-Inst['difB'][1]/d)/Inst['difC'][1] dsp0 = Pos/Inst['difC'][1] N = 0 @@ -1348,7 +1358,7 @@ def func(d,pos,Inst): N += 1 if N > 10: return dsp - + def Dsp2pos(Inst,dsp): ''' convert d-spacing to powder pattern position (2-theta or TOF, musec) ''' @@ -1359,9 +1369,9 @@ def Dsp2pos(Inst,dsp): else: #'A', 'B', 'C' or 'PKS' wave = G2mth.getWave(Inst) val = min(0.995,wave/(2.*dsp)) #set max at 168deg - pos = 2.0*asind(val)+Inst.get('Zero',[0,0])[1] + pos = 2.0*asind(val)+Inst.get('Zero',[0,0])[1] return pos - + def getPeakPos(dataType,parmdict,dsp): ''' convert d-spacing to powder pattern position (2-theta, E or TOF, musec) ''' @@ -1372,7 +1382,7 @@ def getPeakPos(dataType,parmdict,dsp): else: #'A', 'B' or 'C' pos = 2.0*asind(parmdict['Lam']/(2.*dsp))+parmdict['Zero'] return pos - + def calc_rDsq(H,A): 'calc 1/d^2 from individual hkl and A-terms' B = np.array([H[0]**2,H[1]**2,H[2]**2,H[0]*H[1],H[0]*H[2],H[1]*H[2]]) @@ -1384,36 +1394,36 @@ def calc_rDsqA(H,A): # return np.sum(np.array(A)[:,nxs]*B,axis=0) h,k,l = H return A[0]*h*h+A[1]*k*k+A[2]*l*l+A[3]*h*k+A[4]*h*l+A[5]*k*l #quicker - + def calc_rDsq2(H,G): 'computes 1/d^2 from one hkl & reciprocal metric tensor G' return np.inner(H,np.inner(G,H)) - + def calc_rDsqSS(H,A,vec): 'computes 1/d^2 from one hklm, reciprocal metric tensor A & k-vector' rdsq = calc_rDsq(H[:3]+(H[3]*vec).T,A) return rdsq - + def calc_rDsqZ(H,A,Z,tth,lam): 'computes 1/d^2 from hkl array & reciprocal metric tensor A with CW ZERO shift' rdsq = calc_rDsqA(H,A)+Z*sind(tth)*2.0*rpd/lam**2 return rdsq - + def calc_rDsqZSS(H,A,vec,Z,tth,lam): 'computes 1/d^2 from hklm array, reciprocal metric tensor A & k-vector with CW Z shift' rdsq = calc_rDsqA(H[:3]+(H[3][:,np.newaxis]*vec).T,A)+Z*sind(tth)*2.0*rpd/lam**2 return rdsq - + def calc_rDsqT(H,A,Z,tof,difC): 'computes 1/d^2 from hkl array & reciprocal metric tensor A with TOF ZERO shift' rdsq = calc_rDsqA(H,A)+Z/difC return rdsq - + def calc_rDsqTSS(H,A,vec,Z,tof,difC): 'computes 1/d^2 from hklm array, reciprocal metric tensor A & k-vector with TOF Z shift' rdsq = calc_rDsqA(H[:3]+(H[3][:,np.newaxis]*vec).T,A)+Z/difC return rdsq - + def PlaneIntercepts(Amat,H,phase,stack): ''' find unit cell intercepts for a stack of hkl planes ''' @@ -1430,7 +1440,7 @@ def PlaneIntercepts(Amat,H,phase,stack): for j in [0,1,2,3]: hx = [0,0,0] intcpt = ((phase)/360.+step-H[h]*Ux[j,0]-H[k]*Ux[j,1])/H[l] - if 0. <= intcpt <= 1.: + if 0. <= intcpt <= 1.: hx[h] = Ux[j,0] hx[k] = Ux[j,1] hx[l] = intcpt @@ -1449,7 +1459,7 @@ def PlaneIntercepts(Amat,H,phase,stack): HX = HX[np.argsort(A)] Stack.append(HX) return Stack - + def MaxIndex(dmin,A): 'needs doc string' Hmax = [0,0,0] @@ -1460,7 +1470,7 @@ def MaxIndex(dmin,A): for i in range(3): Hmax[i] = int(np.round(cell[i]/dmin)) return Hmax - + def transposeHKLF(transMat,Super,refList): ''' Apply transformation matrix to hkl(m) param: transmat: 3x3 or 4x4 array @@ -1477,7 +1487,7 @@ def transposeHKLF(transMat,Super,refList): if not np.allclose(newH,H[:3+Super],atol=0.01): badRefs.append(newH) return newRefs,badRefs - + def sortHKLd(HKLd,ifreverse,ifdup,ifSS=False): '''sort reflection list on d-spacing; can sort in either order @@ -1494,18 +1504,18 @@ def sortHKLd(HKLd,ifreverse,ifdup,ifSS=False): if ifdup: T.append((H[N],i)) else: - T.append(H[N]) + T.append(H[N]) D = dict(zip(T,HKLd)) T.sort() if ifreverse: T.reverse() X = [] okey = '' - for key in T: + for key in T: if key != okey: X.append(D[key]) #remove duplicate d-spacings okey = key return X - + def SwapIndx(Axis,H): 'needs doc string' if Axis in [1,-1]: @@ -1514,7 +1524,7 @@ def SwapIndx(Axis,H): return [H[1],H[2],H[0]] else: return [H[2],H[0],H[1]] - + def SwapItems(Alist,pos1,pos2): 'exchange 2 items in a list' try: @@ -1523,7 +1533,7 @@ def SwapItems(Alist,pos1,pos2): except IndexError: pass return Alist - + def Rh2Hx(Rh): 'needs doc string' Hx = [0,0,0] @@ -1531,7 +1541,7 @@ def Rh2Hx(Rh): Hx[1] = Rh[1]-Rh[2] Hx[2] = np.sum(Rh) return Hx - + def Hx2Rh(Hx): 'needs doc string' Rh = [0,0,0] @@ -1546,7 +1556,7 @@ def Hx2Rh(Hx): for i in range(3): Rh[i] = -Rh[i] return Rh - + def CentCheck(Cent,H): 'checks individual hkl for centering extinction; returns True for allowed, False otherwise - slow' h,k,l = H @@ -1564,7 +1574,7 @@ def CentCheck(Cent,H): return False else: return True - + def newCentCheck(Cent,H): 'checks np.array of HKls for centering extinction; returns allowed HKLs - fast' K = H.T @@ -1586,7 +1596,7 @@ def newCentCheck(Cent,H): return H[np.where((-K[0]+K[1]+K[2])%3 == 0)] else: return H - + def RBsymCheck(Atoms,ct,cx,cs,AtLookUp,Amat,RBObjIds,SGData): """ Checks members of a rigid body to see if one is a symmetry equivalent of another. If so the atom site frac is set to zero. @@ -1599,7 +1609,7 @@ def RBsymCheck(Atoms,ct,cx,cs,AtLookUp,Amat,RBObjIds,SGData): :param list RBObjIds: atom Id belonging to rigid body being tested :param dict SGData: GSAS-II space group info. :returns: Atoms with modified atom frac entries - + """ for i,Id in enumerate(RBObjIds): XYZo = np.array(Atoms[AtLookUp[Id]][cx:cx+3])%1. @@ -1613,16 +1623,16 @@ def RBsymCheck(Atoms,ct,cx,cs,AtLookUp,Amat,RBObjIds,SGData): Atoms[AtLookUp[Jd]][cx+3] = 0.0 Sytsym,Mult = G2spc.SytSym(Atoms[AtLookUp[Id]][cx:cx+3],SGData)[:2] Atoms[AtLookUp[Id]][cs] = Sytsym - Atoms[AtLookUp[Id]][cs+1] = Mult + Atoms[AtLookUp[Id]][cs+1] = Mult return Atoms def GetBraviasNum(center,system): """Determine the Bravais lattice number, as used in GenHBravais - + :param center: one of: 'P', 'C', 'I', 'F', 'R' (see SGLatt from GSASIIspc.SpcGroup) :param system: one of 'cubic', 'hexagonal', 'tetragonal', 'orthorhombic', 'trigonal' (for R) 'monoclinic', 'triclinic' (see SGSys from GSASIIspc.SpcGroup) - :return: a number between 0 and 13 + :return: a number between 0 and 13 or throws a ValueError exception if the combination of center, system is not found (i.e. non-standard) """ @@ -1668,7 +1678,7 @@ def _GenHBravais_cctbx(dmin, Bravais, A, sg_type, uctbx_unit_cell, miller_index_ '''Alternate form of :func:`GenHBravais` that uses CCTBX internals ''' g_inv = np.array([[A[0], A[3]/2, A[4]/2], - [A[3]/2, A[1], A[5]/2], + [A[3]/2, A[1], A[5]/2], [A[4]/2, A[5]/2, A[2]]]) g = np.linalg.inv(g_inv) g_elems = (g[0][0], g[1][1], g[2][2], g[0][1], g[0][2], g[1][2]) @@ -1689,10 +1699,10 @@ def _GenHBravais_cctbx(dmin, Bravais, A, sg_type, uctbx_unit_cell, miller_index_ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): """Generate the positionally unique powder diffraction reflections - + :param dmin: minimum d-spacing in A :param Bravais: lattice type (see GetBraviasNum). Bravais is one of: - + * 0 F cubic * 1 I cubic * 2 P cubic @@ -1711,9 +1721,9 @@ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): * 15 C monoclinic * 16 P monoclinic * 17 P triclinic - + :param A: reciprocal metric tensor elements as [G11,G22,G33,2*G12,2*G13,2*G23] - :param dict cctbx_args: items defined in CCTBX: + :param dict cctbx_args: items defined in CCTBX: * 'sg_type': value from cctbx.sgtbx.space_group_type(symmorphic_sgs[ibrav]) * 'uctbx_unit_cell': pointer to :meth:`cctbx.uctbx.unit_cell` @@ -1723,7 +1733,7 @@ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): * Currently applies only to Triclinic, Monoclinic & Orthorhombic; otherwise output is 2D list. :returns: HKL unique d list of [h,k,l,d,-1] sorted with largest d first - + """ if cctbx_args: return _GenHBravais_cctbx(dmin, Bravais, A, @@ -1745,7 +1755,7 @@ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): Hmax = MaxIndex(dmin,A) HKL = [] #generate HKL block first - no extinction conditions; obey Laue symmetry - + if Bravais == 17: #triclinic H0 = np.reshape(np.mgrid[0:1,1:Hmax[1]+1,0:1],(3,-1)).T #0k0, k>0 @@ -1753,16 +1763,16 @@ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): H2 = np.reshape(np.mgrid[0:1,0:Hmax[1]+1,1:Hmax[2]+1],(3,-1)).T #0kl, k>=0,l>0 H = np.reshape(np.mgrid[1:Hmax[0]+1,-Hmax[1]:Hmax[1]+1,-Hmax[2]:Hmax[2]+1],(3,-1)).T H = np.vstack((H0,H1,H2,H)) - + elif Bravais in [13,14,15,16]: #monoclinic - b unique H0 = np.reshape(np.mgrid[0:1,1:Hmax[1]+1,0:1],(3,-1)).T #0k0, k>0 H1 = np.reshape(np.mgrid[0:1,0:Hmax[1]+1,1:Hmax[2]+1],(3,-1)).T #0kl, k>=0,l>0 H = np.reshape(np.mgrid[1:Hmax[0]+1,0:Hmax[1]+1,-Hmax[2]:Hmax[2]+1],(3,-1)).T H = np.vstack((H0,H1,H)) - + elif Bravais in [7,8,9,10,11,12]: #orthorhombic - + H0 = np.reshape(np.mgrid[0:1,1:Hmax[1]+1,0:1],(3,-1)).T #0k0, k>0 H1 = np.reshape(np.mgrid[0:1,0:Hmax[1]+1,1:Hmax[2]+1],(3,-1)).T #0kl, k>=0,l>0 H = np.reshape(np.mgrid[1:Hmax[0]+1,0:Hmax[1]+1,0:Hmax[2]+1],(3,-1)).T @@ -1803,7 +1813,7 @@ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): continue H.append([h,k,l]) H = np.array(H) - + #then enforce centering rules & d >= dmin limit; make array or list as needed H = newCentCheck(Cent,H) @@ -1820,7 +1830,7 @@ def GenHBravais(dmin, Bravais, A, cctbx_args=None,ifList=False): return np.flipud(HKL[Indx]) else: return HKL - + def getHKLmax(dmin,SGData,A): 'finds maximum allowed hkl for given A within dmin' SGLaue = SGData['SGLaue'] @@ -1835,22 +1845,22 @@ def getHKLmax(dmin,SGData,A): else: # all others Hmax = MaxIndex(dmin,A) return Hmax - + def GenHLaue(dmin,SGData,A): """Generate the crystallographically unique powder diffraction reflections for a lattice and Bravais type - + :param dmin: minimum d-spacing :param SGData: space group dictionary with at least - + * 'SGLaue': Laue group symbol: one of '-1','2/m','mmm','4/m','6/m','4/mmm','6/mmm', '3m1', '31m', '3', '3R', '3mR', 'm3', 'm3m' * 'SGLatt': lattice centering: one of 'P','A','B','C','I','F' * 'SGUniq': code for unique monoclinic axis one of 'a','b','c' (only if 'SGLaue' is '2/m') otherwise an empty string - + :param A: reciprocal metric tensor elements as [G11,G22,G33,2*G12,2*G13,2*G23] - :return: HKL = list of [h,k,l,d] sorted with largest d first and is unique + :return: HKL = list of [h,k,l,d] sorted with largest d first and is unique part of reciprocal space ignoring anomalous dispersion - + """ import math SGLaue = SGData['SGLaue'] @@ -1858,7 +1868,7 @@ def GenHLaue(dmin,SGData,A): SGUniq = SGData['SGUniq'] #finds maximum allowed hkl for given A within dmin Hmax = getHKLmax(dmin,SGData,A) - + dminsq = 1./(dmin**2) HKL = [] if SGLaue == '-1': #triclinic @@ -1949,25 +1959,25 @@ def GenHLaue(dmin,SGData,A): if 0 < rdsq <= dminsq: HKL.append([h,k,l,1./math.sqrt(rdsq)]) return sortHKLd(HKL,True,True) - -def GenPfHKLs(nMax,SGData,A): - """Generate the unique pole figure reflections for a lattice and Bravais type. + +def GenPfHKLs(nMax,SGData,A): + """Generate the unique pole figure reflections for a lattice and Bravais type. Min d-spacing=1.0A & no more than nMax returned - + :param nMax: maximum number of hkls returned :param SGData: space group dictionary with at least - + * 'SGLaue': Laue group symbol: one of '-1','2/m','mmm','4/m','6/m','4/mmm','6/mmm', '3m1', '31m', '3', '3R', '3mR', 'm3', 'm3m' * 'SGLatt': lattice centering: one of 'P','A','B','C','I','F' * 'SGUniq': code for unique monoclinic axis one of 'a','b','c' (only if 'SGLaue' is '2/m') otherwise an empty string - + :param A: reciprocal metric tensor elements as [G11,G22,G33,2*G12,2*G13,2*G23] :return: HKL = list of 'h k l' strings sorted with largest d first; no duplicate zones - + """ HKL = np.array(GenHLaue(1.0,SGData,A)).T[:3].T #strip d-spacings N = min(nMax,len(HKL)) - return ['%d %d %d'%(h[0],h[1],h[2]) for h in HKL[:N]] + return ['%d %d %d'%(h[0],h[1],h[2]) for h in HKL[:N]] def GenSSHLaue(dmin,SGData,SSGData,Vec,maxH,A): 'needs a doc string' @@ -1978,7 +1988,7 @@ def GenSSHLaue(dmin,SGData,SSGData,Vec,maxH,A): vec = np.array(Vec) vstar = np.sqrt(calc_rDsq(vec,A)) #find extra needed for -n SS reflections dvec = 1./(maxH*vstar+1./dmin) - HKL = GenHLaue(dvec,SGData,A) + HKL = GenHLaue(dvec,SGData,A) SSdH = [vec*h for h in range(-maxH,maxH+1)] SSdH = dict(zip(range(-maxH,maxH+1),SSdH)) for h,k,l,d in HKL: @@ -1993,15 +2003,15 @@ def GenSSHLaue(dmin,SGData,SSGData,Vec,maxH,A): if d >= dmin: HKLM = np.array([h,k,l,dH]) if (G2spc.checkSSLaue([h,k,l,dH],SGData,SSGData) and G2spc.checkSSextc(HKLM,SSGData)) or ifMag: - HKLs.append([h,k,l,dH,d]) + HKLs.append([h,k,l,dH,d]) return HKLs - + def LaueUnique2(SGData,refList): ''' Impose Laue symmetry on hkl - + :param SGData: space group data from 'P '+Laue :param HKLF: np.array([[h,k,l,...]]) reflection set to be converted - + :return: HKLF new reflection array with imposed Laue symmetry ''' for ref in refList: @@ -2010,28 +2020,28 @@ def LaueUnique2(SGData,refList): Uniq = G2mth.sortArray(G2mth.sortArray(G2mth.sortArray(Uniq,2),1),0) ref[:3] = Uniq[-1] return refList - + def LaueUnique(Laue,HKLF): ''' Impose Laue symmetry on hkl - + :param str Laue: Laue symbol, as below - + centrosymmetric Laue groups:: - + ['-1','2/m','112/m','2/m11','mmm','-42m','-4m2','4/mmm','-3','-3m', '-31m','-3m1','6/m','6/mmm','m3','m3m'] - + noncentrosymmetric Laue groups:: - + ['1','2','211','112','m','m11','11m','222','mm2','m2m','2mm', '4','-4','422','4mm','3','312','321','3m','31m','3m1','6','-6', '622','6mm','-62m','-6m2','23','432','-43m'] - + :param HKLF: np.array([[h,k,l,...]]) reflection set to be converted - + :returns: HKLF new reflection array with imposed Laue symmetry ''' - + HKLFT = HKLF.T mat41 = np.array([[0,1,0],[-1,0,0],[0,0,1]]) #hkl -> k,-h,l mat43 = np.array([[0,-1,0],[1,0,0],[0,0,1]]) #hkl -> -k,h,l @@ -2058,13 +2068,13 @@ def LaueUnique(Laue,HKLF): HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[2]<0),HKLFT[:3]*np.array([-1,-1,-1])[:,nxs],HKLFT[:3]) #monoclinic #noncentrosymmetric - all ok - elif Laue == '2': + elif Laue == '2': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[2]<0),HKLFT[:3]*np.array([-1,1,-1])[:,nxs],HKLFT[:3]) elif Laue == '1 1 2': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[1]<0),HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) - elif Laue == '2 1 1': + elif Laue == '2 1 1': HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[1]==0)&(HKLFT[2]<0),HKLFT[:3]*np.array([1,-1,-1])[:,nxs],HKLFT[:3]) elif Laue == 'm': @@ -2074,7 +2084,7 @@ def LaueUnique(Laue,HKLF): elif Laue == '1 1 m': HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) #centrosymmetric - all ok - elif Laue == '2/m 1 1': + elif Laue == '2/m 1 1': HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,-1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]*HKLFT[0]==0)&(HKLFT[1]<0),HKLFT[:3]*np.array([1,-1,1])[:,nxs],HKLFT[:3]) @@ -2096,7 +2106,7 @@ def LaueUnique(Laue,HKLF): elif Laue == 'm m 2': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,1])[:,nxs],HKLFT[:3]) - elif Laue == '2 m m': + elif Laue == '2 m m': HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) elif Laue == 'm 2 m': @@ -2107,16 +2117,16 @@ def LaueUnique(Laue,HKLF): HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) - #tetragonal + #tetragonal #noncentrosymmetric - all ok elif Laue == '4': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,np.squeeze(np.inner(HKLF[:,:3],mat43[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[1]>0),np.squeeze(np.inner(HKLF[:,:3],mat41[nxs,:,:])).T,HKLFT[:3]) - elif Laue == '-4': - HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) + elif Laue == '-4': + HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]<=0,np.squeeze(np.inner(HKLF[:,:3],mat4bar[nxs,:,:])).T,HKLFT[:3]) - HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) + HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<=0,np.squeeze(np.inner(HKLF[:,:3],mat4bar[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[1]==0)&(HKLFT[2]<0),HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) elif Laue == '4 2 2': @@ -2131,20 +2141,20 @@ def LaueUnique(Laue,HKLF): HKLFT[:3] = np.where(HKLFT[1]<0,np.squeeze(np.inner(HKLF[:,:3],mat43[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]HKLFT[1]),np.squeeze(np.inner(HKLF[:,:3],matdm[nxs,:,:])).T,HKLFT[:3]) #centrosymmetric - all ok elif Laue == '4/m': @@ -2155,7 +2165,7 @@ def LaueUnique(Laue,HKLF): elif Laue == '4/m m m': HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) - HKLFT[:3] = np.where(HKLFT[1]<0,np.squeeze(np.inner(HKLF[:,:3],mat43[nxs,:,:])).T,HKLFT[:3]) + HKLFT[:3] = np.where(HKLFT[1]<0,np.squeeze(np.inner(HKLF[:,:3],mat43[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]HKLFT[1],np.squeeze(np.inner(HKLF[:,:3],matdm.T[nxs,:,:])).T,HKLFT[:3]) #cubic - all ok - #noncentrosymmetric - - elif Laue == '2 3': + #noncentrosymmetric - + elif Laue == '2 3': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[2]<0),HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) @@ -2272,8 +2282,8 @@ def LaueUnique(Laue,HKLF): HKLFT[:3] = np.where((HKLFT[2]>=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]<0)&((HKLFT[0]>-HKLFT[2])|(HKLFT[1]>-HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3t[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]<0)&((HKLFT[0]>-HKLFT[2])|(HKLFT[1]>=-HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3t[nxs,:,:])).T,HKLFT[:3]) - HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([-1,1,-1])[:,nxs],HKLFT[:3]) - elif Laue == '4 3 2': + HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([-1,1,-1])[:,nxs],HKLFT[:3]) + elif Laue == '4 3 2': HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,-1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,np.squeeze(np.inner(HKLF[:,:3],mat43[nxs,:,:])).T,HKLFT[:3]) @@ -2282,10 +2292,10 @@ def LaueUnique(Laue,HKLF): HKLFT[:3] = np.where((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2]),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2]),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]==0,np.squeeze(np.inner(HKLF[:,:3],mat2d43[nxs,:,:])).T,HKLFT[:3]) - elif Laue == '-4 3 m': - HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) + elif Laue == '-4 3 m': + HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]<=0,np.squeeze(np.inner(HKLF[:,:3],mat4bar[nxs,:,:])).T,HKLFT[:3]) - HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) + HKLFT[:3] = np.where(HKLFT[0]<=0,HKLFT[:3]*np.array([-1,-1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<=0,np.squeeze(np.inner(HKLF[:,:3],mat4bar[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]==0)&(HKLFT[1]==0)&(HKLFT[2]<0),HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]>=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]>=0)&(HKLFT[1]HKLFT[2]),np.squeeze(np.inner(HKLF[:,:3],matd3q[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[0]<0)&(HKLFT[2]>=-HKLFT[0])&(HKLFT[1]>HKLFT[2]),np.squeeze(np.inner(HKLF[:,:3],matdm3[nxs,:,:])).T,HKLFT[:3]) - #centrosymmetric + #centrosymmetric elif Laue == 'm 3': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,1])[:,nxs],HKLFT[:3]) - HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) + HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]>=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]>=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) elif Laue == 'm 3 m': HKLFT[:3] = np.where(HKLFT[0]<0,HKLFT[:3]*np.array([-1,1,1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[1]<0,HKLFT[:3]*np.array([1,-1,1])[:,nxs],HKLFT[:3]) - HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) + HKLFT[:3] = np.where(HKLFT[2]<0,HKLFT[:3]*np.array([1,1,-1])[:,nxs],HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]>=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where((HKLFT[2]>=0)&((HKLFT[0]>=HKLFT[2])|(HKLFT[1]>HKLFT[2])),np.squeeze(np.inner(HKLF[:,:3],matd3[nxs,:,:])).T,HKLFT[:3]) HKLFT[:3] = np.where(HKLFT[0]>HKLFT[1],np.squeeze(np.inner(HKLF[:,:3],matdm[nxs,:,:])).T,HKLFT[:3]) @@ -2329,8 +2339,8 @@ def RBChk(sytsym,L,M): if L%12 == 2: if M <= L//12: return True,1.0 else: - if M <= L//12+1: return True,1.0 - elif sytsym == '23': #cubics use different Fourier expansion than those below + if M <= L//12+1: return True,1.0 + elif sytsym == '23': #cubics use different Fourier expansion than those below if 2 < L < 11 and [L,M] in [[3,1],[4,1],[6,1],[6,2],[7,1],[8,1],[9,1],[9,2],[10,1],[10,2]]: return True,1.0 elif sytsym == 'm3': @@ -2347,7 +2357,7 @@ def RBChk(sytsym,L,M): if L%12 == 2: if M <= L//12: return True,1.0 else: - if M <= L//12+1: return True,1.0 + if M <= L//12+1: return True,1.0 elif sytsym == '6': if not M%6: return True,1.0 #P? elif sytsym == '-6': #L=M+2J @@ -2389,12 +2399,12 @@ def RBChk(sytsym,L,M): if not L%2 and not (M//2)%2: return True,1.0 elif sytsym == '4/mmm(z)': if not L%2 and not M%4: return True,1.0 - elif sytsym == '3' or sytsym == '3(111)': + elif sytsym in ['3','3(111)']: if not M%3: return True,1.0 #P? - elif sytsym == '-3' or sytsym == '-3(111)': + elif sytsym in ['-3','-3(111)']: if not L%2 and not M%3: return True,1.0 #P? elif sytsym in ['32','32(100)','32(111)']: - if not M%3: return True,-1.0**L + if not M%3: return True,-1.0**(L-M) elif sytsym == '32(120)': if not M%3: return True,-1.0**(L-M) elif sytsym in ['3m','3m(100)','3m(111)']: @@ -2417,7 +2427,7 @@ def RBChk(sytsym,L,M): if M%2: return True,1.0 elif 'mmm' in sytsym : if not L%2 and not M%2: return True,1.0 - elif sytsym == '2(x)' or sytsym == '2(100)': + elif sytsym in ['2(x)','2(100)']: return True,-1.0**(L-M) elif sytsym == '2(y)': return True,-1.0**L @@ -2441,7 +2451,7 @@ def RBChk(sytsym,L,M): elif sytsym == '-1': #P? if not L%2: return True,1.0 return False,0. - + def RBsymChk(RBsym,cubic,coefNames,L=18): '''imposes rigid body symmetry on spherical harmonics terms Key problem is noncubic RB symmetries in cubic site symmetries & vice versa. @@ -2450,12 +2460,6 @@ def RBsymChk(RBsym,cubic,coefNames,L=18): :param list coefNames: sp. harm coefficient names to be checked/converted :param int L: maximum spherical harmonic order no. for cubic generation if needed ''' - # cubicsigns = {'C(3,1)c':[-1,],'C(4,1)c':[1,1,],'C(6,1)c':[1,-1],'C(6,2)c':[1,-1],'C(7,1)c':[1,-1],'C(8,1)c':[1,1,1], - # 'C(9,1)c':[1,-1],'C(9,2)c':[1,-1],'C(10,1)c':[1,-1,-1],'C(10,2)c':[1,1,-1]} - # cubicnames = {'C(3,1)c':['C(3,2)',],'C(4,1)c':['C(4,0)','C(4,4)'],'C(6,1)c':['C(6,0)','C(6,4)'], - # 'C(6,2)c':['C(6,2)','C(6,6)'],'C(7,1)c':['C(7,2)','C(7,6)'],'C(8,1)c':['C(8,0)','C(8,4)','C(8,8)'], - # 'C(9,1)c':['C(9,2)','C((9,6)'],'C(9,2)c':['C(9,4)','C(9,8)'], - # 'C(10,1)c':['C(10,0)','C(10,4)','C(10,8)'],'C(10,2)c':['C(10,2)','C(10,6)','C(10,10)']} newNames = [] newSgns = [] if cubic: #sytsym is a cubic site @@ -2501,7 +2505,7 @@ def RBsymChk(RBsym,cubic,coefNames,L=18): newNames.append(name) newSgns.append(sgn) return newNames,newSgns - + def GenRBCoeff(sytsym,RBsym,L): '''imposes rigid body symmetry on spherical harmonics terms Key problem is noncubic RB symmetries in cubic site symmetries & vice versa. @@ -2516,9 +2520,8 @@ def GenRBCoeff(sytsym,RBsym,L): cubic = False if sytsym in ['23','m3','432','-43m','m3m']: cubic = True - for iord in range(L+1): - if not iord: continue - for n in range(iord+1): + for iord in range(1,L+1): + for n in range(-iord,iord+1): rbChk,sgn = RBChk(sytsym,iord,n) if rbChk: if cubic: @@ -2543,7 +2546,7 @@ def GenShCoeff(sytsym,L): cubic = False if sytsym in ['23','m3','432','-43m','m3m','53m']: cubic = True - for n in range(L+1): + for n in range(-L,L+1): rbChk,sgn = RBChk(sytsym,L,n) if rbChk: if cubic: @@ -2555,8 +2558,8 @@ def GenShCoeff(sytsym,L): return newNames,newSgns def OdfChk(SGLaue,L,M): - '''finds symmetry rules for spherical harmonic coefficients for Laue groups - :param str SGLaue: Laue symbol + '''finds symmetry rules for spherical harmonic coefficients for Laue groups + :param str SGLaue: Laue symbol :param int L: principal harmonic term; only evens are used :param int M: second harmonic term; can be -L <= M <= L :returns True if allowed @@ -2615,7 +2618,7 @@ def GenSHCoeff(SGLaue,SamSym,L,IfLMN=True): else: coeffNames.append('C(%d,%d)'%(iord,n)) return coeffNames - + def CrsAng(H,cell,SGData): '''Convert HKL to polar coordinates with proper orientation WRT space group point group :param array H: hkls @@ -2653,7 +2656,10 @@ def CrsAng(H,cell,SGData): DR = np.inner(H3,np.inner(G,H3)) DHR = np.inner(H,np.inner(G,H3)) DHR /= np.sqrt(DR*DH) - phi = np.where(DHR <= 1.0,acosd(DHR),0.0) + if DHR.shape: + phi = acosd(DHR) + else: + phi = acosd(max(-1,min(DHR,1.))) if Laue == '-1': BA = H.T[1]*a/(b-H.T[0]*cosd(ga)) BB = H.T[0]*sind(ga)**2 @@ -2681,19 +2687,19 @@ def CrsAng(H,cell,SGData): BB = SQ3*H.T[0] beta = atan2d(BA,BB) return phi,beta - + def SamAng(Tth,Gangls,Sangl,IFCoup): """Compute sample orientation angles vs laboratory coord. system - :param Tth: Signed theta - :param Gangls: Sample goniometer angles phi,chi,omega,azmuth - :param Sangl: Sample angle zeros om-0, chi-0, phi-0 + :param Tth: Signed theta + :param Gangls: Sample goniometer angles phi,chi,omega,azmuth + :param Sangl: Sample angle zeros om-0, chi-0, phi-0 :param IFCoup: True if omega & 2-theta coupled in CW scan - :returns: - psi,gam: Sample odf angles + :returns: + psi,gam: Sample odf angles dPSdA,dGMdA: Angle zero derivatives - """ - + """ + if IFCoup: GSomeg = sind(Gangls[2]+Tth) GComeg = cosd(Gangls[2]+Tth) @@ -2701,7 +2707,7 @@ def SamAng(Tth,Gangls,Sangl,IFCoup): GSomeg = sind(Gangls[2]) GComeg = cosd(Gangls[2]) GSTth = sind(Tth) - GCTth = cosd(Tth) + GCTth = cosd(Tth) GSazm = sind(Gangls[3]) GCazm = cosd(Gangls[3]) GSchi = sind(Gangls[1]) @@ -2716,20 +2722,20 @@ def SamAng(Tth,Gangls,Sangl,IFCoup): BT = GSTth*GSomeg+GCTth*GCazm*GComeg CT = -GCTth*GSazm*GSchi DT = -GCTth*GSazm*GCchi - + BC1 = -AT*GSphi+(CT+BT*GCchi)*GCphi BC2 = DT-BT*GSchi BC3 = AT*GCphi+(CT+BT*GCchi)*GSphi - - BC = BC1*SComeg*SCchi+BC2*SComeg*SSchi-BC3*SSomeg + + BC = BC1*SComeg*SCchi+BC2*SComeg*SSchi-BC3*SSomeg psi = acosd(BC) - + BD = 1.0-BC**2 C = np.where(BD>1.e-6,rpd/np.sqrt(BD),0.) dPSdA = [-C*(-BC1*SSomeg*SCchi-BC2*SSomeg*SSchi-BC3*SComeg), -C*(-BC1*SComeg*SSchi+BC2*SComeg*SCchi), -C*(-BC1*SSomeg-BC3*SComeg*SCchi)] - + BA = -BC1*SSchi+BC2*SCchi BB = BC1*SSomeg*SCchi+BC2*SSomeg*SSchi+BC3*SComeg gam = atan2d(BB,BA) @@ -2739,47 +2745,62 @@ def SamAng(Tth,Gangls,Sangl,IFCoup): dBAdO = 0 dBAdC = -BC1*SCchi-BC2*SSchi dBAdF = BC3*SSchi - + dBBdO = BC1*SComeg*SCchi+BC2*SComeg*SSchi-BC3*SSomeg dBBdC = -BC1*SSomeg*SSchi+BC2*SSomeg*SCchi dBBdF = BC1*SComeg-BC3*SSomeg*SCchi - + dGMdA = np.where(BD > 1.e-6,[(BA*dBBdO-BB*dBAdO)/BD,(BA*dBBdC-BB*dBAdC)/BD, \ (BA*dBBdF-BB*dBAdF)/BD],[np.zeros_like(BD),np.zeros_like(BD),np.zeros_like(BD)]) - - return psi,gam,dPSdA,dGMdA + return psi,gam,dPSdA,dGMdA +'''BOH is even order terms from Table 15.2.1 of H.-J. Bunge, "Texture Analysis in Materials Science", +Cuvillier Verlag, Gottingen, 1993, p 501. BOH = Blmu for cubic [001] parallel to Z. Used in GetKcl & GetKclKsl +Dummy odd order terms not needed for texture analysis +''' BOH = { 'L=2':[[],[],[]], 'L=4':[[0.30469720,0.36418281],[],[]], 'L=6':[[-0.14104740,0.52775103],[],[]], 'L=8':[[0.28646862,0.21545346,0.32826995],[],[]], +'L=9':[[],[],[]], 'L=10':[[-0.16413497,0.33078546,0.39371345],[],[]], 'L=12':[[0.26141975,0.27266871,0.03277460,0.32589402], [0.09298802,-0.23773812,0.49446631,0.0],[]], +'L=13':[[],[],[]], 'L=14':[[-0.17557309,0.25821932,0.27709173,0.33645360],[],[]], +'L=15':[[],[],[]], 'L=16':[[0.24370673,0.29873515,0.06447688,0.00377,0.32574495], [0.12039646,-0.25330128,0.23950998,0.40962508,0.0],[]], +'L=17':[[],[],[]], 'L=18':[[-0.16914245,0.17017340,0.34598142,0.07433932,0.32696037], [-0.06901768,0.16006562,-0.24743528,0.47110273,0.0],[]], +'L=19':[[],[],[]], 'L=20':[[0.23067026,0.31151832,0.09287682,0.01089683,0.00037564,0.32573563], [0.13615420,-0.25048007,0.12882081,0.28642879,0.34620433,0.0],[]], +'L=21':[[],[],[]], 'L=22':[[-0.16109560,0.10244188,0.36285175,0.13377513,0.01314399,0.32585583], [-0.09620055,0.20244115,-0.22389483,0.17928946,0.42017231,0.0],[]], +'L=23':[[],[],[]], 'L=24':[[0.22050742,0.31770654,0.11661736,0.02049853,0.00150861,0.00003426,0.32573505], [0.13651722,-0.21386648,0.00522051,0.33939435,0.10837396,0.32914497,0.0], [0.05378596,-0.11945819,0.16272298,-0.26449730,0.44923956,0.0,0.0]], +'L=25':[[],[],[]], 'L=26':[[-0.15435003,0.05261630,0.35524646,0.18578869,0.03259103,0.00186197,0.32574594], [-0.11306511,0.22072681,-0.18706142,0.05439948,0.28122966,0.35634355,0.0],[]], +'L=27':[[],[],[]], 'L=28':[[0.21225019,0.32031716,0.13604702,0.03132468,0.00362703,0.00018294,0.00000294,0.32573501], [0.13219496,-0.17206256,-0.08742608,0.32671661,0.17973107,0.02567515,0.32619598,0.0], [0.07989184,-0.16735346,0.18839770,-0.20705337,0.12926808,0.42715602,0.0,0.0]], +'L=29':[[],[],[]], 'L=30':[[-0.14878368,0.01524973,0.33628434,0.22632587,0.05790047,0.00609812,0.00022898,0.32573594], [-0.11721726,0.20915005,-0.11723436,-0.07815329,0.31318947,0.13655742,0.33241385,0.0], [-0.04297703,0.09317876,-0.11831248,0.17355132,-0.28164031,0.42719361,0.0,0.0]], +'L=31':[[],[],[]], 'L=32':[[0.20533892,0.32087437,0.15187897,0.04249238,0.00670516,0.00054977,0.00002018,0.00000024,0.32573501], [0.12775091,-0.13523423,-0.14935701,0.28227378,0.23670434,0.05661270,0.00469819,0.32578978,0.0], [0.09703829,-0.19373733,0.18610682,-0.14407046,0.00220535,0.26897090,0.36633402,0.0,0.0]], +'L=33':[[],[],[]], 'L=34':[[-0.14409234,-0.01343681,0.31248977,0.25557722,0.08571889,0.01351208,0.00095792,0.00002550,0.32573508], [-0.11527834,0.18472133,-0.04403280,-0.16908618,0.27227021,0.21086614,0.04041752,0.32688152,0.0], [-0.06773139,0.14120811,-0.15835721,0.18357456,-0.19364673,0.08377174,0.43116318,0.0,0.0]] @@ -2789,7 +2810,7 @@ def SamAng(Tth,Gangls,Sangl,IFCoup): def GetKcl(L,N,SGLaue,phi,beta): 'needs doc string' - import pytexture as ptx +# from . import pytexture as ptx if SGLaue in ['m3','m3m']: if 'array' in str(type(phi)) and np.any(phi.shape): Kcl = np.zeros_like(phi) @@ -2801,7 +2822,7 @@ def GetKcl(L,N,SGLaue,phi,beta): pcrs = ptx.pyplmpsi(L,j,len(phi),phi)[0] else: pcrs = ptx.pyplmpsi(L,j,1,phi)[0] - Kcl += BOH['L=%d'%(L)][N-1][im]*pcrs*cosd(j*beta) + Kcl += BOH['L=%d'%(L)][N-1][im]*pcrs*cosd(j*beta) else: if 'array' in str(type(phi)) and np.any(phi.shape): pcrs = ptx.pyplmpsi(L,N,len(phi),phi)[0] @@ -2811,7 +2832,7 @@ def GetKcl(L,N,SGLaue,phi,beta): if N: pcrs *= SQ2 if SGLaue in ['mmm','4/mmm','6/mmm','R3mR','3m1','31m']: - if SGLaue in ['3mR','3m1','31m']: + if SGLaue in ['3mR','3m1','31m']: if N%6 == 3: Kcl = pcrs*sind(N*beta) else: @@ -2821,10 +2842,10 @@ def GetKcl(L,N,SGLaue,phi,beta): else: Kcl = pcrs*(cosd(N*beta)+sind(N*beta)) return Kcl - + def GetKsl(L,M,SamSym,psi,gam): 'needs doc string' - import pytexture as ptx +# from . import pytexture as ptx if 'array' in str(type(psi)) and np.any(psi.shape): psrs,dpdps = ptx.pyplmpsi(L,M,len(psi),psi) else: @@ -2844,14 +2865,14 @@ def GetKsl(L,M,SamSym,psi,gam): Ksl = psrs*dum dKsdp = dpdps*dum dKsdg = psrs*M*(-sind(M*gam)+cosd(M*gam)) - return Ksl,dKsdp,dKsdg - + return Ksl,dKsdp,dKsdg + def GetKclKsl(L,N,SGLaue,psi,phi,beta): """ This is used for spherical harmonics description of preferred orientation; cylindrical symmetry only (M=0) and no sample angle derivatives returned """ - import pytexture as ptx +# from . import pytexture as ptx Ksl,x = ptx.pyplmpsi(L,0,1,psi) Ksl *= RSQ2PI if SGLaue in ['m3','m3m']: @@ -2859,14 +2880,14 @@ def GetKclKsl(L,N,SGLaue,psi,phi,beta): for j in range(0,L+1,4): im = j//4 pcrs,dum = ptx.pyplmpsi(L,j,1,phi) - Kcl += BOH['L=%d'%(L)][N-1][im]*pcrs*cosd(j*beta) + Kcl += BOH['L=%d'%(L)][N-1][im]*pcrs*cosd(j*beta) else: pcrs,dum = ptx.pyplmpsi(L,N,1,phi) pcrs *= RSQ2PI if N: pcrs *= SQ2 if SGLaue in ['mmm','4/mmm','6/mmm','R3mR','3m1','31m']: - if SGLaue in ['3mR','3m1','31m']: + if SGLaue in ['3mR','3m1','31m']: if N%6 == 3: Kcl = pcrs*sind(N*beta) else: @@ -2877,45 +2898,83 @@ def GetKclKsl(L,N,SGLaue,psi,phi,beta): Kcl = pcrs*(cosd(N*beta)+sind(N*beta)) return Kcl*Ksl,Lnorm(L) -def H2ThPh(H,Bmat,Q): +def H2ThPh2(H,Bmat): '''Convert HKL to spherical polar & azimuth angles - + :param array H: array of hkl as [n,3] :param [3,3] array Bmat: inv crystal to Cartesian transformation - :param array Q: quaternion for rotation of HKL to new polar axis :returns array Th: HKL azimuth angles :returns array Ph: HKL polar angles ''' - # A,V = G2mth.Q2AVdeg(Q) - # QR,R = G2mth.make2Quat(V,np.array([0.,0.,1.0])) - # QA = G2mth.AVdeg2Q(A,np.array([0.,0.,1.0])) - # Q2 = G2mth.prodQQ(QR,QA) - Qmat = G2mth.Q2Mat(Q) - CH1 = np.inner(H,Bmat.T) - CH = np.inner(CH1,Qmat.T) - N = nl.norm(CH,axis=1) - CH /= N[:,nxs] - H3 = np.array([0,0,1.]) - DHR = np.inner(CH,H3) - Ph = np.where(DHR <= 1.0,acosd(DHR),0.0) #polar angle 0<=Ph<=180.; correct - TH = CH*np.array([1.,1.,0.])[nxs,:] #projection of CH onto xy plane - N = nl.norm(TH,axis=1) - N = np.where(N > 1.e-5,N,1.) - TH /= N[:,nxs] - Th = atan2d(TH[:,1],TH[:,0]) #azimuth angle 0<=Th<360< - Th = np.where(Th<0.,Th+360.,Th) - return Th,Ph #azimuth,polar angles + Hcart = np.inner(H,Bmat) + R = np.sqrt(np.sum(np.square(Hcart),axis=1)) + Hcart /= R[:,nxs] + Pl = acosd(Hcart[:,2]) + Az = atan2d(Hcart[:,1],Hcart[:,0]) + return R,Az,Pl + +# def H2ThPh(H,Bmat,Q): +# '''Convert HKL to spherical polar & azimuth angles - wrong! + +# :param array H: array of hkl as [n,3] +# :param [3,3] array Bmat: inv crystal to Cartesian transformation +# :param array Q: quaternion for rotation of HKL to new polar axis +# :returns array Th: HKL azimuth angles +# :returns array Ph: HKL polar angles +# ''' +# # A,V = G2mth.Q2AVdeg(Q) +# # QR,R = G2mth.make2Quat(V,np.array([0.,0.,1.0])) +# # QA = G2mth.AVdeg2Q(A,np.array([0.,0.,1.0])) +# # Q2 = G2mth.prodQQ(QR,QA) +# Qmat = G2mth.Q2Mat(Q) +# CH1 = np.inner(H,Bmat.T) +# CH = np.inner(CH1,Qmat.T) +# N = nl.norm(CH,axis=1) +# CH /= N[:,nxs] +# H3 = np.array([0,0,1.]) +# DHR = np.inner(CH,H3) +# Ph = np.where(DHR <= 1.0,acosd(DHR),0.0) #polar angle 0<=Ph<=180.; correct +# TH = CH*np.array([1.,1.,0.])[nxs,:] #projection of CH onto xy plane +# N = nl.norm(TH,axis=1) +# N = np.where(N > 1.e-5,N,1.) +# TH /= N[:,nxs] +# Th = atan2d(TH[:,1],TH[:,0]) #azimuth angle 0<=Th<360< +# Th = np.where(Th<0.,Th+360.,Th) +# return Th,Ph #azimuth,polar angles + +def SetUVvec(Neigh): + ''' Set deformation coordinate choices from neighbors; called in G2phsGUI/UpdateDeformation + + param: list neigh: list of neighboring atoms; each with name, dist & cartesian vector + + :returns list UVvec: list of normalized vectors + :returns list UVchoice: list of names for each + ''' + UVvec = [] + UVchoice = [] + choice = ['A','B','C','D','E','F','G','H'] + for N,neigh in enumerate(Neigh): + UVvec += [neigh[3]/neigh[2],] + UVchoice += choice[N] + Nneigh = len(Neigh) + if Nneigh >= 2: + UVvec += [(Neigh[0][3]+Neigh[1][3])/np.sqrt(Neigh[0][2]**2+Neigh[1][2]**2),] + UVchoice += ['A+B',] + if Nneigh >= 3: + UVvec += [(Neigh[0][3]+Neigh[1][3]+Neigh[2][3])/np.sqrt(Neigh[0][2]**2+Neigh[1][2]**2+Neigh[2][2]**2),] + UVchoice += ['A+B+C',] + return UVvec,UVchoice def SHarmcal(SytSym,SHFln,psi,gam): '''Perform a surface spherical harmonics computation. Presently only used for plotting Note that the the number of gam values must either be 1 or must match psi - + :param str SytSym: sit symmetry - only looking for cubics - remove this :param dict SHFln: spherical harmonics coefficients; key has L & M :param float/array psi: Azimuthal coordinate 0 <= Th <= 360 :param float/array gam: Polar coordinate 0<= Ph <= 180 - + :returns array SHVal: spherical harmonics array for psi,gam values ''' SHVal = np.ones_like(psi)/(4.*np.pi) @@ -2926,8 +2985,8 @@ def SHarmcal(SytSym,SHFln,psi,gam): if SytSym in ['m3m','m3','43m','432','23'] or 'c' in trm: Ksl = CubicSHarm(l,m,psi,gam) else: - p = SHFln[term][2] - Ksl = SphHarmAng(l,m,p,psi,gam) + # p = SHFln[term][2] + Ksl = SphHarmAng(l,m,1.0,psi,gam) SHVal += SHFln[term][0]*Ksl return SHVal @@ -2936,50 +2995,51 @@ def KslCalc(trm,psi,gam): :param str trm:sp. harm term name in the form of 'C(l,m)' or 'C(l,m)c' for cubic :param float/array psi: Azimuthal coordinate 0 <= Th <= 360 :param float/array gam: Polar coordinate 0<= Ph <= 180 - + :returns array Ksl: spherical harmonics angular part for psi,gam pairs ''' l,m = eval(trm.strip('C').strip('c')) if 'c' in trm: return CubicSHarm(l,m,psi,gam) else: - return SphHarmAng(l,m,1.0,psi,gam) - + return SphHarmAng(l,m,1.0,psi,gam) + def SphHarmAng(L,M,P,Th,Ph): ''' Compute spherical harmonics values using scipy.special.sph_harm - + :param int L: degree of the harmonic (L >= 0) :param int M: order number (\\|M\\| <= L) :param int P: sign flag = -1 or 1 :param float/array Th: Azimuthal coordinate 0 <= Th <= 360 :param float/array Ph: Polar coordinate 0<= Ph <= 180 - + :returns ylmp value/array: as reals ''' - ylmp = spsp.sph_harm(M,L,rpd*Th,rpd*Ph) #wants radians; order then degree - + ylmp = spsp.sph_harm(M,L,rpd*Th,rpd*Ph) #wants radians; order then degree; not normalized + #### TODO: this will be deprecated in future scipy; new one sph_harm_y in scipy 1.15.1 + if M > 0: return (-1)**M*P*np.real(ylmp)*SQ2 elif M == 0: - return P*np.real(ylmp) + return P*np.real(ylmp) else: return (-1)**M*P*np.imag(ylmp)*SQ2 - + def CubicSHarm(L,M,Th,Ph): - '''Calculation of the cubic harmonics given in Table 3 in M.Kara & K. Kurki-Suonio, - Acta Cryt. A37, 201 (1981). For L = 14,20 only for m3m from F.M. Mueller and M.G. Priestley, + '''Calculation of the cubic harmonics given in Table 3 in M.Kara & K. Kurki-Suonio, + Acta Cryt. A37, 201 (1981). For L = 14,20 only for m3m from F.M. Mueller and M.G. Priestley, Phys Rev 148, 638 (1966) - + :param int L: degree of the harmonic (L >= 0) :param int M: order number [\\|M\\| <= L] :param float/array Th: Azimuthal coordinate 0 <= Th <= 360 :param float/array Ph: Polar coordinate 0<= Ph <= 180 - + :returns klm value/array: cubic harmonics - + ''' if L == 0: - return SphHarmAng(L,M,1,Th,Ph) + return SphHarmAng(0,0,1,Th,Ph) elif L == 3: return SphHarmAng(3,2,-1,Th,Ph) elif L == 4: @@ -3072,10 +3132,10 @@ def CubicSHarm(L,M,Th,Ph): else: #shouldn't happen return 0.0 return klm - + def Glnh(SHCoef,psi,gam,SamSym): 'needs doc string' - import pytexture as ptx +# from . import pytexture as ptx Fln = np.zeros(len(SHCoef)) for i,term in enumerate(SHCoef): @@ -3094,8 +3154,8 @@ def Glnh(SHCoef,psi,gam,SamSym): def Flnh(SHCoef,phi,beta,SGData): 'needs doc string' - import pytexture as ptx - +# from . import pytexture as ptx + Fln = np.zeros(len(SHCoef)) for i,term in enumerate(SHCoef): l,m,n = eval(term.strip('C')) @@ -3104,14 +3164,14 @@ def Flnh(SHCoef,phi,beta,SGData): for j in range(0,l+1,4): im = j//4 pcrs,dum = ptx.pyplmpsi(l,j,1,phi) - Kcl += BOH['L='+str(l)][n-1][im]*pcrs*cosd(j*beta) + Kcl += BOH['L='+str(l)][n-1][im]*pcrs*cosd(j*beta) else: #all but cubic pcrs,dum = ptx.pyplmpsi(l,n,1,phi) pcrs *= RSQPI if n == 0: pcrs /= SQ2 if SGData['SGLaue'] in ['mmm','4/mmm','6/mmm','R3mR','3m1','31m']: - if SGData['SGLaue'] in ['3mR','3m1','31m']: + if SGData['SGLaue'] in ['3mR','3m1','31m']: if n%6 == 3: Kcl = pcrs*sind(n*beta) else: @@ -3123,13 +3183,13 @@ def Flnh(SHCoef,phi,beta,SGData): Fln[i] = SHCoef[term]*Kcl*Lnorm(l) ODFln = dict(zip(SHCoef.keys(),list(zip(SHCoef.values(),Fln)))) return ODFln - + def polfcal(ODFln,SamSym,psi,gam): '''Perform a pole figure computation. Note that the the number of gam values must either be 1 or must match psi. Updated for numpy 1.8.0 ''' - import pytexture as ptx +# from . import pytexture as ptx PolVal = np.ones_like(psi) for term in ODFln: if abs(ODFln[term][1]) > 1.e-3: @@ -3150,8 +3210,8 @@ def polfcal(ODFln,SamSym,psi,gam): def invpolfcal(ODFln,SGData,phi,beta): 'needs doc string' - import pytexture as ptx - +# from . import pytexture as ptx + invPolVal = np.ones_like(beta) for term in ODFln: if abs(ODFln[term][1]) > 1.e-3: @@ -3161,14 +3221,14 @@ def invpolfcal(ODFln,SGData,phi,beta): for j in range(0,l+1,4): im = j//4 pcrs,dum = ptx.pyplmpsi(l,j,len(beta),phi) - Kcl += BOH['L=%d'%(l)][n-1][im]*pcrs*cosd(j*beta) + Kcl += BOH['L=%d'%(l)][n-1][im]*pcrs*cosd(j*beta) else: #all but cubic pcrs,dum = ptx.pyplmpsi(l,n,len(beta),phi) pcrs *= RSQPI if n == 0: pcrs /= SQ2 if SGData['SGLaue'] in ['mmm','4/mmm','6/mmm','R3mR','3m1','31m']: - if SGData['SGLaue'] in ['3mR','3m1','31m']: + if SGData['SGLaue'] in ['3mR','3m1','31m']: if n%6 == 3: Kcl = pcrs*sind(n*beta) else: @@ -3177,10 +3237,10 @@ def invpolfcal(ODFln,SGData,phi,beta): Kcl = pcrs*cosd(n*beta) else: Kcl = pcrs*(cosd(n*beta)+sind(n*beta)) - invPolVal += ODFln[term][1]*Kcl + invPolVal += ODFln[term][1]*Kcl return invPolVal - - + + def textureIndex(SHCoef): 'needs doc string' Tindx = 1.0 @@ -3206,361 +3266,3 @@ def textureIndex(SHCoef): cellUlbl = ('a','b','c',u'\u03B1',u'\u03B2',u'\u03B3') 'unicode labels for a, b, c, alpha, beta, gamma' - -# self-test materials follow. -selftestlist = [] -'''Defines a list of self-tests''' -selftestquiet = True -def _ReportTest(): - 'Report name and doc string of current routine when ``selftestquiet`` is False' - if not selftestquiet: - import inspect - caller = inspect.stack()[1][3] - doc = eval(caller).__doc__ - if doc is not None: - print('testing '+__file__+' with '+caller+' ('+doc+')') - else: - print('testing '+__file__()+" with "+caller) -NeedTestData = True -def TestData(): - array = np.array - global NeedTestData - NeedTestData = False - global CellTestData - # output from uctbx computed on platform darwin on 2010-05-28 - CellTestData = [ -# cell, g, G, cell*, V, V* - [(4, 4, 4, 90, 90, 90), - array([[ 1.60000000e+01, 9.79717439e-16, 9.79717439e-16], - [ 9.79717439e-16, 1.60000000e+01, 9.79717439e-16], - [ 9.79717439e-16, 9.79717439e-16, 1.60000000e+01]]), array([[ 6.25000000e-02, 3.82702125e-18, 3.82702125e-18], - [ 3.82702125e-18, 6.25000000e-02, 3.82702125e-18], - [ 3.82702125e-18, 3.82702125e-18, 6.25000000e-02]]), (0.25, 0.25, 0.25, 90.0, 90.0, 90.0), 64.0, 0.015625], -# cell, g, G, cell*, V, V* - [(4.0999999999999996, 5.2000000000000002, 6.2999999999999998, 100, 80, 130), - array([[ 16.81 , -13.70423184, 4.48533243], - [-13.70423184, 27.04 , -5.6887143 ], - [ 4.48533243, -5.6887143 , 39.69 ]]), array([[ 0.10206349, 0.05083339, -0.00424823], - [ 0.05083339, 0.06344997, 0.00334956], - [-0.00424823, 0.00334956, 0.02615544]]), (0.31947376387537696, 0.25189277536327803, 0.16172643497798223, 85.283666420376008, 94.716333579624006, 50.825714168082683), 100.98576357983838, 0.0099023858863968445], -# cell, g, G, cell*, V, V* - [(3.5, 3.5, 6, 90, 90, 120), - array([[ 1.22500000e+01, -6.12500000e+00, 1.28587914e-15], - [ -6.12500000e+00, 1.22500000e+01, 1.28587914e-15], - [ 1.28587914e-15, 1.28587914e-15, 3.60000000e+01]]), array([[ 1.08843537e-01, 5.44217687e-02, 3.36690552e-18], - [ 5.44217687e-02, 1.08843537e-01, 3.36690552e-18], - [ 3.36690552e-18, 3.36690552e-18, 2.77777778e-02]]), (0.32991443953692895, 0.32991443953692895, 0.16666666666666669, 90.0, 90.0, 60.000000000000021), 63.652867178156257, 0.015710211406520427], - ] - global CoordTestData - CoordTestData = [ -# cell, ((frac, ortho),...) - ((4,4,4,90,90,90,), [ - ((0.10000000000000001, 0.0, 0.0),(0.40000000000000002, 0.0, 0.0)), - ((0.0, 0.10000000000000001, 0.0),(2.4492935982947065e-17, 0.40000000000000002, 0.0)), - ((0.0, 0.0, 0.10000000000000001),(2.4492935982947065e-17, -2.4492935982947065e-17, 0.40000000000000002)), - ((0.10000000000000001, 0.20000000000000001, 0.29999999999999999),(0.40000000000000013, 0.79999999999999993, 1.2)), - ((0.20000000000000001, 0.29999999999999999, 0.10000000000000001),(0.80000000000000016, 1.2, 0.40000000000000002)), - ((0.29999999999999999, 0.20000000000000001, 0.10000000000000001),(1.2, 0.80000000000000004, 0.40000000000000002)), - ((0.5, 0.5, 0.5),(2.0, 1.9999999999999998, 2.0)), -]), -# cell, ((frac, ortho),...) - ((4.1,5.2,6.3,100,80,130,), [ - ((0.10000000000000001, 0.0, 0.0),(0.40999999999999998, 0.0, 0.0)), - ((0.0, 0.10000000000000001, 0.0),(-0.33424955703700043, 0.39834311042186865, 0.0)), - ((0.0, 0.0, 0.10000000000000001),(0.10939835193016617, -0.051013289294572106, 0.6183281045774256)), - ((0.10000000000000001, 0.20000000000000001, 0.29999999999999999),(0.069695941716497567, 0.64364635296002093, 1.8549843137322766)), - ((0.20000000000000001, 0.29999999999999999, 0.10000000000000001),(-0.073350319180835066, 1.1440160419710339, 0.6183281045774256)), - ((0.29999999999999999, 0.20000000000000001, 0.10000000000000001),(0.67089923785616512, 0.74567293154916525, 0.6183281045774256)), - ((0.5, 0.5, 0.5),(0.92574397446582857, 1.7366491056364828, 3.0916405228871278)), -]), -# cell, ((frac, ortho),...) - ((3.5,3.5,6,90,90,120,), [ - ((0.10000000000000001, 0.0, 0.0),(0.35000000000000003, 0.0, 0.0)), - ((0.0, 0.10000000000000001, 0.0),(-0.17499999999999993, 0.3031088913245536, 0.0)), - ((0.0, 0.0, 0.10000000000000001),(3.6739403974420595e-17, -3.6739403974420595e-17, 0.60000000000000009)), - ((0.10000000000000001, 0.20000000000000001, 0.29999999999999999),(2.7675166561703527e-16, 0.60621778264910708, 1.7999999999999998)), - ((0.20000000000000001, 0.29999999999999999, 0.10000000000000001),(0.17500000000000041, 0.90932667397366063, 0.60000000000000009)), - ((0.29999999999999999, 0.20000000000000001, 0.10000000000000001),(0.70000000000000018, 0.6062177826491072, 0.60000000000000009)), - ((0.5, 0.5, 0.5),(0.87500000000000067, 1.5155444566227676, 3.0)), -]), -] - global LaueTestData #generated by GSAS - LaueTestData = { - 'R 3 m':[(4.,4.,6.,90.,90.,120.),((1,0,1,6),(1,0,-2,6),(0,0,3,2),(1,1,0,6),(2,0,-1,6),(2,0,2,6), - (1,1,3,12),(1,0,4,6),(2,1,1,12),(2,1,-2,12),(3,0,0,6),(1,0,-5,6),(2,0,-4,6),(3,0,-3,6),(3,0,3,6), - (0,0,6,2),(2,2,0,6),(2,1,4,12),(2,0,5,6),(3,1,-1,12),(3,1,2,12),(1,1,6,12),(2,2,3,12),(2,1,-5,12))], - 'R 3':[(4.,4.,6.,90.,90.,120.),((1,0,1,6),(1,0,-2,6),(0,0,3,2),(1,1,0,6),(2,0,-1,6),(2,0,2,6),(1,1,3,6), - (1,1,-3,6),(1,0,4,6),(3,-1,1,6),(2,1,1,6),(3,-1,-2,6),(2,1,-2,6),(3,0,0,6),(1,0,-5,6),(2,0,-4,6), - (2,2,0,6),(3,0,3,6),(3,0,-3,6),(0,0,6,2),(3,-1,4,6),(2,0,5,6),(2,1,4,6),(4,-1,-1,6),(3,1,-1,6), - (3,1,2,6),(4,-1,2,6),(2,2,-3,6),(1,1,-6,6),(1,1,6,6),(2,2,3,6),(2,1,-5,6),(3,-1,-5,6))], - 'P 3':[(4.,4.,6.,90.,90.,120.),((0,0,1,2),(1,0,0,6),(1,0,1,6),(0,0,2,2),(1,0,-1,6),(1,0,2,6),(1,0,-2,6), - (1,1,0,6),(0,0,3,2),(1,1,1,6),(1,1,-1,6),(1,0,3,6),(1,0,-3,6),(2,0,0,6),(2,0,-1,6),(1,1,-2,6), - (1,1,2,6),(2,0,1,6),(2,0,-2,6),(2,0,2,6),(0,0,4,2),(1,1,-3,6),(1,1,3,6),(1,0,-4,6),(1,0,4,6), - (2,0,-3,6),(2,1,0,6),(2,0,3,6),(3,-1,0,6),(2,1,1,6),(3,-1,-1,6),(2,1,-1,6),(3,-1,1,6),(1,1,4,6), - (3,-1,2,6),(3,-1,-2,6),(1,1,-4,6),(0,0,5,2),(2,1,2,6),(2,1,-2,6),(3,0,0,6),(3,0,1,6),(2,0,4,6), - (2,0,-4,6),(3,0,-1,6),(1,0,-5,6),(1,0,5,6),(3,-1,-3,6),(2,1,-3,6),(2,1,3,6),(3,-1,3,6),(3,0,-2,6), - (3,0,2,6),(1,1,5,6),(1,1,-5,6),(2,2,0,6),(3,0,3,6),(3,0,-3,6),(0,0,6,2),(2,0,-5,6),(2,1,-4,6), - (2,2,-1,6),(3,-1,-4,6),(2,2,1,6),(3,-1,4,6),(2,1,4,6),(2,0,5,6),(1,0,-6,6),(1,0,6,6),(4,-1,0,6), - (3,1,0,6),(3,1,-1,6),(3,1,1,6),(4,-1,-1,6),(2,2,2,6),(4,-1,1,6),(2,2,-2,6),(3,1,2,6),(3,1,-2,6), - (3,0,4,6),(3,0,-4,6),(4,-1,-2,6),(4,-1,2,6),(2,2,-3,6),(1,1,6,6),(1,1,-6,6),(2,2,3,6),(3,-1,5,6), - (2,1,5,6),(2,1,-5,6),(3,-1,-5,6))], - 'P 3 m 1':[(4.,4.,6.,90.,90.,120.),((0,0,1,2),(1,0,0,6),(1,0,-1,6),(1,0,1,6),(0,0,2,2),(1,0,-2,6), - (1,0,2,6),(1,1,0,6),(0,0,3,2),(1,1,1,12),(1,0,-3,6),(1,0,3,6),(2,0,0,6),(1,1,2,12),(2,0,1,6), - (2,0,-1,6),(0,0,4,2),(2,0,-2,6),(2,0,2,6),(1,1,3,12),(1,0,-4,6),(1,0,4,6),(2,0,3,6),(2,1,0,12), - (2,0,-3,6),(2,1,1,12),(2,1,-1,12),(1,1,4,12),(2,1,2,12),(0,0,5,2),(2,1,-2,12),(3,0,0,6),(1,0,-5,6), - (3,0,1,6),(3,0,-1,6),(1,0,5,6),(2,0,4,6),(2,0,-4,6),(2,1,3,12),(2,1,-3,12),(3,0,-2,6),(3,0,2,6), - (1,1,5,12),(3,0,-3,6),(0,0,6,2),(2,2,0,6),(3,0,3,6),(2,1,4,12),(2,2,1,12),(2,0,5,6),(2,1,-4,12), - (2,0,-5,6),(1,0,-6,6),(1,0,6,6),(3,1,0,12),(3,1,-1,12),(3,1,1,12),(2,2,2,12),(3,1,2,12), - (3,0,4,6),(3,1,-2,12),(3,0,-4,6),(1,1,6,12),(2,2,3,12))], - 'P 3 1 m':[(4.,4.,6.,90.,90.,120.),((0,0,1,2),(1,0,0,6),(0,0,2,2),(1,0,1,12),(1,0,2,12),(1,1,0,6), - (0,0,3,2),(1,1,-1,6),(1,1,1,6),(1,0,3,12),(2,0,0,6),(2,0,1,12),(1,1,2,6),(1,1,-2,6),(2,0,2,12), - (0,0,4,2),(1,1,-3,6),(1,1,3,6),(1,0,4,12),(2,1,0,12),(2,0,3,12),(2,1,1,12),(2,1,-1,12),(1,1,-4,6), - (1,1,4,6),(0,0,5,2),(2,1,-2,12),(2,1,2,12),(3,0,0,6),(1,0,5,12),(2,0,4,12),(3,0,1,12),(2,1,-3,12), - (2,1,3,12),(3,0,2,12),(1,1,5,6),(1,1,-5,6),(3,0,3,12),(0,0,6,2),(2,2,0,6),(2,1,-4,12),(2,0,5,12), - (2,2,-1,6),(2,2,1,6),(2,1,4,12),(3,1,0,12),(1,0,6,12),(2,2,2,6),(3,1,-1,12),(2,2,-2,6),(3,1,1,12), - (3,1,-2,12),(3,0,4,12),(3,1,2,12),(1,1,-6,6),(2,2,3,6),(2,2,-3,6),(1,1,6,6))], - } - - global FLnhTestData - FLnhTestData = [{ - 'C(4,0,0)': (0.965, 0.42760447), - 'C(2,0,0)': (1.0122, -0.80233610), - 'C(2,0,2)': (0.0061, 8.37491546E-03), - 'C(6,0,4)': (-0.0898, 4.37985696E-02), - 'C(6,0,6)': (-0.1369, -9.04081762E-02), - 'C(6,0,0)': (0.5935, -0.18234928), - 'C(4,0,4)': (0.1872, 0.16358127), - 'C(6,0,2)': (0.6193, 0.27573633), - 'C(4,0,2)': (-0.1897, 0.12530720)},[1,0,0]] -def test0(): - if NeedTestData: TestData() - msg = 'test cell2Gmat, fillgmat, Gmat2cell' - for (cell, tg, tG, trcell, tV, trV) in CellTestData: - G, g = cell2Gmat(cell) - assert np.allclose(G,tG),msg - assert np.allclose(g,tg),msg - tcell = Gmat2cell(g) - assert np.allclose(cell,tcell),msg - tcell = Gmat2cell(G) - assert np.allclose(tcell,trcell),msg -selftestlist.append(test0) - -def test1(): - 'test cell2A and A2Gmat' - _ReportTest() - if NeedTestData: TestData() - msg = 'test cell2A and A2Gmat' - for (cell, tg, tG, trcell, tV, trV) in CellTestData: - G, g = A2Gmat(cell2A(cell)) - assert np.allclose(G,tG),msg - assert np.allclose(g,tg),msg -selftestlist.append(test1) - -def test2(): - 'test Gmat2A, A2cell, A2Gmat, Gmat2cell' - _ReportTest() - if NeedTestData: TestData() - msg = 'test Gmat2A, A2cell, A2Gmat, Gmat2cell' - for (cell, tg, tG, trcell, tV, trV) in CellTestData: - G, g = cell2Gmat(cell) - tcell = A2cell(Gmat2A(G)) - assert np.allclose(cell,tcell),msg -selftestlist.append(test2) - -def test3(): - 'test invcell2Gmat' - _ReportTest() - if NeedTestData: TestData() - msg = 'test invcell2Gmat' - for (cell, tg, tG, trcell, tV, trV) in CellTestData: - G, g = invcell2Gmat(trcell) - assert np.allclose(G,tG),msg - assert np.allclose(g,tg),msg -selftestlist.append(test3) - -def test4(): - 'test calc_rVsq, calc_rV, calc_V' - _ReportTest() - if NeedTestData: TestData() - msg = 'test calc_rVsq, calc_rV, calc_V' - for (cell, tg, tG, trcell, tV, trV) in CellTestData: - assert np.allclose(calc_rV(cell2A(cell)),trV), msg - assert np.allclose(calc_V(cell2A(cell)),tV), msg -selftestlist.append(test4) - -def test5(): - 'test A2invcell' - _ReportTest() - if NeedTestData: TestData() - msg = 'test A2invcell' - for (cell, tg, tG, trcell, tV, trV) in CellTestData: - rcell = A2invcell(cell2A(cell)) - assert np.allclose(rcell,trcell),msg -selftestlist.append(test5) - -def test6(): - 'test cell2AB' - _ReportTest() - if NeedTestData: TestData() - msg = 'test cell2AB' - for (cell,coordlist) in CoordTestData: - A,B = cell2AB(cell) - for (frac,ortho) in coordlist: - to = np.inner(A,frac) - tf = np.inner(B,to) - assert np.allclose(ortho,to), msg - assert np.allclose(frac,tf), msg - to = np.sum(A*frac,axis=1) - tf = np.sum(B*to,axis=1) - assert np.allclose(ortho,to), msg - assert np.allclose(frac,tf), msg -selftestlist.append(test6) - -def test7(): - 'test GetBraviasNum() and GenHBravais()' - _ReportTest() - import os.path - import sys - import GSASIIspc as spc - testdir = os.path.join(os.path.split(os.path.abspath( __file__ ))[0],'testinp') - if os.path.exists(testdir): - if testdir not in sys.path: sys.path.insert(0,testdir) - import sgtbxlattinp - derror = 1e-4 - def indexmatch(hklin, hkllist, system): - for hklref in hkllist: - hklref = list(hklref) - # these permutations are far from complete, but are sufficient to - # allow the test to complete - if system == 'cubic': - permlist = [(1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1),] - elif system == 'monoclinic': - permlist = [(1,2,3),(-1,2,-3)] - else: - permlist = [(1,2,3)] - - for perm in permlist: - hkl = [abs(i) * hklin[abs(i)-1] / i for i in perm] - if hkl == hklref: return True - if [-i for i in hkl] == hklref: return True - else: - return False - - for key in sgtbxlattinp.sgtbx7: - spdict = spc.SpcGroup(key) - cell = sgtbxlattinp.sgtbx7[key][0] - system = spdict[1]['SGSys'] - center = spdict[1]['SGLatt'] - - bravcode = GetBraviasNum(center, system) - - g2list = GenHBravais(sgtbxlattinp.dmin, bravcode, cell2A(cell)) - - assert len(sgtbxlattinp.sgtbx7[key][1]) == len(g2list), 'Reflection lists differ for %s' % key - for h,k,l,d,num in g2list: - for hkllist,dref in sgtbxlattinp.sgtbx7[key][1]: - if abs(d-dref) < derror: - if indexmatch((h,k,l,), hkllist, system): - break - else: - assert 0,'No match for %s at %s (%s)' % ((h,k,l),d,key) -selftestlist.append(test7) - -def test8(): - 'test GenHLaue' - _ReportTest() - import GSASIIspc as spc - import sgtbxlattinp - derror = 1e-4 - dmin = sgtbxlattinp.dmin - - def indexmatch(hklin, hklref, system, axis): - # these permutations are far from complete, but are sufficient to - # allow the test to complete - if system == 'cubic': - permlist = [(1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1),] - elif system == 'monoclinic' and axis=='b': - permlist = [(1,2,3),(-1,2,-3)] - elif system == 'monoclinic' and axis=='a': - permlist = [(1,2,3),(1,-2,-3)] - elif system == 'monoclinic' and axis=='c': - permlist = [(1,2,3),(-1,-2,3)] - elif system == 'trigonal': - permlist = [(1,2,3),(2,1,3),(-1,-2,3),(-2,-1,3)] - elif system == 'rhombohedral': - permlist = [(1,2,3),(2,3,1),(3,1,2)] - else: - permlist = [(1,2,3)] - - hklref = list(hklref) - for perm in permlist: - hkl = [abs(i) * hklin[abs(i)-1] / i for i in perm] - if hkl == hklref: return True - if [-i for i in hkl] == hklref: return True - return False - - for key in sgtbxlattinp.sgtbx8: - spdict = spc.SpcGroup(key)[1] - cell = sgtbxlattinp.sgtbx8[key][0] - Axis = spdict['SGUniq'] - system = spdict['SGSys'] - - g2list = GenHLaue(dmin,spdict,cell2A(cell)) - #if len(g2list) != len(sgtbxlattinp.sgtbx8[key][1]): - # print 'failed',key,':' ,len(g2list),'vs',len(sgtbxlattinp.sgtbx8[key][1]) - # print 'GSAS-II:' - # for h,k,l,d in g2list: print ' ',(h,k,l),d - # print 'SGTBX:' - # for hkllist,dref in sgtbxlattinp.sgtbx8[key][1]: print ' ',hkllist,dref - assert len(g2list) == len(sgtbxlattinp.sgtbx8[key][1]), ( - 'Reflection lists differ for %s' % key - ) - #match = True - for h,k,l,d in g2list: - for hkllist,dref in sgtbxlattinp.sgtbx8[key][1]: - if abs(d-dref) < derror: - if indexmatch((h,k,l,), hkllist, system, Axis): break - else: - assert 0,'No match for %s at %s (%s)' % ((h,k,l),d,key) - #match = False - #if not match: - #for hkllist,dref in sgtbxlattinp.sgtbx8[key][1]: print ' ',hkllist,dref - #print center, Laue, Axis, system -selftestlist.append(test8) - -def test9(): - 'test GenHLaue' - _ReportTest() - import GSASIIspc as G2spc - if NeedTestData: TestData() - for spc in LaueTestData: - data = LaueTestData[spc] - cell = data[0] - hklm = np.array(data[1]) - H = hklm[-1][:3] - hklO = hklm.T[:3].T - A = cell2A(cell) - dmin = 1./np.sqrt(calc_rDsq(H,A)) - SGData = G2spc.SpcGroup(spc)[1] - hkls = np.array(GenHLaue(dmin,SGData,A)) - hklN = hkls.T[:3].T - #print spc,hklO.shape,hklN.shape - err = True - for H in hklO: - if H not in hklN: - print ('%d %s'%(H,' missing from hkl from GSASII')) - err = False - assert(err) -selftestlist.append(test9) - - - - -if __name__ == '__main__': - import GSASIIpath - GSASIIpath.SetBinaryPath() - # run self-tests - selftestquiet = False - for test in selftestlist: - test() - print ("OK") diff --git a/GSASII/GSASIImapvars.py b/GSASII/GSASIImapvars.py index 25eee2cd9..79bba879d 100644 --- a/GSASII/GSASIImapvars.py +++ b/GSASII/GSASIImapvars.py @@ -16,8 +16,8 @@ from __future__ import division, print_function import copy import numpy as np -import GSASIIpath -import GSASIIobj as G2obj +from . import GSASIIpath +from . import GSASIIobj as G2obj # data used for constraints; debug = False # turns on printing as constraint input is processed diff --git a/GSASII/GSASIImath.py b/GSASII/GSASIImath.py index 4020aa87d..cf06252af 100644 --- a/GSASII/GSASIImath.py +++ b/GSASII/GSASIImath.py @@ -9,23 +9,35 @@ import numpy as np import numpy.linalg as nl import numpy.ma as ma +import numpy.fft as fft +import scipy.optimize as so import time import math import copy -import GSASIIpath -import GSASIIElem as G2el -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIpwd as G2pwd -import GSASIIobj as G2obj -import GSASIIfiles as G2fil -import numpy.fft as fft -import scipy.optimize as so +from . import GSASIIpath +GSASIIpath.SetBinaryPath() +from . import GSASIIElem as G2el +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIpwd as G2pwd +from . import GSASIIobj as G2obj +from . import GSASIIfiles as G2fil try: - import pypowder as pwd + if GSASIIpath.binaryPath: + import pypowder as pwd + else: + from . import pypowder as pwd except ImportError: print ('pypowder is not available - profile calcs. not allowed') +try: + if GSASIIpath.binaryPath: + import pytexture as ptx + else: + from . import pytexture as ptx + #import GSASII.pytexture as ptx +except ImportError: # ignore; will report this as an error in GSASIIplot import + pass sind = lambda x: np.sin(x*np.pi/180.) cosd = lambda x: np.cos(x*np.pi/180.) tand = lambda x: np.tan(x*np.pi/180.) @@ -41,12 +53,12 @@ except TypeError: pass nxs = np.newaxis - + ################################################################################ #### Hessian least-squares Levenberg-Marquardt routine ################################################################################ class G2NormException(Exception): pass - + def pinv(a, rcond=1e-15 ): ''' Compute the (Moore-Penrose) pseudo-inverse of a matrix. @@ -68,7 +80,7 @@ def pinv(a, rcond=1e-15 ): Raises: LinAlgError If the SVD computation does not converge. - Notes: + Notes: The pseudo-inverse of a matrix A, denoted :math:`A^+`, is defined as: "the matrix that 'solves' [the least-squares problem] :math:`Ax = b`," i.e., if :math:`\\bar{x}` is said solution, then @@ -83,7 +95,7 @@ def pinv(a, rcond=1e-15 ): consisting of the reciprocals of A's singular values (again, followed by zeros). [1] - References: + References: .. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, FL, Academic Press, Inc., 1980, pp. 139-142. ''' @@ -98,7 +110,7 @@ def dropTerms(bad, hessian, indices, *vectors): '''Remove the 'bad' terms from the Hessian and vector :param tuple bad: a list of variable (row/column) numbers that should be - removed from the hessian and vector. Example: (0,3) removes the 1st and + removed from the hessian and vector. Example: (0,3) removes the 1st and 4th column/row :param np.array hessian: a square matrix of length n x n :param np.array indices: the indices of the least-squares vector of length n @@ -106,14 +118,14 @@ def dropTerms(bad, hessian, indices, *vectors): multiple times, more terms may be removed from this list :param additional-args: various least-squares model values, length n - :returns: hessian, indices, vector0, vector1,... where the lengths are + :returns: hessian, indices, vector0, vector1,... where the lengths are now n' x n' and n', with n' = n - len(bad) ''' out = [np.delete(np.delete(hessian,bad,1),bad,0),np.delete(indices,bad)] for v in vectors: out.append(np.delete(v,bad)) return out - + def setHcorr(info,Amat,xtol,problem=False): '''Find & report high correlation terms in covariance matrix ''' @@ -124,7 +136,7 @@ def setHcorr(info,Amat,xtol,problem=False): Bmat,Nzeros = pinv(Amat,xtol) #Moore-Penrose inversion (via SVD) & count of zeros Bmat = Bmat/Anorm sig = np.sqrt(np.diag(Bmat)) - xvar = np.outer(sig,np.ones_like(sig)) + xvar = np.outer(sig,np.ones_like(sig)) AcovUp = abs(np.triu(np.divide(np.divide(Bmat,xvar),xvar.T),1)) # elements above diagonal if Nzeros or problem: # something is wrong, so report what is found m = min(0.99,0.99*np.amax(AcovUp)) @@ -150,7 +162,7 @@ def setSVDwarn(info,Amat,Nzeros,indices): def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=-3,Print=False,refPlotUpdate=None): ''' Minimize the sum of squares of a function (:math:`f`) evaluated on a series of - values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})` + values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})` where :math:`x = arg min(\\sum_{y=0}^{N_{obs}} (func(y)^2,axis=0))` :param function func: callable method or function @@ -163,9 +175,9 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- :param tuple args: Any extra arguments to func are placed in this tuple. :param float ftol: Relative error desired in the sum of squares. :param float xtol: Relative tolerance of zeros in the SVD solution in nl.pinv. - :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine + :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine until other limits are met (ftol, xtol) - :param int lamda: initial Marquardt lambda=10**lamda + :param int lamda: initial Marquardt lambda=10**lamda :param bool Print: True for printing results (residuals & times) by cycle :returns: (x,cov_x,infodict) where @@ -175,14 +187,14 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- call). * cov_x : ndarray Uses the fjac and ipvt optional outputs to construct an - estimate of the jacobian around the solution. This matrix - must be multiplied by the residual standard deviation to get + estimate of the jacobian around the solution. This matrix + must be multiplied by the residual standard deviation to get the covariance of the parameter estimates -- see curve_fit. - - -- or ``None`` if a singular matrix encountered (indicates very + + -- or ``None`` if a singular matrix encountered (indicates very flat curvature in direction), or some other error is encountered. - - -- or an empty array if a refinement was not performed because + + -- or an empty array if a refinement was not performed because no valid variables were refined. * infodict : dict, a dictionary of optional outputs with the keys: @@ -285,6 +297,9 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- M2 = func(x0+XvecAll,*args) except Exception as Msg: if not hasattr(Msg,'msg'): Msg.msg = str(Msg) + #import traceback + #Msg.msg += '\n' + #Msg.msg += traceback.format_exc() G2fil.G2Print(Msg.msg,mode='warn') loops += 1 d = np.abs(np.diag(nl.qr(Amatlam)[1])) @@ -317,7 +332,7 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- if lam > 10.: G2fil.G2Print('ouch #4 stuck: chisq-new %.4g > chisq0 %.4g with lambda %.1g'% (chisq1,chisq0,lam), mode='warn') - if GSASIIpath.GetConfigValue('debug'): + if GSASIIpath.GetConfigValue('debug'): print('Cycle %d: %.2fs' % (icycle,time.time()-time0)) try: # report highly correlated parameters from full Hessian, if we can info = {'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax, @@ -356,7 +371,7 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- if refPlotUpdate is not None: refPlotUpdate(Histograms,icycle) # update plot if deltaChi2 < ftol: ifConverged = True - if Print: + if Print: G2fil.G2Print("converged") break icycle += 1 @@ -422,7 +437,7 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- G2fil.G2Print('ouch #6 linear algebra error in making final v-cov matrix', mode='error') psing = list(np.where(np.abs(np.diag(nl.qr(Amat)[1])) < 1.e-14)[0]) if not len(psing): # make sure at least the worst term is flagged - d = np.abs(np.diag(nl.qr(Amat)[1])) + d = np.abs(np.diag(nl.qr(Amat)[1])) psing = [np.argmin(d)] Amat, indices, Yvec = dropTerms(psing, Amat, indices, Yvec) info = {'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'SVD0':-1,'Xvec':None, 'chisq0':chisqf} @@ -442,12 +457,12 @@ def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- info['lastShifts'] = lastShifts setSVDwarn(info,Amat,Nzeros,indices) return [x0,Bmat,info] - + def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=-3,Print=False,refPlotUpdate=None): - + ''' Minimize the sum of squares of a function (:math:`f`) evaluated on a series of - values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})` + values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})` where :math:`x = arg min(\\sum_{y=0}^{N_{obs}} (func(y)^2,axis=0))` :param function func: callable method or function @@ -460,7 +475,7 @@ def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- :param tuple args: Any extra arguments to func are placed in this tuple. :param float ftol: Relative error desired in the sum of squares. :param float xtol: Relative tolerance of zeros in the SVD solution in nl.pinv. - :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine + :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine until other limits are met (ftol, xtol) :param bool Print: True for printing results (residuals & times) by cycle @@ -485,16 +500,16 @@ def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- * 'lamMax':0. * 'psing': * 'SVD0': - + ''' - + ifConverged = False deltaChi2 = -10. x0 = np.array(x0, ndmin=1) #might be redundant? n = len(x0) if type(args) != type(()): args = (args,) - + icycle = 0 nfev = 0 if Print: @@ -543,7 +558,7 @@ def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- Yvec,Amat = Hess(x0,*args) Adiag = np.sqrt(np.diag(Amat)) Anorm = np.outer(Adiag,Adiag) - Amat = Amat/Anorm + Amat = Amat/Anorm try: Bmat,Nzero = pinv(Amat,xtol) #Moore-Penrose inversion (via SVD) & count of zeros G2fil.G2Print('Found %d SVD zeros'%(Nzero), mode='warn') @@ -559,19 +574,19 @@ def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=- psing = list(np.where(np.diag(nl.qr(Amat)[1]) < 1.e-14)[0]) return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1, 'chisq0':chisq00}] - + def getVCov(varyNames,varyList,covMatrix): - '''obtain variance-covariance terms for a set of variables. NB: the varyList + '''obtain variance-covariance terms for a set of variables. NB: the varyList and covMatrix were saved by the last least squares refinement so they must match. - + :param list varyNames: variable names to find v-cov matric for :param list varyList: full list of all variables in v-cov matrix - :param nparray covMatrix: full variance-covariance matrix from the last + :param nparray covMatrix: full variance-covariance matrix from the last least squares refinement - + :returns: nparray vcov: variance-covariance matrix for the variables given in varyNames - + ''' vcov = np.zeros((len(varyNames),len(varyNames))) for i1,name1 in enumerate(varyNames): @@ -582,10 +597,10 @@ def getVCov(varyNames,varyList,covMatrix): vcov[i1][i2] = 0.0 # if i1 == i2: # vcov[i1][i2] = 1e-20 -# else: +# else: # vcov[i1][i2] = 0.0 return vcov - + ################################################################################ #### Atom manipulations ################################################################################ @@ -593,7 +608,7 @@ def getVCov(varyNames,varyList,covMatrix): def getAtomPtrs(data,draw=False): ''' get atom data pointers cx,ct,cs,cia in Atoms or Draw Atoms lists NB:may not match column numbers in displayed table - + param: dict: data phase data structure draw: boolean True if Draw Atoms list pointers are required return: cx,ct,cs,cia pointers to atom xyz, type, site sym, uiso/aniso flag @@ -626,7 +641,7 @@ def getNeighbors(atom,radius): indH = atomTypes.index('H') radii[indH] = 0.5 except: - pass + pass nAtom = len(atomData) Indx = list(range(nAtom)) UAtoms = [] @@ -684,19 +699,19 @@ def getNeighbors(atom,radius): if atom != None: newAtoms.append(atom) return newAtoms - + def FindAtomIndexByIDs(atomData,loc,IDs,Draw=True): - '''finds the set of atom array indices for a list of atom IDs. Will search + '''finds the set of atom array indices for a list of atom IDs. Will search either the Atom table or the drawAtom table. - + :param list atomData: Atom or drawAtom table containting coordinates, etc. :param int loc: location of atom id in atomData record :param list IDs: atom IDs to be found :param bool Draw: True if drawAtom table to be searched; False if Atom table is searched - + :returns: list indx: atom (or drawAtom) indices - + ''' indx = [] for i,atom in enumerate(atomData): @@ -708,12 +723,12 @@ def FindAtomIndexByIDs(atomData,loc,IDs,Draw=True): def FillAtomLookUp(atomData,indx): '''create a dictionary of atom indexes with atom IDs as keys - + :param list atomData: Atom table to be used :param int indx: pointer to position of atom id in atom record (typically cia+8) - + :returns: dict atomLookUp: dictionary of atom indexes with atom IDs as keys - + ''' return {atom[indx]:iatm for iatm,atom in enumerate(atomData)} @@ -737,7 +752,7 @@ def MakeDrawAtom(data,atom,oldatom=None): if generalData['Type'] in ['nuclear','faulted',]: if oldatom: opr = oldatom[5] - if atom[9] == 'A': + if atom[9] == 'A': X,U = G2spc.ApplyStringOps(opr,SGData,atom[3:6],atom[11:17]) atomInfo = [atom[:2]+list(X)+oldatom[5:9]+atom[9:11]+list(U)+oldatom[17:]][0] else: @@ -756,7 +771,7 @@ def MakeDrawAtom(data,atom,oldatom=None): Mom = G2spc.ApplyStringOpsMom(opr,SGData,SSGData,mom) else: Mom = G2spc.ApplyStringOpsMom(opr,SGData,None,mom) - if atom[12] == 'A': + if atom[12] == 'A': X,U = G2spc.ApplyStringOps(opr,SGData,atom[3:6],atom[14:20]) atomInfo = [atom[:2]+list(X)+list(Mom)+oldatom[8:12]+atom[12:14]+list(U)+oldatom[20:]][0] else: @@ -778,16 +793,16 @@ def MakeDrawAtom(data,atom,oldatom=None): atNum = generalData['AtomTypes'].index(atom[ct]) atomInfo[cs] = list(generalData['Color'][atNum]) return atomInfo - + def GetAtomsById(atomData,atomLookUp,IdList): '''gets a list of atoms from Atom table that match a set of atom IDs - + :param list atomData: Atom table to be used :param dict atomLookUp: dictionary of atom indexes with atom IDs as keys :param list IdList: atom IDs to be found - + :returns: list atoms: list of atoms found - + ''' atoms = [] for Id in IdList: @@ -795,18 +810,18 @@ def GetAtomsById(atomData,atomLookUp,IdList): continue atoms.append(atomData[atomLookUp[Id]]) return atoms - + def GetAtomItemsById(atomData,atomLookUp,IdList,itemLoc,numItems=1): '''gets atom parameters for atoms using atom IDs - + :param list atomData: Atom table to be used :param dict atomLookUp: dictionary of atom indexes with atom IDs as keys :param list IdList: atom IDs to be found :param int itemLoc: pointer to desired 1st item in an atom table entry :param int numItems: number of items to be retrieved - + :returns: type name: description - + ''' Items = [] if not isinstance(IdList,list): @@ -819,14 +834,14 @@ def GetAtomItemsById(atomData,atomLookUp,IdList,itemLoc,numItems=1): else: Items.append(atomData[atomLookUp[Id]][itemLoc:itemLoc+numItems]) return Items - + def GetAtomCoordsByID(pId,parmDict,AtLookup,indx): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' pfx = [str(pId)+'::A'+i+':' for i in ['x','y','z']] dpfx = [str(pId)+'::dA'+i+':' for i in ['x','y','z']] @@ -836,14 +851,14 @@ def GetAtomCoordsByID(pId,parmDict,AtLookup,indx): dnames = [dpfx[i]+str(AtLookup[ind]) for i in range(3)] XYZ.append([parmDict[name]+parmDict[dname] for name,dname in zip(names,dnames)]) return XYZ - + def GetAtomFracByID(pId,parmDict,AtLookup,indx): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' pfx = str(pId)+'::Afrac:' Frac = [] @@ -851,20 +866,20 @@ def GetAtomFracByID(pId,parmDict,AtLookup,indx): name = pfx+str(AtLookup[ind]) Frac.append(parmDict[name]) return Frac - + # for Atom in Atoms: # XYZ = Atom[cx:cx+3] # if 'A' in Atom[cia]: # U6 = Atom[cia+2:cia+8] - + def GetAtomMomsByID(pId,parmDict,AtLookup,indx): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' pfx = [str(pId)+'::A'+i+':' for i in ['Mx','My','Mz']] Mom = [] @@ -872,14 +887,14 @@ def GetAtomMomsByID(pId,parmDict,AtLookup,indx): names = [pfx[i]+str(AtLookup[ind]) for i in range(3)] Mom.append([parmDict[name] for name in names]) return Mom - + def ApplySeqData(data,seqData,PF2=False): '''Applies result from seq. refinement to drawing atom positions & Uijs - + :param dict data: GSAS-II phase data structure :param dict seqData: GSAS-II sequential refinement results structure :param bool PF2: if True then seqData is from a sequential run of PDFfit2 - + :returns: list drawAtoms: revised Draw Atoms list ''' cx,ct,cs,cia = getAtomPtrs(data) @@ -929,7 +944,7 @@ def ApplySeqData(data,seqData,PF2=False): drawatom = drawAtoms[ind] opr = drawatom[dcs-1] #how do I handle Sfrac? - fade the atoms? - if atom[cia] == 'A': + if atom[cia] == 'A': X,U = G2spc.ApplyStringOps(opr,SGData,atxyz,atuij) drawatom[dcx:dcx+3] = X drawatom[dci-6:dci] = U @@ -938,7 +953,7 @@ def ApplySeqData(data,seqData,PF2=False): drawatom[dcx:dcx+3] = X drawatom[dci-7] = atuiso return drawAtoms - + def FindOctahedron(results): Octahedron = np.array([[1.,0,0],[0,1.,0],[0,0,1.],[-1.,0,0],[0,-1.,0],[0,0,-1.]]) Polygon = np.array([result[3] for result in results]) @@ -955,7 +970,7 @@ def FindOctahedron(results): jAng = np.argmin(Rots) Qbvec = np.cross(Norms[jAng],Octahedron[1]) QB = AVdeg2Q(Rots[jAng],Qbvec) - QQ = prodQQ(QA,QB) + QQ = prodQQ(QA,QB) newNorms = prodQVQ(QQ,Norms) dispVecs = np.array([norm[:,nxs]-Octahedron.T for norm in newNorms]) disp = np.sqrt(np.sum(dispVecs**2,axis=1)) @@ -966,7 +981,7 @@ def FindOctahedron(results): stdDisp = np.std(Disps) A,V = Q2AVdeg(QQ) return bond,std,meanDisp,stdDisp,A,V,vecDisp - + def FindTetrahedron(results): Tetrahedron = np.array([[1.,1,1],[1,-1,-1],[-1,1,-1],[-1,-1,1]])/np.sqrt(3.) Polygon = np.array([result[3] for result in results]) @@ -983,7 +998,7 @@ def FindTetrahedron(results): jAng = np.argmin(Rots) Qbvec = np.cross(Norms[jAng],Tetrahedron[1]) QB = AVdeg2Q(Rots[jAng],Qbvec) - QQ = prodQQ(QA,QB) + QQ = prodQQ(QA,QB) newNorms = prodQVQ(QQ,Norms) dispVecs = np.array([norm[:,nxs]-Tetrahedron.T for norm in newNorms]) disp = np.sqrt(np.sum(dispVecs**2,axis=1)) @@ -994,7 +1009,7 @@ def FindTetrahedron(results): stdDisp = np.std(Disps) A,V = Q2AVdeg(QQ) return bond,std,meanDisp,stdDisp,A,V,vecDisp - + def FindNeighbors(phase,FrstName,AtNames,notName=''): General = phase['General'] cx,ct,cs,cia = getAtomPtrs(phase) @@ -1005,7 +1020,7 @@ def FindNeighbors(phase,FrstName,AtNames,notName=''): atTypes = General['AtomTypes'] Radii = np.array(General['BondRadii']) try: - DisAglCtls = General['DisAglCtls'] + DisAglCtls = General['DisAglCtls'] radiusFactor = DisAglCtls['Factors'][0] except: radiusFactor = 0.85 @@ -1013,7 +1028,7 @@ def FindNeighbors(phase,FrstName,AtNames,notName=''): Orig = atNames.index(FrstName) OId = Atoms[Orig][cia+8] OType = Atoms[Orig][ct] - XYZ = getAtomXYZ(Atoms,cx) + XYZ = getAtomXYZ(Atoms,cx) Neigh = [] Ids = [] Dx = np.inner(Amat,XYZ-XYZ[Orig]).T @@ -1050,7 +1065,7 @@ def FindAllNeighbors(phase,FrstName,AtNames,notName='',Orig=None,Short=False,sea AtTypes = General['AtomTypes'] Radii = copy.copy(np.array(General[skey])) try: - DisAglCtls = General['DisAglCtls'] + DisAglCtls = General['DisAglCtls'] radiusFactor = DisAglCtls['Factors'][sindex] Radii = DisAglCtls[skey] except: @@ -1060,7 +1075,7 @@ def FindAllNeighbors(phase,FrstName,AtNames,notName='',Orig=None,Short=False,sea Orig = atNames.index(FrstName) OId = Atoms[Orig][cia+8] OType = Atoms[Orig][ct] - XYZ = getAtomXYZ(Atoms,cx) + XYZ = getAtomXYZ(Atoms,cx) Oxyz = XYZ[Orig] Neigh = [] Ids = [] @@ -1092,7 +1107,7 @@ def FindAllNeighbors(phase,FrstName,AtNames,notName='',Orig=None,Short=False,sea Neigh.append([AtNames[iA]+Topstr,atTypes[iA],dist[iU],dx[iU]]) Ids.append(Atoms[iA][cia+8]) return Neigh,[OId,Ids] - + def calcBond(A,Ax,Bx,MTCU): cell = G2lat.A2cell(A) Amat,Bmat = G2lat.cell2AB(cell) @@ -1101,17 +1116,17 @@ def calcBond(A,Ax,Bx,MTCU): Dx = Btx-Ax dist = np.sqrt(np.inner(Amat,Dx)) return dist - + def AddHydrogens(AtLookUp,General,Atoms,AddHydId): - + def getTransMat(RXYZ,OXYZ,TXYZ,Amat): - Vec = np.inner(Amat,np.array([OXYZ-TXYZ[0],RXYZ-TXYZ[0]])).T + Vec = np.inner(Amat,np.array([OXYZ-TXYZ[0],RXYZ-TXYZ[0]])).T Vec /= np.sqrt(np.sum(Vec**2,axis=1))[:,nxs] Mat2 = np.cross(Vec[0],Vec[1]) #UxV Mat2 /= np.sqrt(np.sum(Mat2**2)) Mat3 = np.cross(Mat2,Vec[0]) #(UxV)xU - return nl.inv(np.array([Vec[0],Mat2,Mat3])) - + return nl.inv(np.array([Vec[0],Mat2,Mat3])) + cx,ct,cs,cia = General['AtomPtrs'] Cell = General['Cell'][1:7] Amat,Bmat = G2lat.cell2AB(Cell) @@ -1156,10 +1171,10 @@ def getTransMat(RXYZ,OXYZ,TXYZ,Amat): Hpos = np.array([[a,0.,-b],[a,-b*cosd(30.),0.5*b],[a,b*cosd(30.),0.5*b]]) Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ HU = 1.5*Uiso*np.ones(3) - return Hpos,HU + return Hpos,HU elif nBonds == 3: if AddHydId[-1] == 1: - Vec = np.sum(TXYZ-OXYZ,axis=0) + Vec = np.sum(TXYZ-OXYZ,axis=0) Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2)) Vec = -0.93*Vec/Len Hpos = OXYZ+Vec @@ -1198,27 +1213,27 @@ def getTransMat(RXYZ,OXYZ,TXYZ,Amat): HU = 1.5*Uiso return [Hpos[imax],],[HU,] return [],[] - + #def AtomUij2TLS(atomData,atPtrs,Amat,Bmat,rbObj): #unfinished & not used # '''default doc string -# +# # :param type name: description -# +# # :returns: type name: description -# +# # ''' # for atom in atomData: # XYZ = np.inner(Amat,atom[cx:cx+3]) # if atom[cia] == 'A': # UIJ = atom[cia+2:cia+8] - + def TLS2Uij(xyz,g,Amat,rbObj): #not used anywhere, but could be? '''default doc string - + :param type name: description - + :returns: type name: description - + ''' TLStype,TLS = rbObj['ThermalMotion'][:2] Tmat = np.zeros((3,3)) @@ -1236,15 +1251,15 @@ def TLS2Uij(xyz,g,Amat,rbObj): #not used anywhere, but could be? Axyz = np.array([[ 0,XYZ[2],-XYZ[1]], [-XYZ[2],0,XYZ[0]], [XYZ[1],-XYZ[0],0]] ) Umat = Tmat+np.inner(Axyz,Smat)+np.inner(Smat.T,Axyz.T)+np.inner(np.inner(Axyz,Lmat),Axyz.T) beta = np.inner(np.inner(g,Umat),g) - return G2lat.UijtoU6(beta)*gvec - + return G2lat.UijtoU6(beta)*gvec + def AtomTLS2UIJ(atomData,atPtrs,Amat,rbObj): #not used anywhere, but could be? '''default doc string - + :param type name: description - + :returns: type name: description - + ''' cx,ct,cs,cia = atPtrs TLStype,TLS = rbObj['ThermalMotion'][:2] @@ -1272,23 +1287,23 @@ def AtomTLS2UIJ(atomData,atPtrs,Amat,rbObj): #not used anywhere, but could be atom[cia+2:cia+8] = G2spc.U2Uij(beta/gvec) def GetXYZDist(xyz,XYZ,Amat): - '''gets distance from position xyz to all XYZ, xyz & XYZ are np.array + '''gets distance from position xyz to all XYZ, xyz & XYZ are np.array and are in crystal coordinates; Amat is crystal to Cart matrix - + :param type name: description - + :returns: type name: description - + ''' return np.sqrt(np.sum(np.inner(Amat,XYZ-xyz)**2,axis=0)) def getAtomXYZ(atoms,cx): '''Create an array of fractional coordinates from the atoms list - + :param list atoms: atoms object as found in tree :param int cx: offset to where coordinates are found - - :returns: np.array with shape (n,3) + + :returns: np.array with shape (n,3) ''' XYZ = [] for atom in atoms: @@ -1297,28 +1312,28 @@ def getAtomXYZ(atoms,cx): def getRBTransMat(X,Y): '''Get transformation for Cartesian axes given 2 vectors - X will be parallel to new X-axis; X cross Y will be new Z-axis & + X will be parallel to new X-axis; X cross Y will be new Z-axis & (X cross Y) cross Y will be new Y-axis Useful for rigid body axes definintion - + :param array X: normalized vector :param array Y: normalized vector - + :returns: array M: transformation matrix - + use as XYZ' = np.inner(M,XYZ) where XYZ are Cartesian - + ''' Mat2 = np.cross(X,Y) #UxV-->Z Mat2 /= np.sqrt(np.sum(Mat2**2)) Mat3 = np.cross(Mat2,X) #(UxV)xU-->Y Mat3 /= np.sqrt(np.sum(Mat3**2)) - return np.array([X,Mat3,Mat2]) - + return np.array([X,Mat3,Mat2]) + def RotateRBXYZ(Bmat,Cart,oriQ,symAxis=None): '''rotate & transform cartesian coordinates to crystallographic ones - no translation applied. To be used for numerical derivatives - + no translation applied. To be used for numerical derivatives + :param array Bmat: Orthogonalization matrix, see :func:`GSASIIlattice.cell2AB` :param array Cart: 2D array of coordinates :param array Q: quaternion as an np.array @@ -1341,17 +1356,17 @@ def RotateRBXYZ(Bmat,Cart,oriQ,symAxis=None): return XYZ def UpdateRBXYZ(Bmat,RBObj,RBData,RBType): - '''returns crystal coordinates for atoms described by RBObj. + '''returns crystal coordinates for atoms described by RBObj. Note that RBObj['symAxis'], if present, determines the symmetry - axis of the rigid body, which will be aligned to the + axis of the rigid body, which will be aligned to the quaternion direction. - + :param np.array Bmat: see :func:`GSASIIlattice.cell2AB` :param dict rbObj: rigid body selection/orientation information :param dict RBData: rigid body tree data structure :param str RBType: rigid body type, 'Vector' or 'Residue' - :returns: coordinates for rigid body as XYZ,Cart where XYZ is + :returns: coordinates for rigid body as XYZ,Cart where XYZ is the location in crystal coordinates and Cart is in cartesian ''' if RBType == 'Vector': @@ -1393,11 +1408,11 @@ def GetSpnRBData(SpnRB,atId): def UpdateMCSAxyz(Bmat,MCSA): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' xyz = [] atTypes = [] @@ -1431,14 +1446,14 @@ def UpdateMCSAxyz(Bmat,MCSA): atTypes.append(atType) iatm += 1 return np.array(xyz),atTypes - + def SetMolCent(model,RBData): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' rideList = [] RBRes = RBData[model['Type']][model['RBId']] @@ -1456,19 +1471,19 @@ def SetMolCent(model,RBData): cent = np.zeros(3) for i in centList: cent += Cart[i] - model['MolCent'][0] = cent/len(centList) - + model['MolCent'][0] = cent/len(centList) + ############################################################################### #### Various utilities ############################################################################### - + def UpdateRBUIJ(Bmat,Cart,RBObj): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' ''' returns atom I/A, Uiso or UIJ for atoms at XYZ as described by RBObj ''' @@ -1508,57 +1523,57 @@ def UpdateRBUIJ(Bmat,Cart,RBObj): else: Uout.append(['N',]) return Uout - + def GetSHCoeff(pId,parmDict,SHkeys): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' SHCoeff = {} for shkey in SHkeys: shname = str(pId)+'::'+shkey SHCoeff[shkey] = parmDict[shname] return SHCoeff - + def getMass(generalData): '''Computes mass of unit cell contents - + :param dict generalData: The General dictionary in Phase - + :returns: float mass: Crystal unit cell mass in AMU. - + ''' mass = 0. for i,elem in enumerate(generalData['AtomTypes']): mass += generalData['NoAtoms'][elem]*generalData['AtomMass'][i] - return max(mass,1.0) + return max(mass,1.0) def getDensity(generalData): '''calculate crystal structure density - + :param dict generalData: The General dictionary in Phase - + :returns: float density: crystal density in gm/cm^3 - + ''' mass = getMass(generalData) Volume = generalData['Cell'][7] density = mass/(0.6022137*Volume) return density,Volume/mass - + def phaseContents(phase): '''Compute the unit cell and asymmetric unit contents for a phase. - This has been tested only on type='nuclear' phases and - might need adaptation for phases of other types, if the - phase type does not have an occupancy defined. + This has been tested only on type='nuclear' phases and + might need adaptation for phases of other types, if the + phase type does not have an occupancy defined. - :param dict phase: the dict for a phase, as found in the - data tree - :returns: acomp,ccomp where acomp is the asymmetric unit contents and + :param dict phase: the dict for a phase, as found in the + data tree + :returns: acomp,ccomp where acomp is the asymmetric unit contents and ccomp is the contents of the unit cell ''' generalData = phase['General'] @@ -1567,8 +1582,8 @@ def phaseContents(phase): acomp = {} # contents of asymmetric unit for atom in phase['Atoms']: if atom[ct] not in ccomp: - ccomp[atom[ct]] = 0 - acomp[atom[ct]] = 0 + ccomp[atom[ct]] = 0 + acomp[atom[ct]] = 0 ccomp[atom[ct]] += atom[cs+1]*atom[cx+3] acomp[atom[ct]] += atom[cx+3] return acomp,ccomp @@ -1581,33 +1596,33 @@ def fmtPhaseContents(compdict): def getWave(Parms): '''returns wavelength from Instrument parameters dictionary - + :param dict Parms: Instrument parameters; must contain: Lam: single wavelength or Lam1: Ka1 radiation wavelength - + :returns: float wave: wavelength - + ''' try: return Parms['Lam'][1] except KeyError: return Parms['Lam1'][1] - + def getMeanWave(Parms): '''returns mean wavelength from Instrument parameters dictionary - + :param dict Parms: Instrument parameters; must contain: Lam: single wavelength or Lam1,Lam2: Ka1,Ka2 radiation wavelength I(L2)/I(L1): Ka2/Ka1 ratio - + :returns: float wave: mean wavelength - + ''' try: return Parms['Lam'][1] @@ -1615,88 +1630,88 @@ def getMeanWave(Parms): meanLam = (Parms['Lam1'][1]+Parms['I(L2)/I(L1)'][1]*Parms['Lam2'][1])/ \ (1.+Parms['I(L2)/I(L1)'][1]) return meanLam - - + + def El2Mass(Elements): - '''compute molecular weight from Elements - - :param dict Elements: elements in molecular formula; - each must contain + '''compute molecular weight from Elements + + :param dict Elements: elements in molecular formula; + each must contain Num: number of atoms in formula - Mass: at. wt. - + Mass: at. wt. + :returns: float mass: molecular weight. - + ''' mass = 0 for El in Elements: mass += Elements[El]['Num']*Elements[El]['Mass'] return mass - + def Den2Vol(Elements,density): '''converts density to molecular volume - - :param dict Elements: elements in molecular formula; - each must contain + + :param dict Elements: elements in molecular formula; + each must contain Num: number of atoms in formula - Mass: at. wt. + Mass: at. wt. :param float density: material density in gm/cm^3 - - :returns: float volume: molecular volume in A^3 - + + :returns: float volume: molecular volume in A^3 + ''' return El2Mass(Elements)/(density*0.6022137) - + def Vol2Den(Elements,volume): '''converts volume to density - - :param dict Elements: elements in molecular formula; - each must contain + + :param dict Elements: elements in molecular formula; + each must contain Num: number of atoms in formula - Mass: at. wt. + Mass: at. wt. :param float volume: molecular volume in A^3 - + :returns: float density: material density in gm/cm^3 - + ''' return El2Mass(Elements)/(volume*0.6022137) - + def El2EstVol(Elements): '''Estimate volume from molecular formula; assumes atom volume = 10A^3 - - :param dict Elements: elements in molecular formula; - each must contain + + :param dict Elements: elements in molecular formula; + each must contain Num: number of atoms in formula - + :returns: float volume: estimate of molecular volume in A^3 - + ''' vol = 0 for El in Elements: vol += 10.*Elements[El]['Num'] return vol - + def XScattDen(Elements,vol,wave=0.): '''Estimate X-ray scattering density from molecular formula & volume; ignores valence, but includes anomalous effects - - :param dict Elements: elements in molecular formula; - each element must contain + + :param dict Elements: elements in molecular formula; + each element must contain Num: number of atoms in formula Z: atomic number :param float vol: molecular volume in A^3 :param float wave: optional wavelength in A - - :returns: float rho: scattering density in 10^10cm^-2; + + :returns: float rho: scattering density in 10^10cm^-2; if wave > 0 the includes f' contribution :returns: float mu: if wave>0 absorption coeff in cm^-1 ; otherwise 0 :returns: float fpp: if wave>0 f" in 10^10cm^-2; otherwise 0 - + ''' rho = 0 mu = 0 fpp = 0 - if wave: + if wave: Xanom = XAnomAbs(Elements,wave) for El in Elements: f0 = Elements[El]['Z'] @@ -1706,23 +1721,23 @@ def XScattDen(Elements,vol,wave=0.): mu += Xanom[El][2]*Elements[El]['Num'] rho += Elements[El]['Num']*f0 return 28.179*rho/vol,mu/vol,28.179*fpp/vol - + def NCScattDen(Elements,vol,wave=0.): '''Estimate neutron scattering density from molecular formula & volume; ignores valence, but includes anomalous effects - - :param dict Elements: elements in molecular formula; - each element must contain + + :param dict Elements: elements in molecular formula; + each element must contain Num: number of atoms in formula Z: atomic number :param float vol: molecular volume in A^3 :param float wave: optional wavelength in A - - :returns: float rho: scattering density in 10^10cm^-2; + + :returns: float rho: scattering density in 10^10cm^-2; if wave > 0 the includes f' contribution :returns: float mu: if wave>0 absorption coeff in cm^-1 ; otherwise 0 :returns: float fpp: if wave>0 f" in 10^10cm^-2; otherwise 0 - + ''' rho = 0 mu = 0 @@ -1747,17 +1762,17 @@ def NCScattDen(Elements,vol,wave=0.): rho += Elements[El]['Num']*b0 if wave: mu *= wave return 100.*rho/vol,mu/vol,100.*bpp/vol - + def wavekE(wavekE): '''Convert wavelength to energy & vise versa - + :param float waveKe:wavelength in A or energy in kE - + :returns float waveKe:the other one - + ''' return 12.397639/wavekE - + def XAnomAbs(Elements,wave): kE = wavekE(wave) Xanom = {} @@ -1765,7 +1780,7 @@ def XAnomAbs(Elements,wave): Orbs = G2el.GetXsectionCoeff(El) Xanom[El] = G2el.FPcalc(Orbs, kE) return Xanom #f',f", mu - + ################################################################################ #### Modulation math ################################################################################ @@ -1777,7 +1792,7 @@ def makeWaves(waveTypes,FSSdata,XSSdata,USSdata,MSSdata,Mast): XSSdata: array 2x3 x atoms X waves (sin,cos terms) USSdata: array 2x6 x atoms X waves (sin,cos terms) MSSdata: array 2x3 x atoms X waves (sin,cos terms) - + Mast: array orthogonalization matrix for Uij ''' ngl = 36 #selected for integer steps for 1/6,1/4,1/3... @@ -1790,14 +1805,14 @@ def makeWaves(waveTypes,FSSdata,XSSdata,USSdata,MSSdata,Mast): Bu = Mast*np.array(G2lat.U6toUij(USSdata[6:])).T #...cos Uij mods as betaij Am = np.array(MSSdata[:3]).T #atoms x waves x sin pos mods Bm = np.array(MSSdata[3:]).T #...cos pos mods - nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1],Am.shape[1]] + nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1],Am.shape[1]] if nWaves[0]: tauF = np.arange(1.,nWaves[0]+1)[:,nxs]*glTau #Fwaves x ngl FmodA = Af[:,:,nxs]*np.sin(twopi*tauF[nxs,:,:]) #atoms X Fwaves X ngl FmodB = Bf[:,:,nxs]*np.cos(twopi*tauF[nxs,:,:]) Fmod = np.sum(1.0+FmodA+FmodB,axis=1) #atoms X ngl; sum waves else: - Fmod = 1.0 + Fmod = 1.0 XmodZ = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) XmodA = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) XmodB = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) @@ -1805,16 +1820,16 @@ def makeWaves(waveTypes,FSSdata,XSSdata,USSdata,MSSdata,Mast): nx = 0 if 'ZigZag' in waveTypes[iatm]: nx = 1 - Tmm = Ax[iatm][0][:2] + Tmm = Ax[iatm][0][:2] XYZmax = np.array([Ax[iatm][0][2],Bx[iatm][0][0],Bx[iatm][0][1]]) XmodZ[iatm][0] += posZigZag(glTau,Tmm,XYZmax).T elif 'Block' in waveTypes[iatm]: nx = 1 - Tmm = Ax[iatm][0][:2] + Tmm = Ax[iatm][0][:2] XYZmax = np.array([Ax[iatm][0][2],Bx[iatm][0][0],Bx[iatm][0][1]]) XmodZ[iatm][0] += posBlock(glTau,Tmm,XYZmax).T tauX = np.arange(1.,nWaves[1]+1-nx)[:,nxs]*glTau #Xwaves x ngl - if nx: + if nx: XmodA[iatm][:-nx] = Ax[iatm,nx:,:,nxs]*np.sin(twopi*tauX[nxs,:,nxs,:]) #atoms X waves X 3 X ngl XmodB[iatm][:-nx] = Bx[iatm,nx:,:,nxs]*np.cos(twopi*tauX[nxs,:,nxs,:]) #ditto else: @@ -1841,7 +1856,7 @@ def makeWaves(waveTypes,FSSdata,XSSdata,USSdata,MSSdata,Mast): def MagMod(glTau,xyz,modQ,MSSdata,SGData,SSGData): ''' - this needs to make magnetic moment modulations & magnitudes as + this needs to make magnetic moment modulations & magnitudes as fxn of gTau points; NB: this allows only 1 mag. wave fxn. ''' Am = np.array(MSSdata[3:]).T[:,0,:] #atoms x cos mag mods; only 1 wave used @@ -1862,7 +1877,7 @@ def MagMod(glTau,xyz,modQ,MSSdata,SGData,SSGData): SGT = np.vstack((SGT,SGT+np.array([0.,0.,0.,.5])))%1. XYZ = np.array([(np.inner(xyzi,SGMT)+SGT[:,:3])%1. for xyzi in xyz.T]) #Natn,Nop,xyz AMR = np.swapaxes(np.inner(Am,SGMT),0,1) #Nops,Natm,Mxyz - BMR = np.swapaxes(np.inner(Bm,SGMT),0,1) + BMR = np.swapaxes(np.inner(Bm,SGMT),0,1) phi = np.inner(xyz.T,modQ)+(np.inner(SGT[:,:3],modQ)[:,nxs]-SGT[:,3,nxs])*glTau[:,nxs,nxs] psin = np.sin(twopi*phi) #tau,ops,atms pcos = np.cos(twopi*phi) @@ -1871,7 +1886,7 @@ def MagMod(glTau,xyz,modQ,MSSdata,SGData,SSGData): MmodAI = AMR[nxs,:,:,:]*psin[:,:,:,nxs] #Im sin term MmodBI = BMR[nxs,:,:,:]*pcos[:,:,:,nxs] #Im cos term return XYZ,MmodAR,MmodBR,MmodAI,MmodBI #Ntau,Nops,Natm,Mxyz; Re, Im cos & sin parts - + def Modulation(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): ''' H: array nRefBlk x ops X hklt @@ -1882,7 +1897,7 @@ def Modulation(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): Umod: array atoms x 3x3 x ngl glTau,glWt: arrays Gauss-Lorentzian pos & wts ''' - + if nWaves[2]: #uij (adp) waves if len(HP.shape) > 2: HbH = np.exp(-np.sum(HP[:,:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.? @@ -1900,7 +1915,7 @@ def Modulation(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): cosHA = np.sum(Fmod*HbH*np.cos(HdotXD)*glWt,axis=-1) #real part; refBlk X ops x atoms; sum for G-L integration sinHA = np.sum(Fmod*HbH*np.sin(HdotXD)*glWt,axis=-1) #imag part; ditto return np.array([cosHA,sinHA]) # 2 x refBlk x SGops x atoms - + def ModulationTw(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): ''' H: array nRefBlk x tw x ops X hklt @@ -1910,7 +1925,7 @@ def ModulationTw(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): Umod: array atoms x ngl x 3x3 glTau,glWt: arrays Gauss-Lorentzian pos & wts ''' - + if nWaves[2]: if len(HP.shape) > 3: #Blocks of reflections HbH = np.exp(-np.sum(HP[:,:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.? @@ -1928,7 +1943,7 @@ def ModulationTw(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): cosHA = np.sum(Fmod*HbH*np.cos(HdotXD)*glWt,axis=-1) #real part; refBlk X ops x atoms; sum for G-L integration sinHA = np.sum(Fmod*HbH*np.sin(HdotXD)*glWt,axis=-1) #imag part; ditto return np.array([cosHA,sinHA]) # 2 x refBlk x SGops x atoms - + def makeWavesDerv(ngl,waveTypes,FSSdata,XSSdata,USSdata,Mast): ''' Only for Fourier waves for fraction, position & adp (probably not used for magnetism) @@ -1945,7 +1960,7 @@ def makeWavesDerv(ngl,waveTypes,FSSdata,XSSdata,USSdata,Mast): Bx = np.array(XSSdata[3:]).T #...cos pos mods Au = Mast*np.array(G2lat.U6toUij(USSdata[:6])).T #atoms x waves x sin Uij mods Bu = Mast*np.array(G2lat.U6toUij(USSdata[6:])).T #...cos Uij mods - nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1]] + nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1]] StauX = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) #atoms x waves x 3 x ngl CtauX = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) ZtauXt = np.zeros((Ax.shape[0],2,3,ngl)) #atoms x Tminmax x 3 x ngl @@ -1957,7 +1972,7 @@ def makeWavesDerv(ngl,waveTypes,FSSdata,XSSdata,USSdata,Mast): elif 'Block' in waveTypes[iatm]: nx = 1 tauX = np.arange(1.,nWaves[1]+1-nx)[:,nxs]*glTau #Xwaves x ngl - if nx: + if nx: StauX[iatm][nx:] = np.ones_like(Ax)[iatm,nx:,:,nxs]*np.sin(twopi*tauX)[nxs,:,nxs,:] #atoms X waves X 3(xyz) X ngl CtauX[iatm][nx:] = np.ones_like(Bx)[iatm,nx:,:,nxs]*np.cos(twopi*tauX)[nxs,:,nxs,:] #ditto else: @@ -1985,7 +2000,7 @@ def makeWavesDerv(ngl,waveTypes,FSSdata,XSSdata,USSdata,Mast): UmodA = 0. UmodB = 0. return waveShapes,[StauF,CtauF],[StauX,CtauX,ZtauXt,ZtauXx],[StauU,CtauU],UmodA+UmodB - + def ModulationDerv(H,HP,Hij,nWaves,waveShapes,Fmod,Xmod,UmodAB,SCtauF,SCtauX,SCtauU,glTau,glWt): ''' Compute Fourier modulation derivatives @@ -1993,7 +2008,7 @@ def ModulationDerv(H,HP,Hij,nWaves,waveShapes,Fmod,Xmod,UmodAB,SCtauF,SCtauX,SCt HP: array ops X hklt proj to hkl Hij: array 2pi^2[a*^2h^2 b*^2k^2 c*^2l^2 a*b*hk a*c*hl b*c*kl] of projected hklm to hkl space ''' - + Mf = [H.shape[0],]+list(waveShapes[0]) #=[ops,atoms,waves,2] (sin+cos frac mods) dGdMfC = np.zeros(Mf) dGdMfS = np.zeros(Mf) @@ -2003,12 +2018,12 @@ def ModulationDerv(H,HP,Hij,nWaves,waveShapes,Fmod,Xmod,UmodAB,SCtauF,SCtauX,SCt Mu = [H.shape[0],]+list(waveShapes[2]) #=[ops,atoms,waves,12] (sin+cos Uij mods) dGdMuC = np.zeros(Mu) dGdMuS = np.zeros(Mu) - + D = twopi*H[:,3][:,nxs]*glTau[nxs,:] #m*e*tau; ops X ngl HdotX = twopi*np.inner(HP,Xmod) #ops x atoms X ngl HdotXD = HdotX+D[:,nxs,:] if nWaves[2]: - Umod = np.swapaxes((UmodAB),2,4) #atoms x waves x ngl x 3x3 (symmetric so I can do this!) + Umod = np.swapaxes((UmodAB),2,4) #atoms x waves x ngl x 3x3 (symmetric so I can do this!) HuH = np.sum(HP[:,nxs,nxs,nxs]*np.inner(HP,Umod),axis=-1) #ops x atoms x waves x ngl HuH = np.sum(HP[:,nxs,nxs,nxs]*np.inner(HP,Umod),axis=-1) #ops x atoms x waves x ngl HbH = np.exp(-np.sum(HuH,axis=-2)) # ops x atoms x ngl; sum waves - OK vs Modulation version @@ -2033,25 +2048,25 @@ def ModulationDerv(H,HP,Hij,nWaves,waveShapes,Fmod,Xmod,UmodAB,SCtauF,SCtauX,SCt dGdMxCb = -np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) dGdMxC = np.concatenate((dGdMxCa,dGdMxCb),axis=-1) # ops x atoms x waves x 2xyz - imag part - good -# dGdMxSa = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) -# dGdMxSb = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) - dGdMxSa = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) - dGdMxSb = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) +# dGdMxSa = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) +# dGdMxSb = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) + dGdMxSa = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) + dGdMxSb = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) dGdMxS = np.concatenate((dGdMxSa,dGdMxSb),axis=-1) return [dGdMfC,dGdMfS],[dGdMxC,dGdMxS],[dGdMuC,dGdMuS] - + def posFourier(tau,psin,pcos): A = np.array([ps[:,nxs]*np.sin(2*np.pi*(i+1)*tau) for i,ps in enumerate(psin)]) B = np.array([pc[:,nxs]*np.cos(2*np.pi*(i+1)*tau) for i,pc in enumerate(pcos)]) return np.sum(A,axis=0)+np.sum(B,axis=0) - + def posZigZag(T,Tmm,Xmax): DT = Tmm[1]-Tmm[0] Su = 2.*Xmax/DT Sd = 2.*Xmax/(1.-DT) A = np.array([np.where( 0.< (t-Tmm[0])%1. <= DT, -Xmax+Su*((t-Tmm[0])%1.), Xmax-Sd*((t-Tmm[1])%1.)) for t in T]) return A - + #def posZigZagDerv(T,Tmm,Xmax): # DT = Tmm[1]-Tmm[0] # Su = 2.*Xmax/DT @@ -2065,7 +2080,7 @@ def posZigZag(T,Tmm,Xmax): def posBlock(T,Tmm,Xmax): A = np.array([np.where(Tmm[0] < t%1. <= Tmm[1],-Xmax,Xmax) for t in T]) return A - + #def posBlockDerv(T,Tmm,Xmax): # dAdT = np.zeros((2,3,len(T))) # ind = np.searchsorted(T,Tmm) @@ -2073,12 +2088,12 @@ def posBlock(T,Tmm,Xmax): # dAdT[1,:,ind[1]] = Xmax/len(T) # dAdX = np.ones(3)[:,nxs]*np.array([np.where(Tmm[0] < t <= Tmm[1],-1.,1.) for t in T]) #OK # return dAdT,dAdX - + def fracCrenel(tau,Toff,Twid): Tau = (tau-Toff)%1. A = np.where(Tau0.,np.sqrt(var),-0.0001)) else: modeEsds = len(ISO['G2ModeList'])*[-0.0001] - dispEsds = len(ISO['G2VarList'])*[-0.0001] + dispEsds = len(ISO['G2VarList'])*[-0.0001] dispValues = np.dot(normMode2Var,modeVals) modeDict = {str(g2):([val,esd]) for val,g2,esd in zip(modeVals,ISO['G2ModeList'],modeEsds)} @@ -2285,11 +2300,11 @@ def CalcIsoCoords(Phase,parmDict,covdata={}): return modeDict,posDict def ApplyModeDisp(data): - ''' Applies ISODISTORT mode displacements to atom lists. - This changes the contents of the Draw Atoms positions and + ''' Applies ISODISTORT mode displacements to atom lists. + This changes the contents of the Draw Atoms positions and the Atoms positions. - :param dict data: the contents of the Phase data tree item for a + :param dict data: the contents of the Phase data tree item for a particular phase ''' generalData = data['General'] @@ -2324,21 +2339,21 @@ def ApplyModeDisp(data): indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True) for ind in indx: drawatom = drawAtoms[ind] - opr = drawatom[dcs-1] + opr = drawatom[dcs-1] X = G2spc.ApplyStringOps(opr,SGData,atxyz+displ) drawatom[dcx:dcx+3] = X return None else: return 'Draw structure first' - -# gauleg.py Gauss Legendre numerical quadrature, x and w computation -# integrate from a to b using n evaluations of the function f(x) -# usage: from gauleg import gaulegf -# x,w = gaulegf( a, b, n) -# area = 0.0 -# for i in range(1,n+1): # yes, 1..n -# area += w[i]*f(x[i]) + +# gauleg.py Gauss Legendre numerical quadrature, x and w computation +# integrate from a to b using n evaluations of the function f(x) +# usage: from gauleg import gaulegf +# x,w = gaulegf( a, b, n) +# area = 0.0 +# for i in range(1,n+1): # yes, 1..n +# area += w[i]*f(x[i]) def gaulegf(a, b, n): x = range(n+1) # x[0] unused @@ -2356,7 +2371,7 @@ def gaulegf(a, b, n): p3 = p2 p2 = p1 p1 = ((2.0*j-1.0)*z*p2-(j-1.0)*p3)/j - + pp = n*(z*p1-p2)/(z*z-1.0) z1 = z z = z1 - p1/pp @@ -2368,18 +2383,18 @@ def gaulegf(a, b, n): w[i] = 2.0*xl/((1.0-z*z)*pp*pp) w[n+1-i] = w[i] return np.array(x), np.array(w) -# end gaulegf - - +# end gaulegf + + def BessJn(nmax,x): ''' compute Bessel function J(n,x) from scipy routine & recurrance relation returns sequence of J(n,x) for n in range [-nmax...0...nmax] - + :param integer nmax: maximul order for Jn(x) :param float x: argument for Jn(x) - + :returns numpy array: [J(-nmax,x)...J(0,x)...J(nmax,x)] - + ''' import scipy.special as sp bessJn = np.zeros(2*nmax+1) @@ -2390,16 +2405,16 @@ def BessJn(nmax,x): bessJn[i+nmax] = 2*(i-1)*bessJn[nmax+i-1]/x-bessJn[nmax+i-2] bessJn[nmax-i] = bessJn[i+nmax]*(-1)**i return bessJn - + def BessIn(nmax,x): ''' compute modified Bessel function I(n,x) from scipy routines & recurrance relation returns sequence of I(n,x) for n in range [-nmax...0...nmax] - + :param integer nmax: maximul order for In(x) :param float x: argument for In(x) - + :returns numpy array: [I(-nmax,x)...I(0,x)...I(nmax,x)] - + ''' import scipy.special as sp bessIn = np.zeros(2*nmax+1) @@ -2410,15 +2425,15 @@ def BessIn(nmax,x): bessIn[i+nmax] = bessIn[nmax+i-2]-2*(i-1)*bessIn[nmax+i-1]/x bessIn[nmax-i] = bessIn[i+nmax] return bessIn - - + + ################################################################################ -#### distance, angle, planes, torsion stuff +#### distance, angle, planes, torsion stuff ################################################################################ def CalcDist(distance_dict, distance_atoms, parmDict): '''Used in class:`GSASIIobj.ExpressionCalcObj` to compute bond distances - when defined in an expression. + when defined in an expression. ''' if not len(parmDict): return 0. @@ -2440,10 +2455,10 @@ def CalcDist(distance_dict, distance_atoms, parmDict): Txyz = np.inner(M*inv,Txyz)+D dist = np.sqrt(np.sum(np.inner(Amat,(Txyz-Oxyz))**2)) # GSASIIpath.IPyBreak() - return dist - + return dist + def CalcDistDeriv(distance_dict, distance_atoms, parmDict): - '''Used to compute s.u. values on distances tracked in the sequential + '''Used to compute s.u. values on distances tracked in the sequential results table ''' if not len(parmDict): @@ -2455,13 +2470,13 @@ def CalcDistDeriv(distance_dict, distance_atoms, parmDict): Txyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[1])] for x in ['x','y','z']] symNo = distance_dict['symNo'] Tunit = distance_dict['cellNo'] - SGData = distance_dict['SGData'] + SGData = distance_dict['SGData'] deriv = getDistDerv(Oxyz,Txyz,Amat,Tunit,symNo,SGData) return deriv - + def CalcAngle(angle_dict, angle_atoms, parmDict): '''Used in class:`GSASIIobj.ExpressionCalcObj` to compute bond angles - when defined in an expression. + when defined in an expression. ''' if not len(parmDict): return 0. @@ -2493,7 +2508,7 @@ def CalcAngle(angle_dict, angle_atoms, parmDict): return angle def CalcAngleDeriv(angle_dict, angle_atoms, parmDict): - '''Used to compute s.u. values on angles tracked in the sequential + '''Used to compute s.u. values on angles tracked in the sequential results table ''' if not len(parmDict): @@ -2506,17 +2521,17 @@ def CalcAngleDeriv(angle_dict, angle_atoms, parmDict): Bxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][1])] for x in ['x','y','z']] symNo = angle_dict['symNo'] Tunit = angle_dict['cellNo'] - SGData = angle_dict['SGData'] + SGData = angle_dict['SGData'] deriv = getAngleDerv(Oxyz,Axyz,Bxyz,Amat,Tunit,symNo,SGData) return deriv def getSyXYZ(XYZ,ops,SGData): - '''default doc - + '''default doc + :param type name: description - + :returns: type name: description - + ''' XYZout = np.zeros_like(XYZ) for i,[xyz,op] in enumerate(zip(XYZ,ops)): @@ -2536,24 +2551,24 @@ def getSyXYZ(XYZ,ops,SGData): M,T = SGData['SGOps'][syop] XYZout[i] = (np.inner(M,xyz)+T)*inv+SGData['SGCen'][cent]+unit return XYZout - + def getRestDist(XYZ,Amat): '''Compute interatomic distance(s) for use in restraints - + :param type name: description - + :returns: type name: description - + ''' return np.sqrt(np.sum(np.inner(Amat,(XYZ[1]-XYZ[0]))**2)) - + def getRestDeriv(Func,XYZ,Amat,ops,SGData): '''Compute numerical derivatives of restraints for use in minimization - + :param type name: description - + :returns: type name: description - + ''' deriv = np.zeros((len(XYZ),3)) dx = 0.00001 @@ -2569,13 +2584,13 @@ def getRestDeriv(Func,XYZ,Amat,ops,SGData): def getRestAngle(XYZ,Amat): '''Compute interatomic angle(s) for use in restraints - + :param type name: description - + :returns: type name: description - + ''' - + def calcVec(Ox,Tx,Amat): return np.inner(Amat,(Tx-Ox)) @@ -2588,14 +2603,14 @@ def calcVec(Ox,Tx,Amat): angle = (2.-edge)/2. angle = max(angle,-1.) return acosd(angle) - + def getRestPlane(XYZ,Amat): '''Compute deviations from a best plane through atoms for use in restraints - + :param type name: description - + :returns: type name: description - + ''' sumXYZ = np.zeros(3) for xyz in XYZ: @@ -2610,28 +2625,28 @@ def getRestPlane(XYZ,Amat): Evec = np.sqrt(Evec)/(len(XYZ)-3) Order = np.argsort(Evec) return Evec[Order[0]] - -def getRestChiral(XYZ,Amat): + +def getRestChiral(XYZ,Amat): '''compute a chiral restraint - + :param type name: description - + :returns: type name: description - + ''' - VecA = np.empty((3,3)) + VecA = np.empty((3,3)) VecA[0] = np.inner(XYZ[1]-XYZ[0],Amat) VecA[1] = np.inner(XYZ[2]-XYZ[0],Amat) VecA[2] = np.inner(XYZ[3]-XYZ[0],Amat) return nl.det(VecA) - + def getRestTorsion(XYZ,Amat): '''compute a torsion restraint - + :param type name: description - + :returns: type name: description - + ''' VecA = np.empty((3,3)) VecA[0] = np.inner(XYZ[1]-XYZ[0],Amat) @@ -2647,14 +2662,14 @@ def getRestTorsion(XYZ,Amat): Ang = (P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)) TOR = (acosd(Ang)*D/abs(D)+720.)%360. return TOR - + def calcTorsionEnergy(TOR,Coeff=[]): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' sum = 0. if len(Coeff): @@ -2671,11 +2686,11 @@ def calcTorsionEnergy(TOR,Coeff=[]): def getTorsionDeriv(XYZ,Amat,Coeff): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' deriv = np.zeros((len(XYZ),3)) dx = 0.00001 @@ -2686,39 +2701,39 @@ def getTorsionDeriv(XYZ,Amat,Coeff): p1,d1 = calcTorsionEnergy(tor,Coeff) XYZ[j] += 2*x tor = getRestTorsion(XYZ,Amat) - p2,d2 = calcTorsionEnergy(tor,Coeff) + p2,d2 = calcTorsionEnergy(tor,Coeff) XYZ[j] -= x deriv[j][i] = (p2-p1)/(2*dx) return deriv.flatten() def getRestRama(XYZ,Amat): '''Computes a pair of torsion angles in a 5 atom string - + :param nparray XYZ: crystallographic coordinates of 5 atoms :param nparray Amat: crystal to cartesian transformation matrix - + :returns: list (phi,psi) two torsion angles in degrees - + ''' phi = getRestTorsion(XYZ[:5],Amat) psi = getRestTorsion(XYZ[1:],Amat) return phi,psi - + def calcRamaEnergy(phi,psi,Coeff=[]): '''Computes pseudo potential energy from a pair of torsion angles and a - numerical description of the potential energy surface. Used to create - penalty function in LS refinement: + numerical description of the potential energy surface. Used to create + penalty function in LS refinement: :math:`Eval(\\phi,\\psi) = C[0]*exp(-V/1000)` where :math:`V = -C[3] * (\\phi-C[1])^2 - C[4]*(\\psi-C[2])^2 - 2*(\\phi-C[1])*(\\psi-C[2])` - + :param float phi: first torsion angle (:math:`\\phi`) :param float psi: second torsion angle (:math:`\\psi`) :param list Coeff: pseudo potential coefficients - + :returns: list (sum,Eval): pseudo-potential difference from minimum & value; sum is used for penalty function. - + ''' sum = 0. Eval = 0. @@ -2739,15 +2754,15 @@ def calcRamaEnergy(phi,psi,Coeff=[]): def getRamaDeriv(XYZ,Amat,Coeff): '''Computes numerical derivatives of torsion angle pair pseudo potential - with respect of crystallographic atom coordinates of the 5 atom sequence - + with respect of crystallographic atom coordinates of the 5 atom sequence + :param nparray XYZ: crystallographic coordinates of 5 atoms :param nparray Amat: crystal to cartesian transformation matrix :param list Coeff: pseudo potential coefficients - + :returns: list (deriv) derivatives of pseudopotential with respect to 5 atom crystallographic xyz coordinates. - + ''' deriv = np.zeros((len(XYZ),3)) dx = 0.00001 @@ -2765,11 +2780,11 @@ def getRamaDeriv(XYZ,Amat,Coeff): def getRestPolefig(ODFln,SamSym,Grid): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' X,Y = np.meshgrid(np.linspace(1.,-1.,Grid),np.linspace(-1.,1.,Grid)) R,P = np.sqrt(X**2+Y**2).flatten(),atan2d(Y,X).flatten() @@ -2781,19 +2796,19 @@ def getRestPolefig(ODFln,SamSym,Grid): def getRestPolefigDerv(HKL,Grid,SHCoeff): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' pass - + def getDistDerv(Oxyz,Txyz,Amat,Tunit,Top,SGData): '''computes the numerical derivative of the distance between two atoms. - Used in :func:`CalcDistDeriv` (seq. table) and in + Used in :func:`CalcDistDeriv` (seq. table) and in :func:`GSASIIstrMain.RetDistAngle` to compute the s.u. on the distance - + :param list Oxyz: list of x, y, & z values for the Origin atom :param list Txyz: list of x, y, & z values for the Target atom :param np.array Amat: The reciprocal cell tensor @@ -2806,7 +2821,7 @@ def getDistDerv(Oxyz,Txyz,Amat,Tunit,Top,SGData): def calcDist(Ox,Tx,U,inv,C,M,T,Amat): TxT = inv*(np.inner(M,Tx)+T)+C+U return np.sqrt(np.sum(np.inner(Amat,(TxT-Ox))**2)) - + inv = Top/abs(Top) cent = abs(Top)//100 op = abs(Top)%100-1 @@ -2828,22 +2843,23 @@ def calcDist(Ox,Tx,U,inv,C,M,T,Amat): return deriv def setupRBDistDerv(parmDict,varyList,sigList,rigidbodyDict,Phases): - '''Compute a copy of the parameter dict (parmDict) with each varied - parameter incremented by 1 s.u. value and with those values extended to + '''Compute a copy of the parameter dict (parmDict) with each varied + parameter incremented by 1 s.u. value and with those values extended to other parameters due to rigid bodies or constraints. This gets called once to prepare for s.u. computations in func:`GSASIIstrMain.RetDistAngle` - and the values are reused in every distance and angle computation. + and the values are reused in every distance and angle computation. + + :returns: multiParmDict,changedParmDict where - :returns: multiParmDict,changedParmDict where - * multiParmDict[k] (k in varyList, plus None) is the copy of - parmDict where parameter k has been changed; - * changedParmDict[k] (k in varyList) is a list of all the parameters that have been + * multiParmDict[k] (k in varyList, plus None) is the copy of + parmDict where parameter k has been changed; + * changedParmDict[k] (k in varyList) is a list of all the parameters that have been changed in multiParmDict[k] after applying RB & constraints ''' - import GSASIImapvars as G2mv - import GSASIIstrMath as G2stMth + from . import GSASIImapvars as G2mv + from . import GSASIIstrMath as G2stMth def extendChanges(prms): - '''Propagate changes due to constraint and rigid bodies + '''Propagate changes due to constraint and rigid bodies from varied parameters to dependent parameters ''' # apply constraints @@ -2881,25 +2897,25 @@ def extendChanges(prms): def getRBDistDerv(OdxyzNames,TdxyzNames,Amat,Tunit,Top,SGData, multiParmDict,changedParmDict, varyList,sigList): - '''computes the numerical derivative of the distance between two + '''computes the numerical derivative of the distance between two atoms. Used where one or both is in a rigid body. - Used in :func:`GSASIIstrMain.RetDistAngle` to compute the s.u. on + Used in :func:`GSASIIstrMain.RetDistAngle` to compute the s.u. on the distance - + :param list OxyzNames: parameter names for x, y, & z for the Origin atom :param list TxyzNames: parameter names for x, y, & z for the Target atom :param np.array Amat: The reciprocal cell tensor :param Tunit: translation applied to the target atom :param int Top: symmetry operation applied to the target atom :param dict SGData: space group object - :param dict multiParmDict: multiParmDict[var] is the parameter - dict where var has been offset by sigma (see sigList), as well + :param dict multiParmDict: multiParmDict[var] is the parameter + dict where var has been offset by sigma (see sigList), as well as any parameters dependent on var - :param dict changedParmDict: changedParmDict[var] is a list the + :param dict changedParmDict: changedParmDict[var] is a list the parameters changed in multiParmDict[var] - :param list varyList: list of varied parameters in the covariance + :param list varyList: list of varied parameters in the covariance matrix - :param list sigList: list of s.u. values for each of entry in + :param list sigList: list of s.u. values for each of entry in varyList :returns: the derivative w/r to the six coordinates, Oxyz & Txyz @@ -2907,7 +2923,7 @@ def getRBDistDerv(OdxyzNames,TdxyzNames,Amat,Tunit,Top,SGData, def calcDist(Ox,Tx,U,inv,C,M,T,Amat): TxT = inv*(np.inner(M,Tx)+T)+C+U return np.sqrt(np.sum(np.inner(Amat,(TxT-Ox))**2)) - + inv = Top/abs(Top) cent = abs(Top)//100 op = abs(Top)%100-1 @@ -2919,7 +2935,7 @@ def calcDist(Ox,Tx,U,inv,C,M,T,Amat): Oxyz = [multiParmDict[None][k] for k in OxyzNames] Txyz = [multiParmDict[None][k] for k in TxyzNames] d0 = calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat) - deriv = np.zeros(len(varyList)) + deriv = np.zeros(len(varyList)) for i,(var,sig) in enumerate(zip(varyList,sigList)): if var not in multiParmDict or sig == 0: continue # are any of the coordinates changed by changing the value for var? @@ -2953,7 +2969,7 @@ def calcAngle(Oxyz,ABxyz,Amat,Tunit,symNo,SGData): return 0. vec[i] /= dist return acosd(np.sum(vec[0]*vec[1])) - + dx = .00001 deriv = np.zeros(9) for i in [0,1,2]: @@ -2973,25 +2989,25 @@ def calcAngle(Oxyz,ABxyz,Amat,Tunit,symNo,SGData): deriv[i+6] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx) Bxyz[i] -= dx return deriv - + def getAngSig(VA,VB,Amat,SGData,covData={}): - '''Compute an interatomic angle and its uncertainty from two vectors + '''Compute an interatomic angle and its uncertainty from two vectors each between an orgin atom and either of a pair of nearby atoms. - + :param np.array VA: an interatomic vector as a structure :param np.array VB: an interatomic vector also as a structure :param np.array Amat: unit cell parameters as an A vector - :param dict SGData: symmetry information - :param dict covData: covariance information including - the covariance matrix and the list of varied parameters. If not + :param dict SGData: symmetry information + :param dict covData: covariance information including + the covariance matrix and the list of varied parameters. If not supplied, the s.u. values are returned as zeros. - + :returns: angle, sigma(angle) ''' def calcVec(Ox,Tx,U,inv,C,M,T,Amat): TxT = inv*(np.inner(M,Tx)+T)+C+U return np.inner(Amat,(TxT-Ox)) - + def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): VecA = calcVec(Ox,TxA,unitA,invA,CA,MA,TA,Amat) VecA /= np.sqrt(np.sum(VecA**2)) @@ -3002,7 +3018,7 @@ def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): angle = (2.-edge)/2. angle = max(angle,-1.) return acosd(angle) - + OxAN,OxA,TxAN,TxA,unitA,TopA = VA OxBN,OxB,TxBN,TxB,unitB,TopB = VB invA = invB = 1 @@ -3029,19 +3045,19 @@ def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): OxA[i] += 2*dx dadx[i] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx) OxA[i] -= dx - + TxA[i] -= dx a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) TxA[i] += 2*dx dadx[i+3] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx) TxA[i] -= dx - + TxB[i] -= dx a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) TxB[i] += 2*dx dadx[i+6] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx) TxB[i] -= dx - + sigAng = np.sqrt(np.inner(dadx,np.inner(AngVcov,dadx))) if sigAng < 0.01: sigAng = 0.0 @@ -3050,25 +3066,25 @@ def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): return calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat),0.0 def getRBAngSig(VA,VB,Amat,SGData,covData,multiParmDict,changedParmDict): - '''Compute an interatomic angle and its uncertainty from two vectors + '''Compute an interatomic angle and its uncertainty from two vectors each between an orgin atom and either of a pair of nearby atoms. - Uncertainties are computed taling into account rigid bodies and + Uncertainties are computed taling into account rigid bodies and constraints. - + :param np.array VA: an interatomic vector as a structure :param np.array VB: an interatomic vector also as a structure :param np.array Amat: unit cell parameters as an A vector - :param dict SGData: symmetry information - :param dict covData: covariance information including - the covariance matrix and the list of varied parameters. If not + :param dict SGData: symmetry information + :param dict covData: covariance information including + the covariance matrix and the list of varied parameters. If not supplied, the s.u. values are returned as zeros. - + :returns: angle, sigma(angle) ''' def calcVec(Ox,Tx,U,inv,C,M,T,Amat): TxT = inv*(np.inner(M,Tx)+T)+C+U return np.inner(Amat,(TxT-Ox)) - + def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): VecA = calcVec(Ox,TxA,unitA,invA,CA,MA,TA,Amat) VecA /= np.sqrt(np.sum(VecA**2)) @@ -3079,7 +3095,7 @@ def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): angle = (2.-edge)/2. angle = max(angle,-1.) return acosd(angle) - + OxAdN,OxA,TxAdN,TxA,unitA,TopA = VA OxBdN,OxB,TxBdN,TxB,unitB,TopB = VB invA = invB = 1 @@ -3100,7 +3116,7 @@ def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): TxAN = [k.replace('::dA','::A') for k in TxAdN] TxBN = [k.replace('::dA','::A') for k in TxBdN] Ang0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) - deriv = np.zeros(len(varyList)) + deriv = np.zeros(len(varyList)) for i,(var,sig) in enumerate(zip(varyList,sigList)): if var not in multiParmDict or sig == 0: continue # are any of the coordinates changed by changing the var value? @@ -3117,11 +3133,11 @@ def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): def GetDistSig(Oatoms,Atoms,Amat,SGData,covData={}): '''not used - + :param type name: description - + :returns: type name: description - + ''' def calcDist(Atoms,SyOps,Amat): XYZ = [] @@ -3132,7 +3148,7 @@ def calcDist(Atoms,SyOps,Amat): XYZ[-1] = np.inner(Amat,XYZ[-1]).T V1 = XYZ[1]-XYZ[0] return np.sqrt(np.sum(V1**2)) - + SyOps = [] names = [] for i,atom in enumerate(Oatoms): @@ -3143,7 +3159,7 @@ def calcDist(Atoms,SyOps,Amat): c = SGData['SGCen'][abs(Op)//100] SyOps.append([inv,m,t,c,unit]) Dist = calcDist(Oatoms,SyOps,Amat) - + sig = -0.001 if 'covMatrix' in covData: dx = .00001 @@ -3161,16 +3177,16 @@ def calcDist(Atoms,SyOps,Amat): sig = np.sqrt(np.inner(dadx,np.inner(DistVcov,dadx))) if sig < 0.001: sig = -0.001 - + return Dist,sig def GetAngleSig(Oatoms,Atoms,Amat,SGData,covData={}): '''Not used - + :param type name: description - + :returns: type name: description - + ''' def calcAngle(Atoms,SyOps,Amat): @@ -3198,7 +3214,7 @@ def calcAngle(Atoms,SyOps,Amat): c = SGData['SGCen'][abs(Op)//100] SyOps.append([inv,m,t,c,unit]) Angle = calcAngle(Oatoms,SyOps,Amat) - + sig = -0.01 if 'covMatrix' in covData: dx = .00001 @@ -3216,20 +3232,20 @@ def calcAngle(Atoms,SyOps,Amat): sig = np.sqrt(np.inner(dadx,np.inner(AngVcov,dadx))) if sig < 0.01: sig = -0.01 - + return Angle,sig def GetTorsionSig(Oatoms,Atoms,Amat,SGData,covData={}): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' def calcTorsion(Atoms,SyOps,Amat): - + XYZ = [] for i,atom in enumerate(Atoms): Inv,M,T,C,U = SyOps[i] @@ -3249,7 +3265,7 @@ def calcTorsion(Atoms,SyOps,Amat): P23 = np.dot(V2,V3) Tors = acosd((P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)))*D/abs(D) return Tors - + SyOps = [] names = [] for i,atom in enumerate(Oatoms): @@ -3260,7 +3276,7 @@ def calcTorsion(Atoms,SyOps,Amat): c = SGData['SGCen'][abs(Op)//100] SyOps.append([inv,m,t,c,unit]) Tors = calcTorsion(Oatoms,SyOps,Amat) - + sig = -0.01 if 'covMatrix' in covData: dx = .00001 @@ -3272,23 +3288,23 @@ def calcTorsion(Atoms,SyOps,Amat): a0 = calcTorsion(Oatoms,SyOps,Amat) Oatoms[ia][ix+1] += 2*dx dadx[i] = (calcTorsion(Oatoms,SyOps,Amat)-a0)/(2.*dx) - Oatoms[ia][ix+1] -= dx + Oatoms[ia][ix+1] -= dx covMatrix = covData['covMatrix'] varyList = covData['varyList'] TorVcov = getVCov(names,varyList,covMatrix) sig = np.sqrt(np.inner(dadx,np.inner(TorVcov,dadx))) if sig < 0.01: sig = -0.01 - + return Tors,sig - + def GetDATSig(Oatoms,Atoms,Amat,SGData,covData={}): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' def calcDist(Atoms,SyOps,Amat): @@ -3300,7 +3316,7 @@ def calcDist(Atoms,SyOps,Amat): XYZ[-1] = np.inner(Amat,XYZ[-1]).T V1 = XYZ[1]-XYZ[0] return np.sqrt(np.sum(V1**2)) - + def calcAngle(Atoms,SyOps,Amat): XYZ = [] for i,atom in enumerate(Atoms): @@ -3317,7 +3333,7 @@ def calcAngle(Atoms,SyOps,Amat): return acosd(cang) def calcTorsion(Atoms,SyOps,Amat): - + XYZ = [] for i,atom in enumerate(Atoms): Inv,M,T,C,U = SyOps[i] @@ -3337,7 +3353,7 @@ def calcTorsion(Atoms,SyOps,Amat): P23 = np.dot(V2,V3) Tors = acosd((P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)))*D/abs(D) return Tors - + SyOps = [] names = [] for i,atom in enumerate(Oatoms): @@ -3354,7 +3370,7 @@ def calcTorsion(Atoms,SyOps,Amat): Val = calcAngle(Oatoms,SyOps,Amat) else: Val = calcTorsion(Oatoms,SyOps,Amat) - + sigVals = [-0.001,-0.01,-0.01] sig = sigVals[M-3] if 'covMatrix' in covData: @@ -3373,9 +3389,9 @@ def calcTorsion(Atoms,SyOps,Amat): a0 = calcTorsion(Oatoms,SyOps,Amat) Oatoms[ia][ix+1] -= 2*dx if M == 2: - dadx[i] = (calcDist(Oatoms,SyOps,Amat)-a0)/(2.*dx) + dadx[i] = (calcDist(Oatoms,SyOps,Amat)-a0)/(2.*dx) elif M == 3: - dadx[i] = (calcAngle(Oatoms,SyOps,Amat)-a0)/(2.*dx) + dadx[i] = (calcAngle(Oatoms,SyOps,Amat)-a0)/(2.*dx) else: dadx[i] = (calcTorsion(Oatoms,SyOps,Amat)-a0)/(2.*dx) covMatrix = covData['covMatrix'] @@ -3384,7 +3400,7 @@ def calcTorsion(Atoms,SyOps,Amat): sig = np.sqrt(np.inner(dadx,np.inner(Vcov,dadx))) if sig < sigVals[M-3]: sig = sigVals[M-3] - + return Val,sig def GetMag(mag,Cell): @@ -3392,29 +3408,29 @@ def GetMag(mag,Cell): Compute magnetic moment magnitude. :param list mag: atom magnetic moment parms (must be magnetic!) :param list Cell: lattice parameters - + :returns: moment magnitude as float - + ''' G = G2lat.fillgmat(Cell) ast = np.sqrt(np.diag(G)) GS = G/np.outer(ast,ast) mag = np.array(mag) Mag = np.sqrt(np.inner(mag,np.inner(mag,GS))) - return Mag + return Mag def GetMagDerv(mag,Cell): ''' Compute magnetic moment derivatives numerically :param list mag: atom magnetic moment parms (must be magnetic!) :param list Cell: lattice parameters - + :returns: moment derivatives as floats - + ''' def getMag(m): return np.sqrt(np.inner(m,np.inner(m,GS))) - + derv = np.zeros(3) dm = 0.0001 twodm = 2.*dm @@ -3432,7 +3448,7 @@ def getMag(m): def searchBondRestr(origAtoms,targAtoms,bond,Factor,GType,SGData,Amat, defESD=0.01,dlg=None): - '''Search for bond distance restraints. + '''Search for bond distance restraints. ''' foundBonds = [] indices = (-2,-1,0,1,2) @@ -3461,7 +3477,7 @@ def searchBondRestr(origAtoms,targAtoms,bond,Factor,GType,SGData,Amat, Topstr = str(Top) foundBonds.append([[Oid,Tid],['1',Topstr],bond,defESD]) return foundBonds - + def ValEsd(value,esd=0,nTZ=False): '''Format a floating point number with a given level of precision or with in crystallographic format with a "esd", as value(esd). If esd is @@ -3499,7 +3515,7 @@ def ValEsd(value,esd=0,nTZ=False): elif esd != 0: # transform the esd to a one or two digit integer l = math.log10(abs(esd)) % 1. - if l < math.log10(cutoff): l+= 1. + if l < math.log10(cutoff): l+= 1. intesd = int(round(10**l)) # esd as integer # determine the number of digits offset for the esd esdoff = int(round(math.log10(intesd*1./abs(esd)))) @@ -3524,7 +3540,7 @@ def ValEsd(value,esd=0,nTZ=False): elif valoff != 0: # esd = 0; exponential notation ==> esdoff decimal places out = ("{:."+str(esdoff)+"f}").format(value/10**valoff) # format the value else: # esd = 0; non-exponential notation ==> esdoff+1 significant digits - if abs(value) > 0: + if abs(value) > 0: extra = -math.log10(abs(value)) else: extra = 0 @@ -3538,22 +3554,22 @@ def ValEsd(value,esd=0,nTZ=False): if valoff != 0: out += ("e{:d}").format(valoff) # add an exponent, when needed return out - + ############################################################################### #### Protein validation - "ERRATV2" analysis ############################################################################### def validProtein(Phase,old): - + def sumintact(intact): return {'CC':intact['CC'],'NN':intact['NN'],'OO':intact['OO'], 'CN':(intact['CN']+intact['NC']),'CO':(intact['CO']+intact['OC']), 'NO':(intact['NO']+intact['ON'])} - + resNames = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE', 'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','MSE'] # data from errat.f - b1_old = np.array([ + b1_old = np.array([ [1154.343, 600.213, 1051.018, 1132.885, 960.738], [600.213, 1286.818, 1282.042, 957.156, 612.789], [1051.018, 1282.042, 3519.471, 991.974, 1226.491], @@ -3563,10 +3579,10 @@ def sumintact(intact): avg_old = np.array([ 0.225, 0.281, 0.071, 0.237, 0.044]) #Table 1 3.5A Obsd. Fr. p 1513 # data taken from erratv2.ccp b1 = np.array([ - [5040.279078850848200, 3408.805141583649400, 4152.904423767300600, 4236.200004171890200, 5054.781210204625500], - [3408.805141583648900, 8491.906094010220800, 5958.881777877950300, 1521.387352718486200, 4304.078200827221700], - [4152.904423767301500, 5958.881777877952100, 7637.167089335050100, 6620.715738223072500, 5287.691183798410700], - [4236.200004171890200, 1521.387352718486200, 6620.715738223072500, 18368.343774298410000, 4050.797811118806700], + [5040.279078850848200, 3408.805141583649400, 4152.904423767300600, 4236.200004171890200, 5054.781210204625500], + [3408.805141583648900, 8491.906094010220800, 5958.881777877950300, 1521.387352718486200, 4304.078200827221700], + [4152.904423767301500, 5958.881777877952100, 7637.167089335050100, 6620.715738223072500, 5287.691183798410700], + [4236.200004171890200, 1521.387352718486200, 6620.715738223072500, 18368.343774298410000, 4050.797811118806700], [5054.781210204625500, 4304.078200827220800, 5287.691183798409800, 4050.797811118806700, 6666.856740479164700]]) avg = np.array([0.192765509919262, 0.195575208778518, 0.275322406824210, 0.059102357035642, 0.233154192767480]) General = Phase['General'] @@ -3602,7 +3618,7 @@ def sumintact(intact): continue #Box content checks with errat.f $ erratv2.cpp ibox1 arrays indices = (-1,0,1) - Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) + Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) dsmax = 3.75**2 if old: dsmax = 3.5**2 @@ -3651,7 +3667,7 @@ def sumintact(intact): tgts = [] for unit in Units: #assemble list of all possible target atoms jbox = ibox+unit - if np.all(jbox>=0) and np.all((jbox-nbox[:3])<0): + if np.all(jbox>=0) and np.all((jbox-nbox[:3])<0): tgts += list(Boxes[jbox[0],jbox[1],jbox[2]]) tgts = list(set(tgts)) tgts = [tgt for tgt in tgts if atom[:3] != cartAtoms[tgt][:3]] #exclude same residue @@ -3721,15 +3737,19 @@ def sumintact(intact): Probs += 4*[0.,] #skip last 4 residues in chain chainProb += Probs return resNames,chainProb,resIDs - + ################################################################################ #### Texture fitting stuff ################################################################################ def FitTexture(General,Gangls,refData,keyList,pgbar): - import pytexture as ptx +# if GSASIIpath.binaryPath: +# import pytexture as ptx +# else: +# from . import pytexture as ptx +# #import GSASII.pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics - + def printSpHarm(textureData,SHtextureSig): Tindx = 1.0 Tvar = 0.0 @@ -3776,17 +3796,17 @@ def printSpHarm(textureData,SHtextureSig): iBeg += 10 iFin = min(iBeg+10,nCoeff) print(' Texture index J = %.3f(%d)'%(Tindx,int(1000*np.sqrt(Tvar)))) - + def Dict2Values(parmdict, varylist): - '''Use before call to leastsq to setup list of values for the parameters + '''Use before call to leastsq to setup list of values for the parameters in parmdict, as selected by key in varylist''' - return [parmdict[key] for key in varylist] - + return [parmdict[key] for key in varylist] + def Values2Dict(parmdict, varylist, values): - ''' Use after call to leastsq to update the parameter dictionary with + ''' Use after call to leastsq to update the parameter dictionary with values corresponding to keys in varylist''' parmdict.update(list(zip(varylist,values))) - + def errSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar): parmDict.update(list(zip(varyList,values))) Mat = np.empty(0) @@ -3817,7 +3837,7 @@ def errSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar) pgbar.Update(int(R),newmsg='Residual = %5.2f'%(R)) print (' Residual: %.3f%%'%(R)) return Mat - + def dervSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar): Mat = np.empty(0) Sangls = [parmDict['Sample omega'],parmDict['Sample chi'],parmDict['Sample phi']] @@ -3870,7 +3890,7 @@ def dervSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar args=(SGData,cell,Gangls,Texture['Model'],refData,parmDict,varyList,pgbar)) ncyc = int(result[2]['nfev']//2) if ncyc: - runtime = time.time()-begin + runtime = time.time()-begin chisq = np.sum(result[2]['fvec']**2) Values2Dict(parmDict, varyList, result[0]) GOF = chisq/(len(result[2]['fvec'])-len(varyList)) #reduced chi^2 @@ -3886,29 +3906,29 @@ def dervSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar return None else: break - + if ncyc: for parm in parmDict: if 'C' in parm: Texture['SH Coeff'][1][parm] = parmDict[parm] else: - Texture[parm][1] = parmDict[parm] + Texture[parm][1] = parmDict[parm] sigDict = dict(zip(varyList,sig)) printSpHarm(Texture,sigDict) - + return None - + ################################################################################ #### Fourier & charge flip stuff ################################################################################ def adjHKLmax(SGData,Hmax): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' if SGData['SGLaue'] in ['3','3m1','31m','6/m','6/mmm']: Hmax[0] = int(math.ceil(Hmax[0]/6.))*6 @@ -3921,11 +3941,11 @@ def adjHKLmax(SGData,Hmax): def OmitMap(data,reflDict,pgbar=None): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' generalData = data['General'] if not generalData['Map']['MapType']: @@ -3936,7 +3956,7 @@ def OmitMap(data,reflDict,pgbar=None): SGData = generalData['SGData'] SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) SGT = np.array([ops[1] for ops in SGData['SGOps']]) - cell = generalData['Cell'][1:8] + cell = generalData['Cell'][1:8] A = G2lat.cell2A(cell[:6]) Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1 adjHKLmax(SGData,Hmax) @@ -3986,14 +4006,14 @@ def OmitMap(data,reflDict,pgbar=None): mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] G2fil.G2Print ('Omit map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape))) return mapData - + def FourierMap(data,reflDict): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' generalData = data['General'] mapData = generalData['Map'] @@ -4001,7 +4021,7 @@ def FourierMap(data,reflDict): SGData = generalData['SGData'] SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) SGT = np.array([ops[1] for ops in SGData['SGOps']]) - cell = generalData['Cell'][1:8] + cell = generalData['Cell'][1:8] A = G2lat.cell2A(cell[:6]) Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1 adjHKLmax(SGData,Hmax) @@ -4055,14 +4075,14 @@ def FourierMap(data,reflDict): mapData['rho'] = np.real(rho) mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] - + def Fourier4DMap(data,reflDict): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' generalData = data['General'] map4DData = generalData['4DmapData'] @@ -4072,7 +4092,7 @@ def Fourier4DMap(data,reflDict): SSGData = generalData['SSGData'] SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']]) SSGT = np.array([ops[1] for ops in SSGData['SSGOps']]) - cell = generalData['Cell'][1:8] + cell = generalData['Cell'][1:8] A = G2lat.cell2A(cell[:6]) maxM = 4 Hmax = G2lat.getHKLmax(dmin,SGData,A)+[maxM,] @@ -4104,14 +4124,14 @@ def Fourier4DMap(data,reflDict): h,k,l,m = hkl+Hmax Fhkl[h,k,l,m] = F*phasep h,k,l,m = -hkl+Hmax - Fhkl[h,k,l,m] = F*phasem + Fhkl[h,k,l,m] = F*phasem elif 'delt-F' in mapData['MapType']: dF = np.sqrt(Fosq)-np.sqrt(Fcsq) h,k,l,m = hkl+Hmax Fhkl[h,k,l,m] = dF*phasep h,k,l,m = -hkl+Hmax Fhkl[h,k,l,m] = dF*phasem - SSrho = fft.fftn(fft.fftshift(Fhkl))/cell[6] #4D map + SSrho = fft.fftn(fft.fftshift(Fhkl))/cell[6] #4D map rho = fft.fftn(fft.fftshift(Fhkl[:,:,:,maxM+1]))/cell[6] #3D map map4DData['rho'] = np.real(SSrho) map4DData['rhoMax'] = max(np.max(map4DData['rho']),-np.min(map4DData['rho'])) @@ -4124,13 +4144,13 @@ def Fourier4DMap(data,reflDict): G2fil.G2Print ('Fourier map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape))) # map printing for testing purposes -def printRho(SGLaue,rho,rhoMax): +def printRho(SGLaue,rho,rhoMax): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' dim = len(rho.shape) if dim == 2: @@ -4156,17 +4176,17 @@ def printRho(SGLaue,rho,rhoMax): line += '%4d'%(r) print (line+'\n') ## keep this - -def findOffset(SGData,A,Fhkl): + +def findOffset(SGData,A,Fhkl): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' if SGData['SpGrp'] == 'P 1': - return [0,0,0] + return [0,0,0] hklShape = Fhkl.shape hklHalf = np.array(hklShape)//2 sortHKL = np.argsort(Fhkl.flatten()) @@ -4217,14 +4237,14 @@ def findOffset(SGData,A,Fhkl): ptext = ' map offset chi**2: %.3f, map offset: %d %d %d'%(chisq,DX[0],DX[1],DX[2]) G2fil.G2Print(ptext) return DX,ptext - + def ChargeFlip(data,reflDict,pgbar): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' generalData = data['General'] mapData = generalData['Map'] @@ -4240,7 +4260,7 @@ def ChargeFlip(data,reflDict,pgbar): SGData = generalData['SGData'] SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) SGT = np.array([ops[1] for ops in SGData['SGOps']]) - cell = generalData['Cell'][1:8] + cell = generalData['Cell'][1:8] A = G2lat.cell2A(cell[:6]) Vol = cell[6] im = 0 @@ -4287,7 +4307,7 @@ def ChargeFlip(data,reflDict,pgbar): Ncyc = 0 old = np.seterr(all='raise') twophases = [] - while True: + while True: CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))*(1.+0j) CEsig = np.std(CErho) CFrho = np.where(np.real(CErho) >= flipData['k-factor']*CEsig,CErho,-CErho) @@ -4312,24 +4332,24 @@ def ChargeFlip(data,reflDict,pgbar): ctext = ' No.cycles = %d Residual Rcf =%8.3f%s Map size: %s'%(Ncyc,Rcf,'%',str(CErho.shape)) G2fil.G2Print (ctext) roll,ptext = findOffset(SGData,A,CEhkl) #CEhkl needs to be just the observed set, not the full set! - + mapData['Rcf'] = Rcf mapData['rho'] = np.roll(np.roll(np.roll(CErho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] mapData['Type'] = reflDict['Type'] return mapData,twophases,ptext,ctext - -def findSSOffset(SGData,SSGData,A,Fhklm): + +def findSSOffset(SGData,SSGData,A,Fhklm): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' if SGData['SpGrp'] == 'P 1': - return [0,0,0,0] + return [0,0,0,0] hklmShape = Fhklm.shape hklmHalf = np.array(hklmShape)/2 sortHKLM = np.argsort(Fhklm.flatten()) @@ -4382,14 +4402,14 @@ def findSSOffset(SGData,SSGData,A,Fhklm): ptext = ' map offset chi**2: %.3f, map offset: %d %d %d %d'%(chisq,DX[0],DX[1],DX[2],DX[3]) G2fil.G2Print(ptext) return DX,ptext - + def SSChargeFlip(data,reflDict,pgbar): '''default doc string - + :param type name: description - + :returns: type name: description - + ''' generalData = data['General'] mapData = generalData['Map'] @@ -4407,7 +4427,7 @@ def SSChargeFlip(data,reflDict,pgbar): SSGData = generalData['SSGData'] SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']]) SSGT = np.array([ops[1] for ops in SSGData['SSGOps']]) - cell = generalData['Cell'][1:8] + cell = generalData['Cell'][1:8] A = G2lat.cell2A(cell[:6]) Vol = cell[6] maxM = 4 @@ -4448,7 +4468,7 @@ def SSChargeFlip(data,reflDict,pgbar): sumE = np.sum(ma.array(np.absolute(CEhkl),mask=Emask)) Ncyc = 0 old = np.seterr(all='raise') - while True: + while True: CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))*(1.+0j) CEsig = np.std(CErho) CFrho = np.where(np.real(CErho) >= flipData['k-factor']*CEsig,CErho,-CErho) @@ -4486,12 +4506,12 @@ def SSChargeFlip(data,reflDict,pgbar): map4DData['minmax'] = [np.max(map4DData['rho']),np.min(map4DData['rho'])] map4DData['Type'] = reflDict['Type'] return mapData,map4DData,ptext,ctext - + def getRho(xyz,mapData): ''' get scattering density at a point by 8-point interpolation param xyz: coordinate to be probed param: mapData: dict of map data - + :returns: density at xyz ''' rollMap = lambda rho,roll: np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) @@ -4513,9 +4533,9 @@ def getRho(xyz,mapData): R = Rho[0,0,0]*(1.-np.sum(D))+Rho[1,0,0]*D[0]+Rho[0,1,0]*D[1]+Rho[0,0,1]*D[2]+ \ Rho[1,1,1]*D123+Rho[0,1,1]*(D23-D123)+Rho[1,0,1]*(D13-D123)+Rho[1,1,0]*(D12-D123)+ \ Rho[0,0,0]*(D12+D13+D23-D123)-Rho[0,0,1]*(D13+D23-D123)- \ - Rho[0,1,0]*(D23+D12-D123)-Rho[1,0,0]*(D13+D12-D123) + Rho[0,1,0]*(D23+D12-D123)-Rho[1,0,0]*(D13+D12-D123) return R - + def getRhos(XYZ,rho): ''' get scattering density at an array of point by 8-point interpolation this is faster than gerRho which is only used for single points. However, getRhos is @@ -4523,7 +4543,7 @@ def getRhos(XYZ,rho): Thus, getRhos is unused in GSAS-II at this time. param xyz: array coordinates to be probed Nx3 param: rho: array copy of map (NB: don't use original!) - + :returns: density at xyz ''' def getBoxes(rho,I): @@ -4535,7 +4555,7 @@ def getBoxes(rho,I): [[rho[(Ix+1)%Mx,Iy%My,Iz%Mz],rho[(Ix+1)%Mx,Iy%My,(Iz+1)%Mz]], [rho[(Ix+1)%Mx,(Iy+1)%My,Iz%Mz],rho[(Ix+1)%Mx,(Iy+1)%My,(Iz+1)%Mz]]]]) return Rhos - + Blk = 400 #400 doesn't seem to matter nBlk = len(XYZ)//Blk #select Blk so this is an exact divide mapShape = np.array(rho.shape) @@ -4555,10 +4575,10 @@ def getBoxes(rho,I): R[iBeg:iFin] = RIs[:,0]*(1.-Ds[:,2])+RIs[:,1]*Ds[:,2] iBeg += Blk return R - + def SearchMap(generalData,drawingData,Neg=False): '''Does a search of a density map for peaks meeting the criterion of peak - height is greater than mapData['cutOff']/100 of mapData['rhoMax'] where + height is greater than mapData['cutOff']/100 of mapData['rhoMax'] where mapData is data['General']['mapData']; the map is also in mapData. :param generalData: the phase data structure; includes the map @@ -4576,11 +4596,11 @@ def SearchMap(generalData,drawingData,Neg=False): * dcent : ndarray the distance of the peaks from the unit cell center - ''' + ''' rollMap = lambda rho,roll: np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) - + norm = 1./(np.sqrt(3.)*np.sqrt(2.*np.pi)**3) - + def fixSpecialPos(xyz,SGData,Amat): equivs = G2spc.GenAtom(xyz,SGData,Move=True) X = [] @@ -4592,18 +4612,18 @@ def fixSpecialPos(xyz,SGData,Amat): return np.average(X,axis=0) else: return xyz - + def rhoCalc(parms,rX,rY,rZ,res,SGLaue): Mag,x0,y0,z0,sig = parms z = -((x0-rX)**2+(y0-rY)**2+(z0-rZ)**2)/(2.*sig**2) # return norm*Mag*np.exp(z)/(sig*res**3) #not slower but some faults in LS return norm*Mag*(1.+z+z**2/2.)/(sig*res**3) - + def peakFunc(parms,rX,rY,rZ,rho,res,SGLaue): Mag,x0,y0,z0,sig = parms M = rho-rhoCalc(parms,rX,rY,rZ,res,SGLaue) return M - + def peakHess(parms,rX,rY,rZ,rho,res,SGLaue): Mag,x0,y0,z0,sig = parms dMdv = np.zeros(([5,]+list(rX.shape))) @@ -4619,9 +4639,9 @@ def peakHess(parms,rX,rY,rZ,rho,res,SGLaue): Vec = np.sum(np.sum(np.sum(dMdv*(rho-rhoC),axis=3),axis=2),axis=1) dMdv = np.reshape(dMdv,(5,rX.size)) Hess = np.inner(dMdv,dMdv) - + return Vec,Hess - + SGData = generalData['SGData'] Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) peaks = [] @@ -4667,14 +4687,14 @@ def peakHess(parms,rX,rY,rZ,rho,res,SGLaue): peak = (np.array(x1[1:4])-ind)/incre peak = fixSpecialPos(peak,SGData,Amat) rho = rollMap(rho,-ind) - cent = np.ones(3)*.5 + cent = np.ones(3)*.5 dzeros = np.sqrt(np.sum(np.inner(Amat,peaks)**2,axis=0)) dcent = np.sqrt(np.sum(np.inner(Amat,peaks-cent)**2,axis=0)) if Neg: #want negative magnitudes for negative peaks return np.array(peaks),-np.array([mags,]).T,np.array([dzeros,]).T,np.array([dcent,]).T else: return np.array(peaks),np.array([mags,]).T,np.array([dzeros,]).T,np.array([dcent,]).T - + def sortArray(data,pos,reverse=False): '''data is a list of items sort by pos in list; reverse if True @@ -4695,7 +4715,7 @@ def sortArray(data,pos,reverse=False): return X def PeaksEquiv(data,Ind): - '''Find the equivalent map peaks for those selected. Works on the + '''Find the equivalent map peaks for those selected. Works on the contents of data['Map Peaks']. :param data: the phase data structure @@ -4703,12 +4723,12 @@ def PeaksEquiv(data,Ind): :returns: augmented list of peaks including those related by symmetry to the ones in Ind - ''' + ''' def Duplicate(xyz,peaks,Amat): if True in [np.allclose(np.inner(Amat,xyz),np.inner(Amat,peak),atol=0.5) for peak in peaks]: return True return False - + generalData = data['General'] Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) SGData = generalData['SGData'] @@ -4718,18 +4738,18 @@ def Duplicate(xyz,peaks,Amat): for ind in Ind: xyz = np.array(mapPeaks[ind][1:4]) xyzs = np.array([equiv[0] for equiv in G2spc.GenAtom(xyz,SGData,Move=True)]) - for jnd,xyz in enumerate(XYZ): + for jnd,xyz in enumerate(XYZ): Indx[jnd] = Duplicate(xyz,xyzs,Amat) Ind = [] for ind in Indx: if Indx[ind]: Ind.append(ind) return Ind - + def PeaksUnique(data,Ind,Sel,dlg): '''Finds the symmetry unique set of peaks from those selected. Selects - the one closest to the center of the unit cell. - Works on the contents of data['Map Peaks']. Called from OnPeaksUnique in + the one closest to the center of the unit cell. + Works on the contents of data['Map Peaks']. Called from OnPeaksUnique in GSASIIphsGUI.py, :param data: the phase data structure @@ -4738,18 +4758,18 @@ def PeaksUnique(data,Ind,Sel,dlg): :param wx object dlg: progress bar dialog box :returns: the list of symmetry unique peaks from among those given in Ind - ''' + ''' # XYZE = np.array([[equiv[0] for equiv in G2spc.GenAtom(xyz[1:4],SGData,Move=True)] for xyz in mapPeaks]) #keep this!! def noDuplicate(xyz,peaks,Amat): if True in [np.allclose(np.inner(Amat,xyz),np.inner(Amat,peak),atol=0.5) for peak in peaks]: return False return True - + generalData = data['General'] Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) SGData = generalData['SGData'] - mapPeaks = data['Map Peaks'] + mapPeaks = data['Map Peaks'] XYZ = {ind:np.array(mapPeaks[ind][1:4]) for ind in Ind} Indx = [True for ind in Ind] Unique = [] @@ -4761,7 +4781,7 @@ def noDuplicate(xyz,peaks,Amat): for jnd in Ind: # only consider peaks we have not looked at before # and were not already found to be equivalent - if jnd > ind and Indx[jnd]: + if jnd > ind and Indx[jnd]: Equiv = G2spc.GenAtom(XYZ[jnd],SGData,Move=True) xyzs = np.array([equiv[0] for equiv in Equiv]) if not noDuplicate(xyz,xyzs,Amat): @@ -4774,12 +4794,12 @@ def noDuplicate(xyz,peaks,Amat): cntr = mapPeaks[jnd][Sel] icntr = jnd Unique.append(icntr) - dlg.Update(int(ind),newmsg='Map peak no. %d processed'%ind) + dlg.Update(int(ind),newmsg='Map peak no. %d processed'%ind) return Unique def AtomsCollect(data,Ind,Sel): '''Finds the symmetry set of atoms for those selected. Selects - the one closest to the selected part of the unit cell for the + the one closest to the selected part of the unit cell for the selected atoms :param data: the phase data structure @@ -4787,11 +4807,11 @@ def AtomsCollect(data,Ind,Sel): :param int Sel: an index with the selected plane or location in the unit cell to find atoms closest to - :returns: the list of unique atoms where selected atoms may have been + :returns: the list of unique atoms where selected atoms may have been replaced. Anisotropic Uij's are transformed - ''' - cx,ct,cs,ci = getAtomPtrs(data) - cent = np.ones(3)*.5 + ''' + cx,ct,cs,ci = getAtomPtrs(data) + cent = np.ones(3)*.5 generalData = data['General'] Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) SGData = generalData['SGData'] @@ -4818,7 +4838,7 @@ def AtomsCollect(data,Ind,Sel): def DoWilsonStat(refList,Super,normEle,Inst): ns = 0 - if Super: + if Super: ns=1 Eldata = G2el.GetElInfo(normEle,Inst) RefList = np.array(refList).T @@ -4860,238 +4880,238 @@ def DoWilsonStat(refList,Super,normEle,Inst): def getCWsig(ins,pos): '''get CW peak profile sigma^2 - - :param dict ins: instrument parameters with at least 'U', 'V', & 'W' + + :param dict ins: instrument parameters with at least 'U', 'V', & 'W' as values only :param float pos: 2-theta of peak :returns: float getCWsig: peak sigma^2 - + ''' tp = tand(pos/2.0) return ins['U']*tp**2+ins['V']*tp+ins['W'] - + def getCWsigDeriv(pos): '''get derivatives of CW peak profile sigma^2 wrt U,V, & W - + :param float pos: 2-theta of peak - + :returns: list getCWsigDeriv: d(sig^2)/dU, d(sig)/dV & d(sig)/dW - + ''' tp = tand(pos/2.0) return tp**2,tp,1.0 - + def getCWgam(ins,pos): '''get CW peak profile gamma - + :param dict ins: instrument parameters with at least 'X', 'Y' & 'Z' as values only :param float pos: 2-theta of peak :returns: float getCWgam: peak gamma - + ''' return ins['X']/cosd(pos/2.0)+ins['Y']*tand(pos/2.0)+ins.get('Z',0.0) - + def getCWgamDeriv(pos): '''get derivatives of CW peak profile gamma wrt X, Y & Z - + :param float pos: 2-theta of peak - + :returns: list getCWgamDeriv: d(gam)/dX & d(gam)/dY - + ''' return 1./cosd(pos/2.0),tand(pos/2.0),1.0 def getEDsig(ins,pos): '''get ED peak profile sig - + :param dict ins: instrument parameters with at least 'A', 'B' & 'C' as values only :param float pos: energy of peak as keV :returns: float getEDsig: peak sigma^2 im keV**2 - + ''' return ins['A']*pos**2+ins['B']*pos+ins['C'] def getEDsigDeriv(ins,pos): '''get derivatives of ED peak profile sig wrt A, B & C - + :param float pos: energy of peak in keV - + :returns: list getEDsigDeriv: d(sig)/dA, d(sig)/dB & d(sig)/dC, - + ''' return pos**2,pos,1.0 - + def getEDgam(ins,pos): '''get ED peak profile gam - + :param dict ins: instrument parameters with at least X, Y & Z as values only :param float pos: energy of peak as keV :returns: float getEDsig: peak gam im keV - + ''' return ins['X']*pos**2+ins['Y']*pos+ins['Z'] def getEDgamDeriv(ins,pos): '''get derivatives of ED peak profile gam wrt X, Y & Z - + :param float pos: energy of peak in keV - + :returns: list getEDsigDeriv: d(gam)/dX, d(gam)/dY & d(gam)/dZ, - + ''' return pos**2,pos,1.0 - + def getTOFsig(ins,dsp): '''get TOF peak profile sigma^2 - + :param dict ins: instrument parameters with at least 'sig-0', 'sig-1' & 'sig-q' as values only :param float dsp: d-spacing of peak - + :returns: float getTOFsig: peak sigma^2 - + ''' return ins['sig-0']+ins['sig-1']*dsp**2+ins['sig-2']*dsp**4+ins['sig-q']*dsp - + def getTOFsigDeriv(dsp): '''get derivatives of TOF peak profile sigma^2 wrt sig-0, sig-1, & sig-q - + :param float dsp: d-spacing of peak - + :returns: list getTOFsigDeriv: d(sig0/d(sig-0), d(sig)/d(sig-1) & d(sig)/d(sig-q) - + ''' return 1.0,dsp**2,dsp**4,dsp - + def getTOFgamma(ins,dsp): '''get TOF peak profile gamma - + :param dict ins: instrument parameters with at least 'X', 'Y' & 'Z' as values only :param float dsp: d-spacing of peak - + :returns: float getTOFgamma: peak gamma - + ''' return ins['Z']+ins['X']*dsp+ins['Y']*dsp**2 - + def getTOFgammaDeriv(dsp): '''get derivatives of TOF peak profile gamma wrt X, Y & Z - + :param float dsp: d-spacing of peak - + :returns: list getTOFgammaDeriv: d(gam)/dX & d(gam)/dY - + ''' return dsp,dsp**2,1.0 - + def getTOFbeta(ins,dsp): '''get TOF peak profile beta - + :param dict ins: instrument parameters with at least 'beat-0', 'beta-1' & 'beta-q' as values only :param float dsp: d-spacing of peak - + :returns: float getTOFbeta: peak beat - + ''' return ins['beta-0']+ins['beta-1']/dsp**4+ins['beta-q']/dsp**2 - + def getTOFbetaDeriv(dsp): '''get derivatives of TOF peak profile beta wrt beta-0, beta-1, & beat-q - + :param float dsp: d-spacing of peak - + :returns: list getTOFbetaDeriv: d(beta)/d(beat-0), d(beta)/d(beta-1) & d(beta)/d(beta-q) - + ''' return 1.0,1./dsp**4,1./dsp**2 - + def getTOFalpha(ins,dsp): '''get TOF peak profile alpha - + :param dict ins: instrument parameters with at least 'alpha' as values only :param float dsp: d-spacing of peak - + :returns: float getTOFalpha: peak alpha - + ''' return ins['alpha']/dsp - + def getTOFalphaDeriv(dsp): '''get alpha derivatives of TOF peak profile - + :param float dsp: d-spacing of peak - + :returns: float getTOFalphaDeriv: d(alp)/d(alpha) - + ''' return 1./dsp - + def getPinkAlpha(ins,tth): '''get pink neutron peak alpha profile - + :param dict ins: instrument parameters with at least 'alpha' as values only :param float tth: 2-theta of peak - + :returns: float getPinkNalpha: peak alpha - + ''' return ins['alpha-0']+ ins['alpha-1']*sind(tth/2.) - + def getPinkAlphaDeriv(tth): - '''get alpha derivatives of pink neutron peak profile - + '''get alpha derivatives of pink neutron peak profile + :param float tth: 2-theta of peak - + :returns: float getPinkNalphaDeriv: d(alp)/d(alpha-0), d(alp)/d(alpha-1) - + ''' return 1.0,sind(tth/2.) - + def getPinkBeta(ins,tth): '''get pink neutron peak profile beta - + :param dict ins: instrument parameters with at least 'beta-0' & 'beta-1' as values only :param float tth: 2-theta of peak - + :returns: float getPinkbeta: peak beta - + ''' return ins['beta-0']+ins['beta-1']*sind(tth/2.) - + def getPinkBetaDeriv(tth): '''get beta derivatives of pink neutron peak profile - + :param float tth: 2-theta of peak - + :returns: list getPinkNbetaDeriv: d(beta)/d(beta-0) & d(beta)/d(beta-1) - + ''' return 1.0,sind(tth/2.) - + def setPeakparms(Parms,Parms2,pos,mag,ifQ=False,useFit=False): '''set starting peak parameters for single peak fits from plot selection or auto selection - + :param dict Parms: instrument parameters dictionary :param dict Parms2: table lookup for TOF profile coefficients :param float pos: peak position in 2-theta, TOF or Q (ifQ=True) :param float mag: peak top magnitude from pick :param bool ifQ: True if pos in Q :param bool useFit: True if use fitted CW Parms values (not defaults) - + :returns: list XY: peak list entry: for CW: [pos,0,mag,1,sig,0,gam,0] for TOF: [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0] for Pink: [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0] NB: mag refinement set by default, all others off - + ''' ind = 0 if useFit: @@ -5123,7 +5143,7 @@ def setPeakparms(Parms,Parms2,pos,mag,ifQ=False,useFit=False): if ifQ: #qplot - convert back to 2-theta pos = 2.0*asind(pos*getWave(Parms)/(4*math.pi)) sig = getCWsig(ins,pos) - gam = getCWgam(ins,pos) + gam = getCWgam(ins,pos) XY = [pos,0, mag,1, sig,0, gam,0] #default refine intensity 1st elif Parms['Type'][0][2] in ['A','B']: for x in ['U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1']: @@ -5133,16 +5153,16 @@ def setPeakparms(Parms,Parms2,pos,mag,ifQ=False,useFit=False): alp = getPinkAlpha(ins,pos) bet = getPinkBeta(ins,pos) sig = getCWsig(ins,pos) - gam = getCWgam(ins,pos) + gam = getCWgam(ins,pos) XY = [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0] #default refine intensity 1st elif 'E' in Parms['Type'][0]: for x in ['A','B','C','X','Y','Z']: ins[x] = Parms.get(x,[0.0,0.0])[ind] sig = getEDsig(ins,pos) - gam = getEDgam(ins,pos) + gam = getEDgam(ins,pos) XY = [pos,0,mag,1,sig,0,gam,0] #default refine intensity 1st return XY - + ################################################################################ #### MC/SA stuff ################################################################################ @@ -5211,7 +5231,7 @@ def getstart_temp(self, best_state): self.T0 = (fmax-fmin)*1.5 return best_state.x - + def set_range(self,x0,frac): delrange = frac*(self.upper-self.lower) self.upper = x0+delrange @@ -5256,18 +5276,18 @@ class log_sa(base_schedule): #OK def init(self,**options): self.__dict__.update(options) - + def update_guess(self,x0): #same as default #TODO - is this a reasonable update procedure? u = squeeze(random.uniform(0.0, 1.0, size=self.dims)) T = self.T xc = (sign(u-0.5)*T*((1+1.0/T)**abs(2*u-1)-1.0)+1.0)/2.0 xnew = xc*(self.upper - self.lower)+self.lower return xnew - + def update_temp(self): self.k += 1 self.T = self.T0*self.slope**self.k - + class _state(object): def __init__(self): self.x = None @@ -5292,8 +5312,8 @@ def makeTsched(data): sched.update_temp() Tsched.append(sched.T) return Tsched[1:] - -def anneal(func, x0, args=(), schedule='fast', + +def anneal(func, x0, args=(), schedule='fast', T0=None, Tf=1e-12, maxeval=None, maxaccept=None, maxiter=400, feps=1e-6, quench=1.0, c=1.0, lower=-100, upper=100, dwell=50, slope=0.9,ranStart=False, @@ -5307,34 +5327,34 @@ def anneal(func, x0, args=(), schedule='fast', Function to be optimized. :param ndarray x0: Initial guess. - :param tuple args: + :param tuple args: Extra parameters to `func`. - :param base_schedule schedule: + :param base_schedule schedule: Annealing schedule to use (a class). - :param float T0: + :param float T0: Initial Temperature (estimated as 1.2 times the largest cost-function deviation over random points in the range). - :param float Tf: + :param float Tf: Final goal temperature. - :param int maxeval: + :param int maxeval: Maximum function evaluations. :param int maxaccept: Maximum changes to accept. - :param int maxiter: + :param int maxiter: Maximum cooling iterations. :param float feps: Stopping relative error tolerance for the function value in last four coolings. :param float quench,c: Parameters to alter fast_sa schedule. - :param float/ndarray lower,upper: + :param float/ndarray lower,upper: Lower and upper bounds on `x`. :param int dwell: The number of times to search the space at each temperature. - :param float slope: + :param float slope: Parameter for log schedule :param bool ranStart=False: - True for set 10% of ranges about x + True for set 10% of ranges about x :returns: (xmin, Jmin, T, feval, iters, accept, retval) where @@ -5390,17 +5410,17 @@ def anneal(func, x0, args=(), schedule='fast', T_new = T0 * exp(-c * k**quench) ''' - + ''' Scipy license: Copyright (c) 2001, 2002 Enthought, Inc. All rights reserved. - + Copyright (c) 2003-2016 SciPy Developers. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. Redistributions in binary form must reproduce the above copyright @@ -5427,7 +5447,7 @@ def anneal(func, x0, args=(), schedule='fast', else: x0 = random.uniform(size=len(x0))*(upper-lower) + lower best_state.x = None - best_state.cost = numpy.Inf + best_state.cost = numpy.inf last_state.x = asarray(x0).copy() fval = func(x0,*args) @@ -5457,7 +5477,7 @@ def anneal(func, x0, args=(), schedule='fast', best_state.cost = last_state.cost bestn = n if best_state.cost < 1.0 and autoRan: - schedule.set_range(x0,best_state.cost/2.) + schedule.set_range(x0,best_state.cost/2.) if dlg: GoOn = dlg.Update(int(min(100.,best_state.cost*100)), newmsg='%s%8.5f, %s%d\n%s%8.4f%s'%('Temperature =',schedule.T, \ @@ -5527,7 +5547,7 @@ def worker(iCyc,data,RBdata,reflType,reflData,covData,out_q,out_t,out_n,nprocess def MPmcsaSearch(nCyc,data,RBdata,reflType,reflData,covData,nprocs): import multiprocessing as mp - + out_q = mp.Queue() out_t = mp.Queue() out_n = mp.Queue() @@ -5552,19 +5572,19 @@ def MPmcsaSearch(nCyc,data,RBdata,reflType,reflData,covData,nprocs): def mcsaSearch(data,RBdata,reflType,reflData,covData,pgbar,start=True): '''default doc string - + :param type name: description - + :returns: type name: description ''' - + class RandomDisplacementBounds(object): '''random displacement with bounds''' def __init__(self, xmin, xmax, stepsize=0.5): self.xmin = xmin self.xmax = xmax self.stepsize = stepsize - + def __call__(self, x): '''take a random step but ensure the new position is within the bounds''' while True: @@ -5574,11 +5594,11 @@ def __call__(self, x): if np.all(xnew < self.xmax) and np.all(xnew > self.xmin): break return xnew - + global tsum,nsum tsum = 0. nsum = 0 - + def getMDparms(item,pfx,parmDict,varyList): parmDict[pfx+'MDaxis'] = item['axis'] parmDict[pfx+'MDval'] = item['Coef'][0] @@ -5587,10 +5607,10 @@ def getMDparms(item,pfx,parmDict,varyList): limits = item['Coef'][2] lower.append(limits[0]) upper.append(limits[1]) - + def getAtomparms(item,pfx,aTypes,SGData,parmDict,varyList): parmDict[pfx+'Atype'] = item['atType'] - aTypes |= set([item['atType'],]) + aTypes |= set([item['atType'],]) pstr = ['Ax','Ay','Az'] XYZ = [0,0,0] for i in range(3): @@ -5603,7 +5623,7 @@ def getAtomparms(item,pfx,aTypes,SGData,parmDict,varyList): lower.append(limits[0]) upper.append(limits[1]) parmDict[pfx+'Amul'] = len(list(G2spc.GenAtom(XYZ,SGData))) - + def getRBparms(item,mfx,aTypes,RBdata,SGData,atNo,parmDict,varyList): parmDict[mfx+'MolCent'] = item['MolCent'] parmDict[mfx+'RBId'] = item['RBId'] @@ -5649,13 +5669,13 @@ def getRBparms(item,mfx,aTypes,RBdata,SGData,atNo,parmDict,varyList): aTypes |= set(atypes) atNo += len(atypes) return atNo - + def GetAtomM(Xdata,SGData): Mdata = [] for xyz in Xdata: Mdata.append(float(len(list(G2spc.GenAtom(xyz,SGData))))) return np.array(Mdata) - + def GetAtomT(RBdata,parmDict): 'Needs a doc string' atNo = parmDict['atNo'] @@ -5687,7 +5707,7 @@ def GetAtomT(RBdata,parmDict): else: continue #skips March Dollase return Tdata - + def GetAtomX(RBdata,parmDict): 'Needs a doc string' Bmat = parmDict['Bmat'] @@ -5733,7 +5753,7 @@ def GetAtomX(RBdata,parmDict): else: continue #skips March Dollase return Xdata.T - + def getAllTX(Tdata,Mdata,Xdata,SGM,SGT): allX = np.inner(Xdata,SGM)+SGT allT = np.repeat(Tdata,allX.shape[1]) @@ -5745,7 +5765,7 @@ def getAllX(Xdata,SGM,SGT): allX = np.inner(Xdata,SGM)+SGT allX = np.reshape(allX,(-1,3)) return allX - + def normQuaternions(RBdata,parmDict,varyList,values): for iObj in range(parmDict['nObj']): pfx = str(iObj)+':' @@ -5757,7 +5777,7 @@ def normQuaternions(RBdata,parmDict,varyList,values): parmDict[pfx+name] = V[i-1] else: parmDict[pfx+name] = A - + def mcsaCalc(values,refList,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict): ''' Compute structure factors for all h,k,l for phase input: @@ -5766,13 +5786,13 @@ def mcsaCalc(values,refList,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict): ifInv: bool True if centrosymmetric allFF: array[nref,natoms] each value is mult*FF(H)/max(mult) RBdata: [dict] rigid body dictionary - varyList: [list] names of varied parameters in MC/SA (not used here) + varyList: [list] names of varied parameters in MC/SA (not used here) ParmDict: [dict] problem parameters puts result F^2 in each ref[5] in refList returns: delt-F*rcov*delt-F/sum(Fo^2) - ''' - + ''' + global tsum,nsum t0 = time.time() parmDict.update(dict(zip(varyList,values))) #update parameter tables @@ -5794,7 +5814,7 @@ def mcsaCalc(values,refList,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict): tsum += (time.time()-t0) nsum += 1 return np.sqrt(M/np.sum(refList[4]**2)) - + def MCSAcallback(x, f,accept): return not pgbar.Update(int(min(100,f*100)), newmsg='%s%8.4f%s'%('MC/SA Residual:',int(f*100),'%'))[0] @@ -5824,7 +5844,7 @@ def MCSAcallback(x, f,accept): name = pfx+pstr[i] parmDict[name] = atm[cx+i] atNo += 1 - parmDict['nfixAt'] = len(fixAtoms) + parmDict['nfixAt'] = len(fixAtoms) MCSA = generalData['MCSA controls'] reflName = MCSA['Data source'] MCSAObjs = data['MCSA']['Models'] #list of MCSA models @@ -5946,7 +5966,7 @@ def MCSAcallback(x, f,accept): results = anneal(mcsaCalc,x0,args=(refs,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict), schedule=MCSA['Algorithm'], dwell=MCSA['Annealing'][2],maxiter=10000, T0=T0, Tf=MCSA['Annealing'][1], - quench=MCSA['fast parms'][0], c=MCSA['fast parms'][1], + quench=MCSA['fast parms'][0], c=MCSA['fast parms'][1], lower=lower, upper=upper, slope=MCSA['log slope'],ranStart=MCSA.get('ranStart',False), ranRange=MCSA.get('ranRange',10.)/100.,autoRan=MCSA.get('autoRan',False),dlg=pgbar) G2fil.G2Print (' Acceptance rate: %.2f%% MCSA residual: %.2f%%'%(100.*results[5]/results[3],100.*results[1])) @@ -5964,7 +5984,7 @@ def MCSAcallback(x, f,accept): import scipy.cluster.hierarchy as SCH - + ################################################################################ #### Quaternion & other geometry stuff ################################################################################ @@ -5972,12 +5992,12 @@ def MCSAcallback(x, f,accept): def Cart2Polar(X,Y,Z): ''' convert Cartesian to polar coordinates in deg ''' - + R = np.sqrt(X**2+Y**2+Z**2) Pl = acosd(Z/R) Az = atan2d(Y,X) return R,Az,Pl - + def Polar2Cart(R,Az,Pl): '''Convert polar angles in deg to Cartesian coordinates ''' @@ -6013,26 +6033,26 @@ def prodQQ(QA,QB): D[1] = QA[0]*QB[1]+QA[1]*QB[0]+QA[2]*QB[3]-QA[3]*QB[2] D[2] = QA[0]*QB[2]-QA[1]*QB[3]+QA[2]*QB[0]+QA[3]*QB[1] D[3] = QA[0]*QB[3]+QA[1]*QB[2]-QA[2]*QB[1]+QA[3]*QB[0] - + # D[0] = QA[0]*QB[0]-np.dot(QA[1:],QB[1:]) # D[1:] = QA[0]*QB[1:]+QB[0]*QA[1:]+np.cross(QA[1:],QB[1:]) - + return D - + def normQ(QA): ''' get length of quaternion & normalize it q=r+ai+bj+ck ''' n = np.sqrt(np.sum(np.array(QA)**2)) return QA/n - + def invQ(Q): ''' get inverse of quaternion q=r+ai+bj+ck; q* = r-ai-bj-ck ''' return Q*np.array([1,-1,-1,-1]) - + def prodQVQ(Q,V): ''' compute the quaternion vector rotation qvq-1 = v' @@ -6049,8 +6069,8 @@ def prodQVQ(Q,V): T10 = -Q[3]*Q[3] M = np.array([[T8+T10,T6-T4,T3+T7],[T4+T6,T5+T10,T9-T2],[T7-T3,T2+T9,T5+T8]]) VP = 2.*np.inner(V,M) - return VP+V - + return VP+V + def Q2Mat(Q): ''' make rotation matrix from quaternion q=r+ai+bj+ck @@ -6070,7 +6090,7 @@ def Q2Mat(Q): [2*(ad+bc), aa-bb+cc-dd, 2.*(cd-ab)], [2*(bd-ac), 2.*(ab+cd), aa-bb-cc+dd]] return np.around(np.array(M),8) - + def AV2Q(A,V): ''' convert angle (radians) & vector to quaternion q=r+ai+bj+ck @@ -6087,7 +6107,7 @@ def AV2Q(A,V): else: Q[3] = 1. return Q - + def AVdeg2Q(A,V): ''' convert angle (degrees) & vector to quaternion q=r+ai+bj+ck @@ -6104,7 +6124,7 @@ def AVdeg2Q(A,V): else: Q[3] = 1. return Q - + def Q2AVdeg(Q): ''' convert quaternion to angle (degrees 0-360) & normalized vector q=r+ai+bj+ck @@ -6113,7 +6133,7 @@ def Q2AVdeg(Q): V = np.array(Q[1:]) V = V/sind(A/2.) return A,V - + def Q2AV(Q): ''' convert quaternion to angle (radians 0-2pi) & normalized vector q=r+ai+bj+ck @@ -6122,7 +6142,7 @@ def Q2AV(Q): V = np.array(Q[1:]) V = V/np.sin(A/2.) return A,V - + def randomQ(r0,r1,r2,r3): ''' create random quaternion from 4 random numbers in range (-1,1) ''' @@ -6136,12 +6156,12 @@ def randomQ(r0,r1,r2,r3): sum += Q[2]**2 Q[3] = np.sqrt(1.-sum)*np.where(r3<0.,-1.,1.) return Q - + def randomAVdeg(r0,r1,r2,r3): ''' create random angle (deg),vector from 4 random number in range (-1,1) ''' return Q2AVdeg(randomQ(r0,r1,r2,r3)) - + def makeQuat(A,B,C): ''' Make quaternion from rotation of A vector to B vector about C axis @@ -6177,7 +6197,7 @@ def makeQuat(A,B,C): def make2Quat(A,B): ''' Make quaternion from rotation of A vector to B vector - + :param np.array A,B: Cartesian 3-vectors :returns: quaternion & rotation angle in radians q=r+ai+bj+ck ''' diff --git a/GSASII/GSASIImiscGUI.py b/GSASII/GSASIImiscGUI.py index 3424c6716..b07058b4c 100644 --- a/GSASII/GSASIImiscGUI.py +++ b/GSASII/GSASIImiscGUI.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- ''' -Misc routines for GUI-based input and output, including image reading follow. +Misc routines for GUI-based input and output, including image reading follow. This module contains quite a bit of older code that could use some attention, or possibly movement into other modules. It was previously called GSASIIIO.py -which is why most modules reference it as G2IO. +which is why most modules reference it as G2IO. ''' @@ -25,7 +25,7 @@ import re import copy import platform -import pickle as cPickle +import pickle import sys import random as ran @@ -33,19 +33,19 @@ import numpy.ma as ma import wx -import GSASIIpath -import GSASIIdataGUI as G2gd -import GSASIIobj as G2obj +from . import GSASIIpath +from . import GSASIIdataGUI as G2gd +from . import GSASIIobj as G2obj #import GSASIIpwdGUI as G2pdG -import GSASIIimgGUI as G2imG -import GSASIIElem as G2el -import GSASIIfiles as G2fil -import GSASIIctrlGUI as G2G -import GSASIImath as G2mth -import GSASIIElem as G2elem -import GSASIIspc as G2spc -import GSASIIlattice as G2lat -import GSASIIpwd as G2pwd +from . import GSASIIimgGUI as G2imG +from . import GSASIIElem as G2el +from . import GSASIIfiles as G2fil +from . import GSASIIctrlGUI as G2G +from . import GSASIImath as G2mth +from . import GSASIIElem as G2elem +from . import GSASIIspc as G2spc +from . import GSASIIlattice as G2lat +from . import GSASIIpwd as G2pwd DEBUG = False #=True for various prints TRANSP = False #=true to transpose images for testing @@ -58,7 +58,7 @@ def FileDlgFixExt(dlg,file): if ext not in file: file += ext return file - + def GetPowderPeaks(fileName): 'Read powder peaks from a file' sind = lambda x: math.sin(x*math.pi/180.) @@ -81,7 +81,7 @@ def GetPowderPeaks(fileName): File.close() if Comments: print ('Comments on file:') - for Comment in Comments: + for Comment in Comments: print (Comment) if 'wavelength' in Comment: wave = float(Comment.split('=')[1]) @@ -166,7 +166,7 @@ def GetCheckImageFile(G2frame,treeId): #GSASIIpath.IPyBreak() if not os.path.exists(imagefile): - # note that this fails (at least on Mac) to get an image during the GUI initialization + # note that this fails (at least on Mac) to get an image during the GUI initialization prevnam = os.path.split(imagefile)[1] prevext = os.path.splitext(imagefile)[1] wildcard = 'Image format (*'+prevext+')|*'+prevext @@ -192,7 +192,7 @@ def onClose(event): h,w = Image.size[:2] mainsizer.Add(wx.StaticText(dlg,wx.ID_ANY,'File '+str(filename)+'\nImage size: '+str(h)+' x '+str(w)), 0,wx.ALIGN_LEFT|wx.ALL, 2) - + vsizer = wx.BoxSizer(wx.HORIZONTAL) vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Wavelength (\xC5) '), 0,wx.ALIGN_LEFT|wx.ALL, 2) @@ -244,7 +244,7 @@ def onClose(event): dlg.SetSizer(mainsizer) dlg.CenterOnParent() dlg.ShowModal() - + def LoadImage2Tree(imagefile,G2frame,Comments,Data,Npix,Image): '''Load an image into the tree. Saves the location of the image, as well as the ImageTag (where there is more than one image in the file), if defined. @@ -318,7 +318,7 @@ def LoadImage2Tree(imagefile,G2frame,Comments,Data,Npix,Image): Data['centerAzm'] = False Data['fullIntegrate'] = GSASIIpath.GetConfigValue('fullIntegrate',True) Data['setRings'] = False - Data['background image'] = ['',-1.0] + Data['background image'] = ['',-1.0] Data['dark image'] = ['',-1.0] Data['Flat Bkg'] = 0.0 Data['Oblique'] = [0.5,False] @@ -346,9 +346,9 @@ def ReadImages(G2frame,imagefile): be made in only one place. :param wx.Frame G2frame: main GSAS-II Frame and data object. - :param str imagefile: name of image file + :param str imagefile: name of image file - :returns: a list of the id's of the IMG tree items created + :returns: a list of the id's of the IMG tree items created ''' # determine which formats are compatible with this file primaryReaders = [] @@ -369,7 +369,7 @@ def ReadImages(G2frame,imagefile): rd.errors = "" # clear out any old errors if not rd.ContentsValidator(imagefile): # rejected on cursory check errorReport += "\n "+rd.formatName + ' validator error' - if rd.errors: + if rd.errors: errorReport += ': '+rd.errors continue ParentFrame = G2frame @@ -409,7 +409,7 @@ def ReadImages(G2frame,imagefile): print('Error reading file '+imagefile) print('Error messages(s)\n'+errorReport) return [] - #raise Exception('No image read') + #raise Exception('No image read') def SaveMultipleImg(G2frame): if not G2frame.GPXtree.GetCount(): @@ -451,11 +451,11 @@ def SaveMultipleImg(G2frame): for key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']: File.write(key+':'+str(mask[key])+'\n') File.close() - + def PutG2Image(filename,Comments,Data,Npix,image): 'Write an image as a python pickle - might be better as an .edf file?' File = open(filename,'wb') - cPickle.dump([Comments,Data,Npix,image],File,2) + pickle.dump([Comments,Data,Npix,image],File,2) File.close() return @@ -464,7 +464,7 @@ def PutG2Image(filename,Comments,Data,Npix,image): objectScanIgnore += [ma.MaskedArray] # fails in doc builds except AttributeError: pass - + def objectScan(data,tag,indexStack=[]): '''Recursively scan an object looking for unexpected data types. This is used in debug mode to scan .gpx files for objects we did not @@ -486,6 +486,11 @@ def objectScan(data,tag,indexStack=[]): return None elif type(data) in objectScanIgnore: return None + # not always recognized: + elif 'GSASIIobj.G2VarObj' in str(type(data)): + return None + elif 'GSASIIobj.ExpressionObj' in str(type(data)): + return None else: s = 'unexpected object in '+tag for i in indexStack: @@ -498,12 +503,9 @@ def objectScan(data,tag,indexStack=[]): if "gdi.Colour" in str(type(data)): return tuple(data) return - -def cPickleLoad(fp): - if '2' in platform.python_version_tuple()[0]: - return cPickle.load(fp) - else: - return cPickle.load(fp,encoding='latin-1') + +def pickleLoad(fp): + return pickle.load(fp,encoding='latin-1') def ProjFileOpen(G2frame,showProvenance=True): 'Read a GSAS-II project file and load into the G2 data tree' @@ -530,22 +532,22 @@ def ProjFileOpen(G2frame,showProvenance=True): if result == wx.ID_YES: updateFromSeq = True fp = open(GPXphase,'rb') - data = cPickleLoad(fp) # first block in file should be Phases + data = pickleLoad(fp) # first block in file should be Phases if data[0][0] != 'Phases': raise Exception('Unexpected block in {} file. How did this happen?' .format(GPXphase)) Phases = {} for name,vals in data[1:]: Phases[name] = vals - name,CovData = cPickleLoad(fp)[0] # 2nd block in file should be Covariance - name,RigidBodies = cPickleLoad(fp)[0] # 3rd block in file should be Rigid Bodies + name,CovData = pickleLoad(fp)[0] # 2nd block in file should be Covariance + name,RigidBodies = pickleLoad(fp)[0] # 3rd block in file should be Rigid Bodies fp.close() # index the histogram updates hist = open(GPXhist,'rb') try: while True: loc = hist.tell() - datum = cPickleLoad(hist)[0] + datum = pickleLoad(hist)[0] tmpHistIndex[datum[0]] = loc except EOFError: pass @@ -558,7 +560,7 @@ def ProjFileOpen(G2frame,showProvenance=True): sizeList = {} while True: try: - data = cPickleLoad(filep) + data = pickleLoad(filep) except EOFError: break datum = data[0] @@ -568,7 +570,7 @@ def ProjFileOpen(G2frame,showProvenance=True): # scan the GPX file for unexpected objects if GSASIIpath.GetConfigValue('debug'): global unexpectedObject - unexpectedObject = False + unexpectedObject = False objectScan(data,'tree item "{}" entry '.format(datum[0])) #if unexpectedObject: # print(datum[0]) @@ -586,7 +588,7 @@ def ProjFileOpen(G2frame,showProvenance=True): data[0][1] = RigidBodies elif updateFromSeq and datum[0] in tmpHistIndex: hist.seek(tmpHistIndex[datum[0]]) - hdata = cPickleLoad(hist) + hdata = pickleLoad(hist) if data[0][0] != hdata[0][0]: print('Error! Updating {} with {}'.format(data[0][0],hdata[0][0])) datum = hdata[0] @@ -595,16 +597,16 @@ def ProjFileOpen(G2frame,showProvenance=True): for j,(name,val) in enumerate(data[1:]): if name not in xferItems: continue data[j+1][1] = hdata[hItems[name]][1] - if datum[0].startswith('PWDR'): + if datum[0].startswith('PWDR'): if 'ranId' not in datum[1][0]: # patch: add random Id if not present datum[1][0]['ranId'] = ran.randint(0,sys.maxsize) G2frame.GPXtree.SetItemPyData(Id,datum[1][:3]) #temp. trim off junk (patch?) - elif datum[0].startswith('HKLF'): + elif datum[0].startswith('HKLF'): if 'ranId' not in datum[1][0]: # patch: add random Id if not present datum[1][0]['ranId'] = ran.randint(0,sys.maxsize) G2frame.GPXtree.SetItemPyData(Id,datum[1]) else: - G2frame.GPXtree.SetItemPyData(Id,datum[1]) + G2frame.GPXtree.SetItemPyData(Id,datum[1]) if datum[0] == 'Controls' and 'LastSavedUsing' in datum[1]: LastSavedUsing = datum[1]['LastSavedUsing'] if datum[0] == 'Controls' and 'PythonVersions' in datum[1] and GSASIIpath.GetConfigValue('debug') and showProvenance: @@ -684,7 +686,7 @@ def ProjFileOpen(G2frame,showProvenance=True): G2G.updateNotifier(G2frame,int(LastSavedUsing.split()[0])) except: pass - + def ProjFileSave(G2frame): 'Save a GSAS-II project file' if not G2frame.GPXtree.IsEmpty(): @@ -695,20 +697,21 @@ def ProjFileSave(G2frame): return print ('save to file: '+G2frame.GSASprojectfile) # stick the file name into the tree and version info into tree so they are saved. - # (Controls should always be created at this point) + # (Controls should always have been created in tree at this point) try: Controls = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + Controls['PythonVersions'] = G2frame.PackageVersions Controls['LastSavedAs'] = os.path.abspath(G2frame.GSASprojectfile) - Controls['LastSavedUsing'] = str(GSASIIpath.GetVersionNumber()) + Controls['LastSavedUsing'] = f"#{GSASIIpath.GetVersionNumber()}, {GSASIIpath.GetVersionTag()}" if GSASIIpath.HowIsG2Installed().startswith('git'): - try: - g2repo = GSASIIpath.openGitRepo(GSASIIpath.path2GSAS2) - commit = g2repo.head.commit - Controls['LastSavedUsing'] += f" git {commit.hexsha[:6]}" - except: - pass - Controls['PythonVersions'] = G2frame.PackageVersions + g2repo = GSASIIpath.openGitRepo(GSASIIpath.path2GSAS2) + commit = g2repo.head.commit + Controls['LastSavedUsing'] += f" git {commit.hexsha[:8]}" + else: + gv = getSavedVersionInfo() + if gv is not None: + Controls['LastSavedUsing'] += f" static {gv.git_version[:8]}" except: pass wx.BeginBusyCursor() @@ -718,16 +721,16 @@ def ProjFileSave(G2frame): data = [] name = G2frame.GPXtree.GetItemText(item) if name.startswith('Hist/Phase'): # skip over this - item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) + item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) continue data.append([name,G2frame.GPXtree.GetItemPyData(item)]) item2, cookie2 = G2frame.GPXtree.GetFirstChild(item) while item2: name = G2frame.GPXtree.GetItemText(item2) data.append([name,G2frame.GPXtree.GetItemPyData(item2)]) - item2, cookie2 = G2frame.GPXtree.GetNextChild(item, cookie2) - item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) - cPickle.dump(data,file,2) + item2, cookie2 = G2frame.GPXtree.GetNextChild(item, cookie2) + item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) + pickle.dump(data,file,2) file.close() pth = os.path.split(os.path.abspath(G2frame.GSASprojectfile))[0] if GSASIIpath.GetConfigValue('Save_paths'): G2G.SaveGPXdirectory(pth) @@ -752,20 +755,20 @@ def SaveIntegration(G2frame,PickId,data,Overwrite=False): Comments.append('Dark image = %s\n'%str(data['dark image'])) Comments.append('Background image = %s\n'%str(data['background image'])) Comments.append('Gain map = %s\n'%str(data['Gain map'])) - + if 'PWDR' in name: if 'target' in data: - names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] + names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] codes = [0 for i in range(14)] else: if data.get('IfPink',False): names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1','Azimuth'] codes = [0 for i in range(15)] else: - names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] + names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] codes = [0 for i in range(12)] elif 'SASD' in name: - names = ['Type','Lam','Zero','Azimuth'] + names = ['Type','Lam','Zero','Azimuth'] codes = [0 for i in range(4)] X = 4.*np.pi*npsind(X/2.)/data['wavelength'] #convert to q Xminmax = [X[0],X[-1]] @@ -796,7 +799,7 @@ def SaveIntegration(G2frame,PickId,data,Overwrite=False): Sample['Omega'] = data['GonioAngles'][0] Sample['Chi'] = data['GonioAngles'][1] Sample['Phi'] = data['GonioAngles'][2] - Sample['Azimuth'] = (azm+dazm)%360. #put here as bin center + Sample['Azimuth'] = (azm+dazm)%360. #put here as bin center polariz = data['PolaVal'][0] for item in Comments: for key in ('Temperature','Pressure','Time','FreePrm1','FreePrm2','FreePrm3','Omega', @@ -828,7 +831,7 @@ def SaveIntegration(G2frame,PickId,data,Overwrite=False): W = np.where(Y>0.,1./Y,1.e-6) #probably not true Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Aname) G2frame.IntgOutList.append(Id) - G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments) + G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Limits'),copy.deepcopy([tuple(Xminmax),Xminmax])) if 'PWDR' in Aname: G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Background'),[['chebyschev-1',1,3,1.0,0.0,0.0], @@ -843,7 +846,7 @@ def SaveIntegration(G2frame,PickId,data,Overwrite=False): G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Index Peak List'),[[],[]]) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Unit Cells List'),[]) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection Lists'),{}) - elif 'SASD' in Aname: + elif 'SASD' in Aname: G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Substances'),G2pwd.SetDefaultSubstances()) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample) G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Models'),G2pwd.SetDefaultSASDModel()) @@ -853,7 +856,7 @@ def SaveIntegration(G2frame,PickId,data,Overwrite=False): G2frame.GPXtree.SetItemPyData(Id,[valuesdict, [np.array(X),np.array(Y),np.array(W),np.zeros(N),np.zeros(N),np.zeros(N)]]) return Id #last powder pattern generated - + def XYsave(G2frame,XY,labelX='X',labelY='Y',names=[]): 'Save XY table data' pth = G2G.GetExportPath(G2frame) @@ -878,10 +881,10 @@ def XYsave(G2frame,XY,labelX='X',labelY='Y',names=[]): header = '%s,%s(%d)\n'%(labelX,labelY,i) File.write(header) for x,y in XY[i].T: - File.write('%.3f,%.3f\n'%(x,y)) + File.write('%.3f,%.3f\n'%(x,y)) File.close() - print (' XY data saved to: '+filename) - + print (' XY data saved to: '+filename) + def PeakListSave(G2frame,file,peaks): 'Save powder peaks to a data file' print ('save peak list to file: '+G2frame.peaklistfile) @@ -896,7 +899,7 @@ def PeakListSave(G2frame,file,peaks): file.write("%10.4f %12.2f %10.3f %10.3f \n" % \ (peak[0],peak[2],peak[4],peak[6])) print ('peak list saved') - + def IndexPeakListSave(G2frame,peaks): 'Save powder peaks from the indexing list' file = open(G2frame.peaklistfile,'wa') @@ -923,7 +926,7 @@ def ExportPowderList(G2frame): along with their descriptions (note that a extension may be repeated but descriptions are unique). This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only. - + :param wx.Frame G2frame: the GSAS-II main data tree window ''' extList = [] @@ -940,13 +943,13 @@ def ExportPowderList(G2frame): def ExportPowder(G2frame,TreeName,fileroot,extension,hint=''): '''Writes a single powder histogram using the Export routines. - This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only. + This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only. :param wx.Frame G2frame: the GSAS-II main data tree window :param str TreeName: the name of the histogram (PWDR ...) in the data tree :param str fileroot: name for file to be written, extension ignored :param str extension: extension for file to be written (start with '.'). Must - match a powder export routine that has a Writer object. + match a powder export routine that has a Writer object. :param str hint: a string that must match the export's format ''' filename = os.path.abspath(os.path.splitext(fileroot)[0]+extension) @@ -972,10 +975,10 @@ def ExportPowder(G2frame,TreeName,fileroot,extension,hint=''): print('No Export routine supports extension '+extension) def ExportSequentialFullCIF(G2frame,seqData,Controls): - '''Handles access to CIF exporter a bit differently for sequential fits, as this is + '''Handles access to CIF exporter a bit differently for sequential fits, as this is not accessed via the usual export menus ''' - import G2export_CIF + from exports import G2export_CIF ##################### debug code to reload exporter before each use #### #import importlib as imp #imp.reload(G2export_CIF) @@ -983,12 +986,12 @@ def ExportSequentialFullCIF(G2frame,seqData,Controls): ######################################################################## obj = G2export_CIF.ExportProjectCIF(G2frame) obj.Exporter(None,seqData=seqData,Controls=Controls) - + def ExportSequential(G2frame,data,obj,exporttype): ''' Used to export from every phase/dataset in a sequential refinement using a .Writer method for either projects or phases. Prompts to select histograms - and for phase exports, which phase(s). + and for phase exports, which phase(s). :param wx.Frame G2frame: the GSAS-II main data tree window :param dict data: the sequential refinement data object @@ -1111,7 +1114,7 @@ def ReadDIFFaX(DIFFaXfile): elif trans: if diff: Trans.append(diff) - + #STRUCTURE records laueRec = Struct[1].split() Layer['Laue'] = laueRec[0] @@ -1150,7 +1153,7 @@ def ReadDIFFaX(DIFFaXfile): if '/' in val: newVals.append(eval(val+'.')) else: - newVals.append(float(val)) + newVals.append(float(val)) atomRec = [atomName,atomType,newVals[0],newVals[1],newVals[2],newVals[4],newVals[3]/78.9568] Layer['Layers'][-1]['Atoms'].append(atomRec) N += 1 @@ -1203,7 +1206,7 @@ def fmtCell(cell): msgs[phlbl] = newData return # create a new phase - try: + try: sgnum = int(newData[0].strip()) sgsym = G2spc.spgbyNum[sgnum] sgname = sgsym.replace(" ","") @@ -1221,7 +1224,7 @@ def fmtCell(cell): Atoms = newPhase['Atoms'] = [] for a in newData[3:]: if not a.strip(): continue - try: + try: elem,n,wyc,x,y,z = a.split() atom = [] atom.append(elem+n) @@ -1233,7 +1236,7 @@ def fmtCell(cell): atom.append(SytSym) atom.append(Mult) atom.append('I') - atom += [0.02,0.,0.,0.,0.,0.,0.,] + atom += [0.02,0.,0.,0.,0.,0.,0.,] atom.append(ran.randint(0,sys.maxsize)) Atoms.append(atom) except: @@ -1269,33 +1272,33 @@ def fmtCell(cell): def mkParmDictfromTree(G2frame,sigDict=None): '''Load the GSAS-II refinable parameters from the tree into dict parmDict - Updating refined values to those from the last cycle. Optionally + Updating refined values to those from the last cycle. Optionally compute the s.u. values for the parameters and place them in sigDict. - The actions in the routine are used in a number of places around - the GSAS-II code where it would be "cleaner" to use this instead. - Perhaps that will happen as code revisions are made. One example + The actions in the routine are used in a number of places around + the GSAS-II code where it would be "cleaner" to use this instead. + Perhaps that will happen as code revisions are made. One example of this, :meth:`GSASIIfiles.ExportBaseclass.loadParmDict`. :param wx.Frame G2frame: a reference to the main GSAS-II window - :param dict sigDict: a Python dict with sigma (s.u.) values for + :param dict sigDict: a Python dict with sigma (s.u.) values for each parameter - :returns: parmDict, a dict with the value for all refined and most + :returns: parmDict, a dict with the value for all refined and most unrefined GSAS-II parameters used in the diffraction computations. This parmDict version has only values, as opposed to the version used in some parts of the code that has refinement flags and initial - values as well. + values as well. ''' - import GSASIIstrIO as G2stIO - import GSASIIstrMath as G2stMth - import GSASIImapvars as G2mv + from . import GSASIIstrIO as G2stIO + from . import GSASIIstrMath as G2stMth + from . import GSASIImapvars as G2mv G2frame.CheckNotebook() parmDict = {} rigidbodyDict = {} covDict = {} consDict = {} - + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() if G2frame.GPXtree.IsEmpty(): return # nothing to do rigidbodyDict = G2frame.GPXtree.GetItemPyData( @@ -1304,7 +1307,7 @@ def mkParmDictfromTree(G2frame,sigDict=None): G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance')) consDict = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints')) - + rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False) parmDict.update(rbDict) rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]}) @@ -1357,6 +1360,9 @@ def mkParmDictfromTree(G2frame,sigDict=None): def LogCellChanges(G2frame): '''Log varied cell parameters into the data tree notebook''' + Controls = G2frame.GPXtree.GetItemPyData( + G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + if Controls['max cyc'] == 0: return # no fit so no change covData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance')) parmDict = mkParmDictfromTree(G2frame) @@ -1366,9 +1372,8 @@ def LogCellChanges(G2frame): pId = phasedict['pId'] SGData = phasedict['General']['SGData'] for hist in phasedict['Histograms']: - if not phasedict['Histograms'][hist]['Use']: continue - #skip single crystal histograms! - if 'Type' in phasedict['Histograms'][hist] and 'S' in phasedict['Histograms'][hist]['Type']: continue + if not phasedict['Histograms'][hist]['Use']: continue + if 'HKLF' in hist: continue #skip single crystal histograms! hId = Histograms[hist]['hId'] if any(phasedict['Histograms'][hist]['HStrain'][1]) or phasedict['General']['Cell'][0]: cellList,cellSig = G2lat.getCellSU(pId,hId,SGData,parmDict,covData) @@ -1376,7 +1381,7 @@ def LogCellChanges(G2frame): G2frame.AddToNotebook(f'Phase {pId} Hist {hId}: {txt}', 'CEL',False) if __name__ == '__main__': - import GSASIIdataGUI + from . import GSASIIdataGUI application = GSASIIdataGUI.GSASIImain(0) G2frame = application.main #app = wx.PySimpleApp() @@ -1385,7 +1390,7 @@ def LogCellChanges(G2frame): #filename = '/tmp/notzip.zip' #filename = '/tmp/all.zip' #filename = '/tmp/11bmb_7652.zip' - + #selection=None, confirmoverwrite=True, parent=None # choicelist=[ ('a','b','c'), # ('test1','test2'),('no choice',)] diff --git a/GSASII/GSASIImpsubs.py b/GSASII/GSASIImpsubs.py index 0d2e9ae20..317a7f1a0 100644 --- a/GSASII/GSASIImpsubs.py +++ b/GSASII/GSASIImpsubs.py @@ -18,9 +18,9 @@ import multiprocessing as mp import numpy as np import numpy.ma as ma -import GSASIIpath -import GSASIIpwd as G2pwd -import GSASIIfiles as G2fil +from . import GSASIIpath +from . import GSASIIpwd as G2pwd +from . import GSASIIfiles as G2fil sind = lambda x: np.sin(x*np.pi/180.) cosd = lambda x: np.cos(x*np.pi/180.) diff --git a/GSASII/GSASIIobj.py b/GSASII/GSASIIobj.py index bcb2ec5be..f9819e4e5 100644 --- a/GSASII/GSASIIobj.py +++ b/GSASII/GSASIIobj.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- #GSASIIobj - data objects for GSAS-II ''' -Classes and routines defined in :mod:`GSASIIobj` follow. +Classes and routines defined in :mod:`GSASIIobj` follow. ''' # Note that documentation for GSASIIobj.py has been moved # to file docs/source/GSASIIobj.rst @@ -12,14 +12,10 @@ import random as ran import sys import os.path -if '2' in platform.python_version_tuple()[0]: - import cPickle -else: - import pickle as cPickle -import GSASIIpath -import GSASIImath as G2mth -import GSASIIspc as G2spc +import pickle import numpy as np +from GSASII import GSASIImath as G2mth +from GSASII import GSASIIspc as G2spc DefaultControls = { 'deriv type':'analytic Hessian', @@ -40,10 +36,10 @@ ['Chiral','Volumes'],['Torsion','Torsions'],['Rama','Ramas'], ['ChemComp','Sites'],['Texture','HKLs'],['Moments','Moments'], ['General','General']] -'''Names of restraint keys for the restraint dict and the location of +'''Names of restraint keys for the restraint dict and the location of the restraints in each dict ''' - + def StripUnicode(string,subs='.'): '''Strip non-ASCII characters from strings @@ -141,9 +137,9 @@ def MakeUniqueLabel(lbl,labellist): ''' reVarStep = {} -''' This dictionary lists the preferred step size for numerical -derivative computation w/r to a GSAS-II variable. Keys are compiled -regular expressions and values are the step size for that parameter. +''' This dictionary lists the preferred step size for numerical +derivative computation w/r to a GSAS-II variable. Keys are compiled +regular expressions and values are the step size for that parameter. Initialized in :func:`CompileVarDesc`. ''' # create a default space group object for P1; N.B. fails when building documentation @@ -162,7 +158,7 @@ def GetPhaseNames(fl): PhaseNames = [] while True: try: - data = cPickle.load(fl) + data = pickle.load(fl) except EOFError: break datum = data[0] @@ -184,8 +180,10 @@ def SetNewPhase(Name='New Phase',SGData=None,cell=None,Super=None): [1.0,1.0,1.0,90.,90,90.,1.] ''' - if SGData is None: SGData = P1SGData - if cell is None: cell=[1.0,1.0,1.0,90.,90.,90.,1.] + if SGData is None: + SGData = P1SGData + if cell is None: + cell=[1.0,1.0,1.0,90.,90.,90.,1.] phaseData = { 'ranId':ran.randint(0,sys.maxsize), 'General':{ @@ -227,14 +225,22 @@ def ReadCIF(URLorFile): (from James Hester). The open routine gets confused with DOS names that begin with a letter and colon "C:\\dir\" so this routine will try to open the passed name as a file and if that - fails, try it as a URL + fails, try it as a URL. + + Used for CIF imports and for reading CIF templates for project CIF exports :param str URLorFile: string containing a URL or a file name. Code will try first to open it as a file and then as a URL. - :returns: a PyCifRW CIF object. + :returns: a PyCifRW CIF object or an empty string if PyCifRW is not accessible ''' - import CifFile as cif # PyCifRW from James Hester + try: + import CifFile as cif # PyCifRW from James Hester as a package + except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + except ImportError: + return '' # alternate approach: #import urllib @@ -249,15 +255,15 @@ def ReadCIF(URLorFile): return cif.ReadCif(URLorFile) def TestIndexAll(): - '''Test if :func:`IndexAllIds` has been called to index all phases and - histograms (this is needed before :func:`G2VarObj` can be used. + '''Test if :func:`IndexAllIds` has been called to index all phases and + histograms (this is needed before :func:`G2VarObj` can be used. :returns: Returns True if indexing is needed. ''' if PhaseIdLookup or AtomIdLookup or HistIdLookup: return False return True - + def IndexAllIds(Histograms,Phases): '''Scan through the used phases & histograms and create an index to the random numbers of phases, histograms and atoms. While doing this, @@ -267,17 +273,17 @@ def IndexAllIds(Histograms,Phases): Note: this code assumes that the atom random Id (ranId) is the last element each atom record. - This is called when phases & histograms are looked up - in these places (only): - + This is called when phases & histograms are looked up + in these places (only): + * :func:`GSASIIstrIO.GetUsedHistogramsAndPhases` (which loads the histograms and phases from a GPX file), - * :meth:`~GSASIIdataGUI.GSASII.GetUsedHistogramsAndPhasesfromTree` (which does the same thing but from the data tree.) + * :meth:`~GSASIIdataGUI.GSASII.GetUsedHistogramsAndPhasesfromTree` (which does the same thing but from the data tree.) * :meth:`~GSASIIdataGUI.GSASII.OnFileClose` (clears out an old project) - - Note that globals :data:`PhaseIdLookup` and :data:`PhaseRanIdLookup` are + + Note that globals :data:`PhaseIdLookup` and :data:`PhaseRanIdLookup` are also set in :func:`AddPhase2Index` to temporarily assign a phase number as a phase is being imported. - + TODO: do we need a lookup for rigid body variables? ''' # process phases and atoms @@ -333,7 +339,7 @@ def IndexAllIds(Histograms,Phases): def AddPhase2Index(rdObj,filename): '''Add a phase to the index during reading - Used where constraints are generated during import (ISODISTORT CIFs) + Used where constraints are generated during import (ISODISTORT CIFs) ''' ranId = rdObj.Phase['ranId'] ph = 'from '+filename # phase is not named yet @@ -580,15 +586,15 @@ def getVarDescr(varname): return l def CompileVarDesc(): - '''Set the values in the variable lookup tables + '''Set the values in the variable lookup tables (:attr:`reVarDesc` and :attr:`reVarStep`). This is called in :func:`getDescr` and :func:`getVarStep` so this - initialization is always done before use. These variables are + initialization is always done before use. These variables are also used in script `makeVarTbl.py` which creates the table in section 3.2 of the Sphinx docs (:ref:`VarNames_table`). Note that keys may contain regular expressions, where '[xyz]' - matches 'x' 'y' or 'z' (equivalently '[x-z]' describes this as range + matches 'x' 'y' or 'z' (equivalently '[x-z]' describes this as range of values). '.*' matches any string. For example:: 'AUiso':'Atomic isotropic displacement parameter', @@ -663,8 +669,8 @@ def CompileVarDesc(): '([XYZ])$' : ('Cauchy instrument broadening \\1',1e-5), 'Zero' : 'Debye-Scherrer zero correction', 'Shift' : 'Bragg-Brentano sample displ.', - 'SurfRoughA' : 'Bragg-Brenano surface roughness A', - 'SurfRoughB' : 'Bragg-Brenano surface roughness B', + 'SurfRoughA' : 'Bragg-Brentano surface roughness A', + 'SurfRoughB' : 'Bragg-Brentano surface roughness B', 'Transparency' : 'Bragg-Brentano sample tranparency', 'DebyeA' : 'Debye model amplitude', 'DebyeR' : 'Debye model radius', @@ -751,7 +757,7 @@ def CompileVarDesc(): }.items(): # Needs documentation: HAP: LeBail, newLeBail # hist: Azimuth, Chi, Omega, Phi, Bank, nDebye, nPeaks - + if len(value) == 2: #VarDesc[key] = value[0] reVarDesc[re.compile(key)] = value[0] @@ -761,13 +767,13 @@ def CompileVarDesc(): reVarDesc[re.compile(key)] = value def removeNonRefined(parmList): - '''Remove items from variable list that are not refined and should not + '''Remove items from variable list that are not refined and should not appear as options for constraints :param list parmList: a list of strings of form "p:h:VAR:a" where VAR is the variable name - :returns: a list after removing variables where VAR matches a + :returns: a list after removing variables where VAR matches a entry in local variable NonRefinedList ''' NonRefinedList = ['Omega','Type','Chi','Phi', 'Azimuth','Gonio. radius', @@ -777,7 +783,7 @@ def removeNonRefined(parmList): 'nDebye', #'', ] return [prm for prm in parmList if prm.split(':')[2] not in NonRefinedList] - + def getDescr(name): '''Return a short description for a GSAS-II variable @@ -803,10 +809,10 @@ def getVarStep(name,parmDict=None): :param str name: A complete variable name (with colons, :) :param dict parmDict: A dict with parameter values or None (default) - :returns: a float that should be an appropriate step size, either from - the value supplied in :func:`CompileVarDesc` or based on the value for - name in parmDict, if supplied. If not found or the value is zero, - a default value of 1e-5 is used. If parmDict is None (default) and + :returns: a float that should be an appropriate step size, either from + the value supplied in :func:`CompileVarDesc` or based on the value for + name in parmDict, if supplied. If not found or the value is zero, + a default value of 1e-5 is used. If parmDict is None (default) and no value is provided in :func:`CompileVarDesc`, then None is returned. ''' CompileVarDesc() # compile the regular expressions, if needed @@ -879,23 +885,23 @@ def prmLookup(name,prmDict): considering a wild card for histogram or atom number (use of both will never occur at the same time). - :param name: a GSAS-II parameter name (str, see :func:`getVarDescr` - and :func:`CompileVarDesc`) or a :class:`G2VarObj` object. - :param dict prmDict: a min/max dictionary, (parmMinDict + :param name: a GSAS-II parameter name (str, see :func:`getVarDescr` + and :func:`CompileVarDesc`) or a :class:`G2VarObj` object. + :param dict prmDict: a min/max dictionary, (parmMinDict or parmMaxDict in Controls) where keys are :class:`G2VarObj` - objects. + objects. :returns: Two values, (**matchname**, **value**), are returned where: - * **matchname** *(str)* is the :class:`G2VarObj` object - corresponding to the actual matched name, - which could contain a wildcard even if **name** does not; and + * **matchname** *(str)* is the :class:`G2VarObj` object + corresponding to the actual matched name, + which could contain a wildcard even if **name** does not; and * **value** *(float)* which contains the parameter limit. ''' for key,value in prmDict.items(): if str(key) == str(name): return key,value if key == name: return key,value return None,None - + def _lookup(dic,key): '''Lookup a key in a dictionary, where None returns an empty string @@ -928,9 +934,9 @@ class G2VarObj(object): '''Defines a GSAS-II variable either using the phase/atom/histogram unique Id numbers or using a character string that specifies variables by phase/atom/histogram number (which can change). - Note that :func:`GSASIIstrIO.GetUsedHistogramsAndPhases`, - which calls :func:`IndexAllIds` (or - :func:`GSASIIscriptable.G2Project.index_ids`) should be used to + Note that :func:`GSASIIstrIO.GetUsedHistogramsAndPhases`, + which calls :func:`IndexAllIds` (or + :func:`GSASIIscriptable.G2Project.index_ids`) should be used to (re)load the current Ids before creating or later using the G2VarObj object. @@ -1109,19 +1115,19 @@ def __eq__(self, other): if self.name != other.name: return False return True - + def fmtVarByMode(self, seqmode, note, warnmsg): - '''Format a parameter object for display. Note that these changes - are only temporary and are only shown only when the Constraints + '''Format a parameter object for display. Note that these changes + are only temporary and are only shown only when the Constraints data tree is selected. - * In a non-sequential refinement or where the mode is 'use-all', the + * In a non-sequential refinement or where the mode is 'use-all', the name is converted unchanged to a str - * In a sequential refinement when the mode is 'wildcards-only' the - name is converted unchanged to a str but a warning is added + * In a sequential refinement when the mode is 'wildcards-only' the + name is converted unchanged to a str but a warning is added for non-wildcarded HAP or Histogram parameters - * In a sequential refinement or where the mode is 'auto-wildcard', - a histogram number is converted to a wildcard (*) and then + * In a sequential refinement or where the mode is 'auto-wildcard', + a histogram number is converted to a wildcard (*) and then converted to str :param str mode: the sequential mode (see above) @@ -1131,10 +1137,10 @@ def fmtVarByMode(self, seqmode, note, warnmsg): :returns: varname, explain, note, warnmsg (all str values) where: * varname is the parameter expressed as a string, - * explain is blank unless there is a warning explanation about + * explain is blank unless there is a warning explanation about the parameter or blank - * note is the previous value unless overridden - * warnmsg is the previous value unless overridden + * note is the previous value unless overridden + * warnmsg is the previous value unless overridden ''' explain = '' s = self.varname() @@ -1239,19 +1245,19 @@ def ReInitialize(self): def ExtensionValidator(self, filename): '''This methods checks if the file has the correct extension - + :returns: - + * False if this filename will not be supported by this reader (only when strictExtension is True) * True if the extension matches the list supplied by the reader * None if the reader allows un-registered extensions - + ''' if filename: ext = os.path.splitext(filename)[1] if not ext and self.strictExtension: return False - for ext in self.extensionlist: + for ext in self.extensionlist: if sys.platform == 'windows': if filename.lower().endswith(ext): return True else: @@ -1305,7 +1311,7 @@ class ImportPhase(ImportBaseclass): '''Defines a base class for the reading of files with coordinates Objects constructed that subclass this (in import/G2phase_*.py etc.) will be used - in :meth:`GSASIIdataGUI.GSASII.OnImportPhase` and in + in :meth:`GSASIIdataGUI.GSASII.OnImportPhase` and in :func:`GSASIIscriptable.import_generic`. See :ref:`Writing a Import Routine` for an explanation on how to use this class. @@ -1385,7 +1391,7 @@ class ImportPowderData(ImportBaseclass): '''Defines a base class for the reading of files with powder data. Objects constructed that subclass this (in import/G2pwd_*.py etc.) will be used - in :meth:`GSASIIdataGUI.GSASII.OnImportPowder` and in + in :meth:`GSASIIdataGUI.GSASII.OnImportPowder` and in :func:`GSASIIscriptable.import_generic`. See :ref:`Writing a Import Routine` for an explanation on how to use this class. @@ -1536,10 +1542,10 @@ class ImportImage(ImportBaseclass): * Images are read alternatively in :func:`GSASIImiscGUI.ReadImages`, which puts image info directly into the data tree. - * Unlike all other data types read by GSAS-II, images are only kept in memory as - they are used and function :func:`GSASIIfiles.GetImageData` or + * Unlike all other data types read by GSAS-II, images are only kept in memory as + they are used and function :func:`GSASIIfiles.GetImageData` or :func:`GSASIIfiles.RereadImageData` is used to reread images - if they are reloaded. For quick retrieval of previously read images, it may be useful to + if they are reloaded. For quick retrieval of previously read images, it may be useful to save sums of images or save a keyword (see ``ImageTag``, below When reading an image, the ``Reader()`` routine in the ImportImage class @@ -1560,12 +1566,12 @@ class ImportImage(ImportBaseclass): * ``ImageTag``: image number or other keyword used to retrieve image from a multi-image data file (defaults to ``1`` if not specified). * ``sumfile``: holds sum image file name if a sum was produced from a multi image file - * ``PolaVal``: has two values, the polarization fraction (typically 0.95-0.99 - for synchrotrons, 0.5 for lab instruments) and a refinement flag + * ``PolaVal``: has two values, the polarization fraction (typically 0.95-0.99 + for synchrotrons, 0.5 for lab instruments) and a refinement flag (such as ``[0.99, False]``). - * ``setdist``: nominal distance from sample to detector. Note that ``distance`` may - be changed during calibration, but ``setdist`` will not be, so that calibration may be - repeated. + * ``setdist``: nominal distance from sample to detector. Note that ``distance`` may + be changed during calibration, but ``setdist`` will not be, so that calibration may be + repeated. optional data items: @@ -1666,7 +1672,7 @@ def FindFunction(f): except Exception as msg: print('call to',f,' failed with error=',str(msg)) return None,None # not found - + class ExpressionObj(object): '''Defines an object with a user-defined expression, to be used for secondary fits or restraints. Object is created null, but is changed @@ -2096,12 +2102,12 @@ def __init__(self,msg): self.msg = msg def __str__(self): return repr(self.msg) - + def HowDidIgetHere(wherecalledonly=False): '''Show a traceback with calls that brought us to the current location. Used for debugging. - :param bool wherecalledonly: When True, the entire calling stack is + :param bool wherecalledonly: When True, the entire calling stack is shown. When False (default), only the 2nd to last stack entry (the routine that called the calling routine is shown. ''' @@ -2160,7 +2166,7 @@ class ShowTiming(object): tim0.start('start') tim0.start('in section 1') tim0.start('in section 2') - + etc. (Note that each section should have a unique label.) After the last section, end timing with:: @@ -2168,7 +2174,7 @@ class ShowTiming(object): Show timing results with:: tim0.show() - + ''' def __init__(self): self.timeSum = [] @@ -2200,8 +2206,8 @@ def show(self): print('{} {:20} {:8.2f} ms {:5.2f}%'.format(i,lbl,1000.*val,100*val/sumT)) def validateAtomDrawType(typ,generalData={}): - '''Confirm that the selected Atom drawing type is valid for the current - phase. If not, use 'vdW balls'. This is currently used only for setting a + '''Confirm that the selected Atom drawing type is valid for the current + phase. If not, use 'vdW balls'. This is currently used only for setting a default when atoms are added to the atoms draw list. ''' if typ in ('lines','vdW balls','sticks','balls & sticks','ellipsoids'): @@ -2212,15 +2218,15 @@ def validateAtomDrawType(typ,generalData={}): return 'vdW balls' def patchControls(Controls): - '''patch routine to convert variable names used in parameter limits - to G2VarObj objects + '''patch routine to convert variable names used in parameter limits + to G2VarObj objects (See :ref:`Parameter Limits` description.) ''' - import GSASIIfiles as G2fil + from . import GSASIIfiles as G2fil #patch (added Oct 2020) convert variable names for parm limits to G2VarObj for d in 'parmMaxDict','parmMinDict': if d not in Controls: Controls[d] = {} - for k in Controls[d]: + for k in Controls[d]: if type(k) is str: G2fil.G2Print("Applying patch to Controls['{}']".format(d)) Controls[d] = {G2VarObj(k):v for k,v in Controls[d].items()} @@ -2239,6 +2245,7 @@ def patchControls(Controls): # end patch if __name__ == "__main__": + from . import GSASIIpath # test variable descriptions for var in '0::Afrac:*',':1:Scale','1::dAx:0','::undefined': v = var.split(':')[2] diff --git a/GSASII/GSASIIpath.py b/GSASII/GSASIIpath.py index 81a083366..fea470e61 100644 --- a/GSASII/GSASIIpath.py +++ b/GSASII/GSASIIpath.py @@ -9,6 +9,7 @@ import sys import platform import glob +#import pathlib import subprocess import datetime as dt try: @@ -27,7 +28,7 @@ g2URL = "https://github.com/AdvancedPhotonSource/GSAS-II.git" # tutorial repo owner & Repo name gitTutorialOwn,gitTutorialRepo = 'AdvancedPhotonSource', 'GSAS-II-Tutorials' - + path2GSAS2 = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) # location of this file; save before any changes in pwd # convert version numbers as '1.2.3' to integers (1002) and back (to 1.2) @@ -35,16 +36,16 @@ intver = lambda vs: sum([int(i) for i in vs.split('.')[0:2]]*np.array((1000,1))) def GetConfigValue(key,default=None,getDefault=False): - '''Return the configuration file value for key or a default value + '''Return the configuration file value for key or a default value if not specified. - + :param str key: a value to be found in the configuration settings - :param any default: a value to be supplied if a value for key is - not specified in the config file or the config file is not found. + :param any default: a value to be supplied if a value for key is + not specified in the config file or the config file is not found. Defaults to None. - :param bool getDefault: If True looks up the default value from the - config_example.py file (default value is False). Do not specify a - getDefault=True if a value is provided for default. + :param bool getDefault: If True looks up the default value from the + config_example.py file (default value is False). Do not specify a + getDefault=True if a value is provided for default. :returns: the value found or the default. ''' if getDefault: @@ -57,8 +58,11 @@ def GetConfigValue(key,default=None,getDefault=False): return None def SetConfigValue(parmdict): - '''Set configuration variables from a dictionary where elements are lists - First item in list is the default value and second is the value to use. + '''Set configuration variables. Note that parmdict is a dictionary + from :func:`GSASIIctrlGUI.GetConfigValsDocs` where each element is a + lists. The first item in list is the default value, the second is + the value to use for that configuration variable. Most of the + information gathered in GetConfigValsDocs is no longer used. ''' global configDict for var in parmdict: @@ -74,26 +78,26 @@ def SetConfigValue(parmdict): def GetConfigDefault(key): '''Return the default value for a config value - + :param str key: a value to be found in the configuration (config_example.py) file :returns: the default value or None ''' try: - import config_example + from . import config_example except: return None return config_example.__dict__.get(key) def addPrevGPX(fil,cDict): - '''Add a GPX file to the list of previous files. + '''Add a GPX file to the list of previous files. Move previous names to start of list. Keep most recent five files ''' global configDict fil = os.path.abspath(os.path.expanduser(fil)) - if 'previous_GPX_files' not in cDict: + if 'previous_GPX_files' not in cDict: cDict['previous_GPX_files'] = [[],[],[],'Previous .gpx files'] # unexpected try: - pos = cDict['previous_GPX_files'][1].index(fil) + pos = cDict['previous_GPX_files'][1].index(fil) if pos == 0: return cDict['previous_GPX_files'][1].pop(pos) # if present, remove if not 1st except ValueError: @@ -105,28 +109,10 @@ def addPrevGPX(fil,cDict): cDict['previous_GPX_files'][1] = files[:5] configDict['previous_GPX_files'] = cDict['previous_GPX_files'][1] -# routines for looking a version numbers in files -version = -1 -def SetVersionNumber(RevString): - '''Set the subversion (svn) version number. No longer in use. - - :param str RevString: something like "$Revision: 5796 $" - that is set by subversion when the file is retrieved from subversion. - - Place ``GSASIIpath.SetVersionNumber("$Revision: 5796 $")`` in every python - file. - ''' - try: - RevVersion = int(RevString.split(':')[1].split()[0]) - global version - version = max(version,RevVersion) - except: - pass - def LoadConfigFile(filename): '''Read a GSAS-II configuration file. Comments (starting with "%") are removed, as are empty lines - + :param str filename: base file name (such as 'file.dat'). Files with this name are located from the path and the contents of each are concatenated. :returns: a list containing each non-empty (after removal of comments) line @@ -153,11 +139,11 @@ def LoadConfigFile(filename): def GetBinaryPrefix(pyver=None): '''Creates the first part of the binary directory name - such as linux_64_p3.9 (where the full name will be - linux_64_p3.9_n1.21). + such as linux_64_p3.9 (where the full name will be + linux_64_p3.9_n1.21). - Note that any change made here is also needed in GetBinaryDir in - fsource/SConstruct + Note that any change made here is also needed in GetBinaryDir in + fsource/SConstruct or GSASII-buildtools/compile/nameTar.py ''' if sys.platform == "win32": prefix = 'win' @@ -189,18 +175,16 @@ def GetBinaryPrefix(pyver=None): #============================================================================== #============================================================================== -# hybrid routines that use git & svn (to be revised to remove svn someday) G2_installed_result = None def HowIsG2Installed(): - '''Determines if GSAS-II was installed with git, svn or none of the above. + '''Determines if GSAS-II was installed with git. Result is cached to avoid time needed for multiple calls of this. - :returns: - * a string starting with 'git' from git, - if installed from the GSAS-II GitHub repository (defined in g2URL), + :returns: + * a string starting with 'git' from git, + if installed from the GSAS-II GitHub repository (defined in g2URL), the string is 'github', if the post-3/2024 directory structure is in use '-rev' is appended. - * or 'svn' is installed from an svn repository (assumed as defined in g2home) * or 'noVCS' if installed without a connection to a version control system ''' global G2_installed_result @@ -220,80 +204,91 @@ def HowIsG2Installed(): return G2_installed_result except: pass - if svnGetRev(verbose=False): - G2_installed_result = 'svn' - return G2_installed_result G2_installed_result = 'noVCS' return G2_installed_result - -def GetVersionNumber(): - '''Obtain a version number for GSAS-II from git, svn or from the - files themselves, if no other choice. - This routine was used to get the GSAS-II version from strings - placed in files by svn with the version number being the latest - number found, gathered by :func:`SetVersionNumber` (not 100% accurate - as the latest version might have files changed that are not tagged - by svn or with a call to SetVersionNumber. Post-svn this info - will not be reliable, and this mechanism is replaced by a something - created with a git hook, file git_verinfo.py (see the git_filters.py file). +def getSavedVersionInfo(): + '''Get version number information from a file written by install + routines. This is faster than getting the information from git. Also, + when GSAS-II is installed into Python, the files are no longer in + a git repository so querying git is not possible. + + The saved_version.py file is written by install/save_versions.py. + The git_verinfo.py file is written by install/tag-version.py or + by install/incr-mini-version.py. If both are present, use the + saved_version.py file preferentially. + + :returns: a reference to the version variables or None if no + version info file is found. + ''' + try: + from . import saved_version as gv + return gv + except: + pass + try: + from . import git_verinfo as gv + return gv + except: + pass + return None # this is unexpected as all dists should have git_verinfo.py - Before resorting to the approaches above, try to ask git or svn - directly. +def GetVersionNumber(): + '''Obtain a numeric (sequential) version number for GSAS-II from version + files, or directly from git if no other choice. - :returns: an int value usually, but a value of 'unknown' might occur + :returns: an int value normally, but unexpected error conditions could result in + a value of 'unknown' or '?'. ''' + # look for a previously recorded tag -- this is quick + gv = getSavedVersionInfo() + if gv is not None: + for item in gv.git_tags+gv.git_prevtags: + if item.isnumeric(): return int(item) + if HowIsG2Installed().startswith('git'): - # look for a recorded tag -- this is quick - try: - import git_verinfo as gv - try: - for item in gv.git_tags: - if item.isnumeric(): return int(item) - except: - pass - try: - for item in gv.git_prevtags: - if item.isnumeric(): return int(item) - except: - pass - except: - pass - # no luck, ask Git for the most recent tag (must start & end with a number) + # unexpected: should always find a version from getSavedVersionInfo() + # from a version file, but if that fails ask Git for the most + # recent tag that starts & ends with a digit and does not contain a '.' try: g2repo = openGitRepo(path2GSAS2) - tag,vers,gnum = g2repo.git.describe('--tags','--match','[0-9]*[0-9]').split('-') + tag,vers,gnum = g2repo.git.describe('--tags','--match','[0-9]*[0-9]', + '--exclude','*.*').split('-') if tag.isnumeric(): return int(tag) except: pass return "unknown" - - elif HowIsG2Installed() == 'svn': - rev = svnGetRev() - if rev is not None: return rev - # No luck asking, look up version information from git_verinfo.py - try: - import git_verinfo as gv - try: - for item in gv.git_tags: - if item.isnumeric(): return int(item) - except: - pass + # Not installed from git and no version file -- very strange! + return "?" + +def GetVersionTag(): + '''Obtain a release (X.Y.Z) version number for GSAS-II from version + files, or directly from git if no other choice. + + :returns: a string of form .. normally, but unexpected + error conditions could result in a value of '?.?.?' or '?'. + ''' + # look for a previously recorded tag -- this is quick + gv = getSavedVersionInfo() + if gv is not None: + if '?' not in gv.git_versiontag: return gv.git_versiontag + + if HowIsG2Installed().startswith('git'): + # unexpected: should always find a version from getSavedVersionInfo() + # from a version file, but if that fails ask Git for the most + # recent tag that has the X.Y.Z format. try: - for item in gv.git_prevtags: - if item.isnumeric(): return int(item) + g2repo = openGitRepo(path2GSAS2) + tag,vers,gnum = g2repo.git.describe('--tags','--match','*.*.*').split('-') + return tag except: pass - except: - pass - - # all else failed, use the svn SetVersionNumber value (global version) - if version > 5000: # a small number must be wrong - return version - else: - return "unknown" - + return "?.?.?" + + # Not installed from git and no version file -- very strange! + return "?" + def getG2Branch(): '''Get name of current branch, as named on local computer ''' @@ -306,17 +301,17 @@ def getG2Branch(): def getG2VersionInfo(): '''Get the git version information. This can be a bit slow, so reading - .../GSASII/saved_version.py may be faster (in main but not master branch) + .../GSASII/saved_version.py may be faster ''' + gv = getSavedVersionInfo() if HowIsG2Installed().startswith('git'): g2repo = openGitRepo(path2GSAS2) commit = g2repo.head.commit - ctim = commit.committed_datetime.strftime('%d-%b-%Y %H:%M') + ctim = commit.committed_datetime.strftime('%d-%b-%y %H:%M') now = dt.datetime.now().replace( tzinfo=commit.committed_datetime.tzinfo) delta = now - commit.committed_datetime age = delta.total_seconds()/(60*60*24.) - gversion = f"Tag: #{GetVersionNumber()}" msg = '' if g2repo.head.is_detached: msg = ("\n" + @@ -329,53 +324,83 @@ def getG2VersionInfo(): rc,lc,_ = gitCheckForUpdates(False,g2repo) if rc is None: msg += f"\n\tNo history found. On development branch? ({g2repo.active_branch})" - elif str(g2repo.active_branch) != 'master': - msg += f'\n\tUsing development branch "{g2repo.active_branch}"' + elif str(g2repo.active_branch) != 'main': + msg += f'\n\tUsing development branch: {g2repo.active_branch}' elif age > 60 and len(rc) > 0: - msg += f"\n\t**** This version is really old. Please update. >= {len(rc)} updates have been posted ****" - elif age > 5 and len(rc) > 0: - msg += f"\n\t**** Please consider updating. >= {len(rc)} updates have been posted" - elif len(rc) > 0: - msg += f"\n\tThis GSAS-II version is ~{len(rc)} updates behind current." - return f" GSAS-II: {commit.hexsha[:6]}, {ctim} ({age:.1f} days old). {gversion}{msg}" - elif HowIsG2Installed() == 'svn': - rev = svnGetRev() - if rev is None: - "no SVN" + msg += f"\n\t**** This version is really old ({age:.1f} days). Please update.\n\t**** At least {len(rc)} updates have been posted ****" + elif (age > 5 and len(rc) > 0) or len(rc) > 5: + msg += f"\n\t**** Please consider updating. This version is {age:.1f} days old\n\t**** and {len(rc)} or more updates behind." +# elif len(rc) > 0: +# msg += f"\n\tThis GSAS-II version is ~{len(rc)} updates behind current." + # could consider getting version & tag from gv if not None (see below) + gversion = f"{GetVersionNumber()}/{GetVersionTag()}" + if len(rc) > 0: + return f" GSAS-II: {gversion} posted {ctim} (\u2265{len(rc)} new updates) [{commit.hexsha[:8]}]{msg}" else: - rev = f"SVN version {rev}" - - # patch 11/2020: warn if GSASII path has not been updated past v4576. - # For unknown reasons on Mac with gsas2full, there have been checksum - # errors in the .so files that prevented svn from completing updates. - # If GSASIIpath.svnChecksumPatch is not present, then the fix for that - # has not been retrieved, so warn. Keep for a year or so. + return f" GSAS-II: {gversion} posted {ctim} (no new updates) [{commit.hexsha[:8]}]{msg}" + elif gv is not None: + vt = '' + cvt = '' try: - svnChecksumPatch - except: - print('Warning GSAS-II incompletely updated. Please contact toby@anl.gov') - # end patch - - return f"Latest GSAS-II revision: {GetVersionNumber()} (svn {rev})" - else: - try: - import git_verinfo as gv - if gv.git_tags: - msg = f"{' '.join(gv.git_tags)}, Git: {gv.git_version[:6]}" - else: - msg = (f"{gv.git_version[:6]}; "+ - f"Prev ver: {' '.join(gv.git_prevtags)}"+ - f", {gv.git_prevtaggedversion[:6]}") - return f"GSAS-II version: {msg} (Manual update)" + if gv.git_versiontag: + vt = gv.git_versiontag + cvt,cvn = getGitHubVersion() except: pass - # all else fails, use the old version number routine - return f"GSAS-II installed manually, last revision: {GetVersionNumber()}" + + for item in gv.git_tags+gv.git_prevtags: + if item.isnumeric(): + tags = item + if vt: + tags += f', {vt}' + msg = f"GSAS-II version: Git: {gv.git_version[:8]}, #{tags} (reinstall to update)" + if vt != cvt and cvt is not None: + msg += f'\n\tNote that the current GSAS-II version is {cvt}' + return msg + # Failed to get version info, fallback on old version number routine; should not happen anymore + return f"GSAS-II installed without git, last tag: #{GetVersionNumber()}, {GetVersionTag()}" #============================================================================== #============================================================================== -# routines to interface with git. +# routines to interface with GitHub. +def saveGitHubVersion(): + '''Get the latest GSAS-II version tags from the GitHub site + and place them into the config.ini file. This is always done in + background so that app startup time is minimally delayed. + :returns: Returns a Popen object (see subprocess). + ''' + try: + import requests + requests + except: + print('Unable to use requests module') + return + return subprocess.Popen([sys.executable, __file__, '--github-tags']) + if GetConfigValue('debug'): print('Updates fetched') + +def getGitHubVersion(): + '''Get the latest git version info when not accessible from git + as saved by saveGitHubVersion + ''' + import configparser + cfgfile = os.path.expanduser(os.path.normpath('~/.GSASII/config.ini')) + if not os.path.exists(cfgfile): + if GetConfigValue('debug'): print(f"{cfgfile} not found") + return None,None + try: + cfg = configparser.ConfigParser() + # Read the configuration file + cfg.read(cfgfile) + except Exception as err: + if GetConfigValue('debug'): print(f"Error reading {cfgfile}\n",err) + return None,None + if 'version info' not in cfg: + if GetConfigValue('debug'): print(f"no saved version number in {cfgfile}") + return None,None + return cfg['version info'].get('lastVersionTag'),cfg['version info'].get('lastVersionNumber') + +# routines to interface with git. BASE_HEADER = {'Accept': 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28'} @@ -383,34 +408,34 @@ def openGitRepo(repo_path): try: import git except: - return None + return None try: # patch 3/2024 for svn dir organization return git.Repo(path2GSAS2) except git.InvalidGitRepositoryError: pass return git.Repo(os.path.dirname(path2GSAS2)) - + def gitLookup(repo_path,gittag=None,githash=None): '''Return information on a particular checked-in version - of GSAS-II. + of GSAS-II. :param str repo_path: location where GSAS-II has been installed - :param str gittag: a tag value. - :param str githash: hex hash code (abbreviated to as few characters as + :param str gittag: a tag value. + :param str githash: hex hash code (abbreviated to as few characters as needed to keep it unique). If None (default), a tag must be supplied. - :returns: either None if the tag/hash is not found or a tuple with - four values (hash, tag-list, message,date_time) where + :returns: either None if the tag/hash is not found or a tuple with + four values (hash, tag-list, message,date_time) where - * hash (str) is the git checking hash code; - * tag-list is a list of tags (typically there will - be one or two); + * hash (str) is the git checking hash code; + * tag-list is a list of tags (typically there will + be one or two); * message is the check-in message (str) * date_time is the check-in date as a datetime object ''' try: import git except: - return None + return None g2repo = openGitRepo(repo_path) if gittag is not None and githash is not None: raise ValueError("Cannot specify a hash and a tag") @@ -428,16 +453,16 @@ def gitLookup(repo_path,gittag=None,githash=None): raise ValueError("Must specify either a hash or a tag") tags = [i.name for i in g2repo.tags if i.commit == commit] return (commit.hexsha, tags, commit.message,commit.committed_datetime) - + def gitHash2Tags(githash=None,g2repo=None): - '''Find tags associated with a particular git commit. - Note that if `githash` cannot be located because it does not - exist or is not unique, a `git.BadName` exception is raised. + '''Find tags associated with a particular git commit. + Note that if `githash` cannot be located because it does not + exist or is not unique, a `git.BadName` exception is raised. - :param str githash: hex hash code (abbreviated to as few characters as + :param str githash: hex hash code (abbreviated to as few characters as needed to keep it unique). If None (default), the HEAD is used. - :param str g2repo: git.Rwpo connecton to GSAS-II installation. If - None (default) it will be opened. + :param str g2repo: git.Rwpo connecton to GSAS-II installation. If + None (default) it will be opened. :returns: a list of tags (each a string) ''' if g2repo is None: @@ -448,17 +473,17 @@ def gitHash2Tags(githash=None,g2repo=None): commit = g2repo.commit(githash) #return [i.name for i in g2repo.tags if i.commit == commit] # slow with a big repo return g2repo.git.tag('--points-at',commit).split('\n') - + def gitTag2Hash(gittag,g2repo=None): '''Provides the hash number for a git tag. - Note that if `gittag` cannot be located because it does not - exist or is too old and is beyond the `depth` of the local - repository, a `ValueError` exception is raised. + Note that if `gittag` cannot be located because it does not + exist or is too old and is beyond the `depth` of the local + repository, a `ValueError` exception is raised. :param str repo_path: location where GSAS-II has been installed. :param str gittag: a tag value. - :param str g2repo: git.Rwpo connecton to GSAS-II installation. If - None (default) it will be opened. + :param str g2repo: git.Rwpo connecton to GSAS-II installation. If + None (default) it will be opened. :returns: a str value with the hex hash for the commit. ''' if g2repo is None: @@ -469,9 +494,9 @@ def gitTestGSASII(verbose=True,g2repo=None): '''Test a the status of a GSAS-II installation :param bool verbose: if True (default), status messages are printed - :param str g2repo: git.Rwpo connecton to GSAS-II installation. If - None (default) it will be opened. - :returns: istat, with the status of the repository, with one of the + :param str g2repo: git.Rwpo connecton to GSAS-II installation. If + None (default) it will be opened. + :returns: istat, with the status of the repository, with one of the following values: * -1: path is not found @@ -481,12 +506,12 @@ def gitTestGSASII(verbose=True,g2repo=None): * value&1==1: repository has local changes (uncommitted/stashed) * value&2==2: repository has been regressed (detached head) * value&4==4: repository has staged files - * value&8==8: repository has has been switched to non-master branch + * value&8==8: repository has has been switched to other than main branch * value==0: no problems noted ''' if g2repo is None: - if not os.path.exists(path2GSAS2): + if not os.path.exists(path2GSAS2): if verbose: print(f'Warning: Directory {path2GSAS2} not found') return -1 if os.path.exists(os.path.join(path2GSAS2,'..','.git')): @@ -508,32 +533,34 @@ def gitTestGSASII(verbose=True,g2repo=None): if g2repo.head.is_detached: code += 2 # detached else: - if g2repo.active_branch.name != 'master': - code += 8 # not on master branch + if g2repo.active_branch.name != 'main': + code += 8 # not on main branch if g2repo.index.diff("HEAD"): code += 4 # staged # test if there are local changes committed return code def gitCheckForUpdates(fetch=True,g2repo=None): - '''Provides a list of the commits made locally and those in the - local copy of the repo that have not been applied. Does not - provide useful information in the case of a detached Head (see + '''Provides a list of the commits made locally and those in the + local copy of the repo that have not been applied. Does not + provide useful information in the case of a detached Head (see :func:`countDetachedCommits` for that.) - :param bool fetch: if True (default), updates are copied over from + :param bool fetch: if True (default), updates are downloaded from the remote repository (git fetch), before checking for changes. - :param str g2repo: git.Rwpo connecton to GSAS-II installation. If - None (default) it will be opened. - :returns: a list containing (remotecommits, localcommits, fetched) where + At present, this feature is not used anywhere in GSAS-II (fetch=False + in all calls). + :param str g2repo: git.Rwpo connecton to GSAS-II installation. If + None (default) it will be opened. + :returns: a list containing (remotecommits, localcommits, fetched) where - * remotecommits is a list of hex hash numbers of remote commits and + * remotecommits is a list of hex hash numbers of remote commits and * localcommits is a list of hex hash numbers of local commits and * fetched is a bool that will be True if the update (fetch) step ran successfully - Note that if the head is detached (GSAS-II has been reverted to an - older version) or the branch has been changed, the values for each + Note that if the head is detached (GSAS-II has been reverted to an + older version) or the branch has been changed, the values for each of the three items above will be None. ''' try: @@ -546,28 +573,37 @@ def gitCheckForUpdates(fetch=True,g2repo=None): g2repo = openGitRepo(path2GSAS2) if g2repo.head.is_detached: return (None,None,None) + prevremotecommits = None if fetch: try: + head = g2repo.head.ref + tracking = head.tracking_branch() + prevremotecommits = [i.hexsha for i in head.commit.iter_items(g2repo, f'{head.path}..{tracking.path}')] g2repo.remote().fetch() fetched = True except git.GitCommandError as msg: - print(f'Failed to get updates from {g2repo.remote().url}') + print(f'Failed to get updates from {g2repo.remote().url}\nerror: {msg}') try: head = g2repo.head.ref tracking = head.tracking_branch() localcommits = [i.hexsha for i in head.commit.iter_items(g2repo, f'{tracking.path}..{head.path}')] remotecommits = [i.hexsha for i in head.commit.iter_items(g2repo, f'{head.path}..{tracking.path}')] + if prevremotecommits is None: prevremotecommits = remotecommits + if fetch and len(prevremotecommits) != len(remotecommits): + print(f"Fetch of new GSAS-II versions uploaded {len(remotecommits)-len(prevremotecommits)} new updates.") + elif fetch and GetConfigValue('debug'): + print('Updates fetched; nothing new') return remotecommits,localcommits,fetched except: return (None,None,None) - + def countDetachedCommits(g2repo=None): '''Count the number of commits that have been made since - a commit that is containined in the master branch + a commit that is containined in the main branch - returns the count and the commit object for the + returns the count and the commit object for the parent commit that connects the current stranded - branch to the master branch. + branch to the main branch. None is returned if no connection is found ''' @@ -575,11 +611,11 @@ def countDetachedCommits(g2repo=None): g2repo = openGitRepo(path2GSAS2) if not g2repo.head.is_detached: return 0,g2repo.commit() - # is detached head in master branch? - if g2repo.commit() in g2repo.iter_commits('master'): + # is detached head in main branch? + if g2repo.commit() in g2repo.iter_commits('main'): return 0,g2repo.commit() - # count number of commits since leaving master branch - masterList = list(g2repo.iter_commits('master')) + # count number of commits since leaving main branch + masterList = list(g2repo.iter_commits('main')) for c,i in enumerate(g2repo.commit().iter_parents()): if i in masterList: return c+1,i @@ -587,48 +623,48 @@ def countDetachedCommits(g2repo=None): return None,None def gitCountRegressions(g2repo=None): - '''Count the number of new check ins on the master branch since + '''Count the number of new check ins on the main branch since the head was detached as well as any checkins made on the detached - head. + head. - :returns: mastercount,detachedcount, where + :returns: maincount,detachedcount, where - * mastercount is the number of check ins made on the master branch - remote repository since the reverted check in was first made. - * detachedcount is the number of check ins made locally + * maincount is the number of check ins made on the main branch + remote repository since the reverted check in was first made. + * detachedcount is the number of check ins made locally starting from the detached head (hopefully 0) - If the connection between the current head and the master branch + If the connection between the current head and the main branch cannot be established, None is returned for both. If the connection from the reverted check in to the newest version - (I don't see how this could happen) then only mastercount will be None. + (I don't see how this could happen) then only maincount will be None. ''' if g2repo is None: g2repo = openGitRepo(path2GSAS2) - # get parent of current head that is in master branch + # get parent of current head that is in main branch detachedcount,parent = countDetachedCommits(g2repo) if detachedcount is None: return None,None - mastercount = 0 - for h in g2repo.iter_commits('master'): + maincount = 0 + for h in g2repo.iter_commits('main'): if h == parent: - return mastercount,detachedcount - mastercount += 1 + return maincount,detachedcount + maincount += 1 return None,detachedcount def gitGetUpdate(mode='Background'): - '''Download the latest updates into the local copy of the GSAS-II - repository from the remote master, but don't actually update the - GSAS-II files being used. This can be done immediately or in background. + '''Download the latest updates into the local copy of the GSAS-II + repository from the remote main, but don't actually update the + GSAS-II files being used. This can be done immediately or in background. - In 'Background' mode, a background process is launched. The results - from the process are recorded in file in ~/GSASII_bkgUpdate.log - (located in %HOME% on Windows). A pointer to the created process is + In 'Background' mode, a background process is launched. The results + from the process are recorded in file in ~/GSASII_bkgUpdate.log + (located in %HOME% on Windows). A pointer to the created process is returned. - In 'immediate' mode, the update is performed immediately. The + In 'immediate' mode, the update is performed immediately. The function does not return until after the update is downloaded. - :returns: In 'Background' mode, returns a Popen object (see subprocess). + :returns: In 'Background' mode, returns a Popen object (see subprocess). In 'immediate' mode nothing is returned. ''' if mode == 'Background': @@ -639,25 +675,25 @@ def gitGetUpdate(mode='Background'): if GetConfigValue('debug'): print('Updates fetched') def gitHistory(values='tag',g2repo=None,maxdepth=100): - '''Provides the history of commits to the master, either as tags + '''Provides the history of commits to the main, either as tags or hash values - :param str values: specifies what type of values are returned. - If values=='hash', then hash values or for values=='tag', a - list of list of tag(s). - :param str g2repo: git.Rwpo connecton to GSAS-II installation. If - None (default) it will be opened. - :returns: a list of str values where each value is a hash for - a commit (values=='hash'), + :param str values: specifies what type of values are returned. + If values=='hash', then hash values or for values=='tag', a + list of list of tag(s). + :param str g2repo: git.Rwpo connecton to GSAS-II installation. If + None (default) it will be opened. + :returns: a list of str values where each value is a hash for + a commit (values=='hash'), for values=='tag', a list of lists, where a list of tags is provided - for each commit. When tags are provided, for any commit that does + for each commit. When tags are provided, for any commit that does not have any associated tag(s), that entry is omitted from the list. - for values=='both', a list of lists, where a hash is followed by a + for values=='both', a list of lists, where a hash is followed by a list of tags (if any) is provided ''' if g2repo is None: g2repo = openGitRepo(path2GSAS2) - history = list(g2repo.iter_commits('master')) + history = list(g2repo.iter_commits('main')) if values.lower().startswith('h'): return [i.hexsha for i in history] elif values.lower().startswith('t'): @@ -671,7 +707,7 @@ def gitHistory(values='tag',g2repo=None,maxdepth=100): # for t in g2repo.tags: # tagmap.setdefault(t.commit.hexsha, []).append(t.name) # return [[i.hexsha]+tagmap.get(i.hexsha,[]) for i in history] - + # potentially faster code r1 = [[i.hexsha]+g2repo.git.tag('--points-at',i).split('\n') for i in history[:maxdepth]] @@ -682,54 +718,48 @@ def gitHistory(values='tag',g2repo=None,maxdepth=100): def getGitBinaryReleases(cache=False): '''Retrieves the binaries and download urls of the latest release - :param bool cache: when cache is True and the binaries file names - are retrieved (does not always succeed when done via GitHub - Actions), the results are saved in a file for reuse should the - retrieval fail. Default is False so the file is not changed. + :param bool cache: when cache is True and the binaries file names + are retrieved (does not always succeed when done via GitHub + Actions), the results are saved in a file for reuse should the + retrieval fail. Default is False so the file is not changed. - :returns: a URL dict for GSAS-II binary distributions found in the newest - release in a GitHub repository. The repo location is defined in global + :returns: a URL dict for GSAS-II binary distributions found in the newest + release in a GitHub repository. The repo location is defined in global `G2binURL`. - The dict keys are references to binary distributions, which are named - as f"{platform}_p{pver}_n{npver}" where platform is determined + The dict keys are references to binary distributions, which are named + as f"{platform}_p{pver}_n{npver}" where platform is determined in :func:`GSASIIpath.GetBinaryPrefix` (linux_64, mac_arm, win_64,...) - and where `pver` is the Python version (such as "3.10") and `npver` is + and where `pver` is the Python version (such as "3.10") and `npver` is the numpy version (such as "1.26"). - The value associated with each key contains the full URL to - download a tar containing that binary distribution. + The value associated with each key contains the full URL to + download a tar containing that binary distribution. ''' - # Get first page of releases try: import requests except: print('Unable to install binaries in getGitBinaryReleases():\n requests module not available') return + # Get first page of releases. (Could there be more than one?) releases = [] tries = 0 while tries < 5: # this has been known to fail, so retry tries += 1 releases = requests.get( - url=f"{G2binURL}/releases", + url=f"{G2binURL}/releases", headers=BASE_HEADER ).json() - try: - # Get assets of latest release - assets = requests.get( - url=f"{G2binURL}/releases/{releases[-1]['id']}/assets", - headers=BASE_HEADER - ).json() - + try: + # loop over assets of latest release (will [-1] always get this?) versions = [] URLs = [] - count = 0 - for asset in assets: - if asset['name'].endswith('.tgz'): - versions.append(asset['name'][:-4]) # Remove .tgz tail - URLs.append(asset['browser_download_url']) - count += 1 - # Cache the binary releases for later use in case GitHub + for asset in releases[-1]['assets']: + if not asset['name'].endswith('.tgz'): continue + versions.append(asset['name'][:-4]) # Remove .tgz tail + URLs.append(asset['browser_download_url']) + count = len(versions) + # Cache the binary releases for later use in case GitHub # prevents us from using a query to get them if cache and count > 4: fp = open(os.path.join(path2GSAS2,'inputs','BinariesCache.txt'),'w') @@ -739,7 +769,7 @@ def getGitBinaryReleases(cache=False): fp.close() return dict(zip(versions,URLs)) except: - print('Attempt to list GSAS-II binary releases failed, sleeping for 10 sec and then retrying') + print('Attempt to get GSAS-II binary releases/assets failed, sleeping for 10 sec and then retrying') import time time.sleep(10) # this does not seem to help when GitHub is not letting the queries through @@ -754,20 +784,20 @@ def getGitBinaryReleases(cache=False): return res except: raise IOError('Cache read of releases failed too.') - -def getGitBinaryLoc(npver=None,pyver=None,verbose=True): - '''Identify the best GSAS-II binary download location from the - distributions in the latest release section of the github repository + +def getGitBinaryLoc(npver=None,pyver=None,verbose=True,debug=False): + '''Identify the best GSAS-II binary download location from the + distributions in the latest release section of the github repository on the CPU platform, and Python & numpy versions. The CPU & Python versions must match, but the numpy version may only be close. - + :param str npver: Version number to use for numpy, if None (default) the version is taken from numpy in the current Python interpreter. :param str pyver: Version number to use for Python, if None (default) the version is taken from the current Python interpreter. :param bool verbose: if True (default), status messages are printed :returns: a URL for the tar file (success) or None (failure) - ''' + ''' bindir = GetBinaryPrefix(pyver) if npver: inpver = intver(npver) @@ -776,11 +806,15 @@ def getGitBinaryLoc(npver=None,pyver=None,verbose=True): inpver = intver(np.__version__) # get binaries matching the required install, approximate match for numpy URLdict = getGitBinaryReleases() + if debug: + print('URLdict:') + for k in URLdict: print(k,URLdict[k]) versions = {} for d in URLdict: if d.startswith(bindir): v = intver(d.rstrip('/').split('_')[3].lstrip('n')) versions[v] = d + if debug: print('versions:',versions) intVersionsList = sorted(versions.keys()) if not intVersionsList: print('No binaries located to match',bindir) @@ -815,15 +849,15 @@ def getGitBinaryLoc(npver=None,pyver=None,verbose=True): def InstallGitBinary(tarURL, instDir, nameByVersion=False, verbose=True): '''Install the GSAS-II binary files into the location - specified. - + specified. + :param str tarURL: a URL for the tar file. :param str instDir: location directory to install files. This directory may not exist and will be created if needed. :param bool nameByVersion: if True, files are put into a subdirectory - of `instDir`, named to match the tar file (with plaform, Python & - numpy versions). - Default is False, where the binary files are put directly into + of `instDir`, named to match the tar file (with plaform, Python & + numpy versions). + Default is False, where the binary files are put directly into `instDir`. :param bool verbose: if True (default), status messages are printed. :returns: None @@ -837,12 +871,14 @@ def InstallGitBinary(tarURL, instDir, nameByVersion=False, verbose=True): print('Unable to install binaries in InstallGitBinary():\n requests module not available') return # download to scratch + tarobj = None tar = tempfile.NamedTemporaryFile(suffix='.tgz',delete=False) try: tar.close() if verbose: print(f'Downloading {tarURL}') r = requests.get(tarURL, allow_redirects=True) - open(tar.name, 'wb').write(r.content) + with open(tar.name, 'wb') as fp: + fp.write(r.content) # open in tar tarobj = tarfile.open(name=tar.name) if nameByVersion: @@ -865,23 +901,28 @@ def InstallGitBinary(tarURL, instDir, nameByVersion=False, verbose=True): # set file mode and mod/access times (but not ownership) os.chmod(newfil,f.mode) os.utime(newfil,(f.mtime,f.mtime)) - if verbose: print(f'Created GSAS-II binary file {newfil}') + if verbose: print(f'Created GSAS-II binary file {os.path.split(newfil)[1]}') + if verbose: print(f'Binary files created in {os.path.split(newfil)[0]}') + finally: - del tarobj + if tarobj: del tarobj os.unlink(tar.name) def GetRepoUpdatesInBackground(): - '''Wrapper to make sure that :func:`gitGetUpdate` is called only + '''Get the latest GSAS-II version info. + This serves to make sure that :func:`gitGetUpdate` is called only if git has been used to install GSAS-II. - + :returns: returns a Popen object (see subprocess) ''' if HowIsG2Installed().startswith('git'): return gitGetUpdate(mode='Background') + else: + return saveGitHubVersion() def gitStartUpdate(cmdopts): - '''Update GSAS-II in a separate process, by running this script with the - options supplied in the call to this function and then exiting GSAS-II. + '''Update GSAS-II in a separate process, by running this script with the + options supplied in the call to this function and then exiting GSAS-II. ''' cmd = [sys.executable, __file__] + cmdopts if GetConfigValue('debug'): print('Starting updates with command\n\t'+ @@ -894,14 +935,14 @@ def gitStartUpdate(cmdopts): sys.exit() def dirGitHub(dirlist,orgName=gitTutorialOwn, repoName=gitTutorialRepo): - '''Obtain a the contents of a GitHub repository directory using + '''Obtain a the contents of a GitHub repository directory using the GitHub REST API. - :param str dirlist: a list of sub-directories `['parent','child',sub']` - for `parent/child/sub` or `[]` for a file in the top-level directory. + :param str dirlist: a list of sub-directories `['parent','child',sub']` + for `parent/child/sub` or `[]` for a file in the top-level directory. :param str orgName: the name of the GitHub organization :param str repoName: the name of the GitHub repository - :returns: a list of file names or None if the dirlist info does not + :returns: a list of file names or None if the dirlist info does not reference a directory examples:: @@ -909,11 +950,11 @@ def dirGitHub(dirlist,orgName=gitTutorialOwn, repoName=gitTutorialRepo): dirGitHub([], 'GSASII', 'TutorialTest') dirGitHub(['TOF Sequential Single Peak Fit', 'data']) - The first example will get the contents of the top-level - directory for the specified repository + The first example will get the contents of the top-level + directory for the specified repository - The second example will provide the contents of the - "TOF Sequential Single Peak Fit"/data directory. + The second example will provide the contents of the + "TOF Sequential Single Peak Fit"/data directory. ''' try: import requests @@ -931,17 +972,17 @@ def dirGitHub(dirlist,orgName=gitTutorialOwn, repoName=gitTutorialRepo): return None def rawGitHubURL(dirlist,filename,orgName=gitTutorialOwn, repoName=gitTutorialRepo, - branchname="master"): - '''Create a URL that can be used to view/downlaod the raw version of - file in a GitHub repository. + branchname="main"): + '''Create a URL that can be used to view/downlaod the raw version of + file in a GitHub repository. - :param str dirlist: a list of sub-directories `['parent','child',sub']` - for `parent/child/sub` or `[]` for a file in the top-level directory. + :param str dirlist: a list of sub-directories `['parent','child',sub']` + for `parent/child/sub` or `[]` for a file in the top-level directory. :param str filename: the name of the file :param str orgName: the name of the GitHub organization :param str repoName: the name of the GitHub repository - :param str branchname: the name of the GitHub branch. Defaults - to "master". + :param str branchname: the name of the GitHub branch. Defaults + to "main". :returns: a URL-encoded URL ''' @@ -954,7 +995,7 @@ def rawGitHubURL(dirlist,filename,orgName=gitTutorialOwn, repoName=gitTutorialRe return f"https://raw.githubusercontent.com/{orgName}/{repoName}/{branchname}/{dirname}{filename}" def downloadDirContents(dirlist,targetDir,orgName=gitTutorialOwn, repoName=gitTutorialRepo): - '''Download the entire contents of a directory from a repository + '''Download the entire contents of a directory from a repository on GitHub. Used to download data for a tutorial. ''' try: @@ -972,7 +1013,8 @@ def downloadDirContents(dirlist,targetDir,orgName=gitTutorialOwn, repoName=gitTu r = requests.get(URL, allow_redirects=True) outfil = os.path.join(targetDir,fil) if r.status_code == 200: - open(outfil, 'wb').write(r.content) + with open(outfil, 'wb') as fp: + fp.write(r.content) print(f'wrote {outfil}') elif r.status_code == 404: print(f'Warning: {fil} is likely a subdirectory of directory {"/".join(dirlist)!r}') @@ -982,776 +1024,16 @@ def downloadDirContents(dirlist,targetDir,orgName=gitTutorialOwn, repoName=gitTu #============================================================================== #============================================================================== -# routines to interface with subversion -g2home = 'https://subversion.xray.aps.anl.gov/pyGSAS' # 'Define the location of the GSAS-II subversion repository' -proxycmds = [] # 'Used to hold proxy information for subversion, set if needed in whichsvn' -svnLocCache = None # 'Cached location of svn to avoid multiple searches for it' - -def MakeByte2str(arg): - '''Convert output from subprocess pipes (bytes) to str (unicode) in Python 3. - In Python 2: Leaves output alone (already str). - Leaves stuff of other types alone (including unicode in Py2) - Works recursively for string-like stuff in nested loops and tuples. - - typical use:: - - out = MakeByte2str(out) - - or:: - - out,err = MakeByte2str(s.communicate()) - - ''' - if isinstance(arg,str): return arg - if isinstance(arg,bytes): - try: - return arg.decode() - except: - if GetConfigValue('debug'): print('Decode error') - return arg - if isinstance(arg,list): - return [MakeByte2str(i) for i in arg] - if isinstance(arg,tuple): - return tuple([MakeByte2str(i) for i in arg]) - return arg - -def getsvnProxy(): - '''Loads a proxy for subversion from the proxyinfo.txt file created - by bootstrap.py or File => Edit Proxy...; If not found, then the - standard http_proxy and https_proxy environment variables are scanned - (see https://docs.python.org/3/library/urllib.request.html#urllib.request.getproxies) - with case ignored and that is used. - ''' - global proxycmds - proxycmds = [] - proxyinfo = os.path.join(os.path.expanduser('~/.G2local/'),"proxyinfo.txt") - if not os.path.exists(proxyinfo): - proxyinfo = os.path.join(path2GSAS2,"proxyinfo.txt") - if os.path.exists(proxyinfo): - fp = open(proxyinfo,'r') - host = fp.readline().strip() - # allow file to begin with comments - while host.startswith('#'): - host = fp.readline().strip() - port = fp.readline().strip() - etc = [] - line = fp.readline() - while line: - etc.append(line.strip()) - line = fp.readline() - fp.close() - setsvnProxy(host,port,etc) - return host,port,etc - import urllib.request - proxdict = urllib.request.getproxies() - varlist = ("https","http") - for var in proxdict: - if var.lower() in varlist: - proxy = proxdict[var] - pl = proxy.split(':') - if len(pl) < 2: continue - host = pl[1].strip('/') - port = '' - if len(pl) == 3: - port = pl[2].strip('/').strip() - return host,port,'' - return '','','' - -def setsvnProxy(host,port,etc=[]): - '''Sets the svn commands needed to use a proxy - ''' - global proxycmds - proxycmds = [] - host = host.strip() - port = port.strip() - if host: - proxycmds.append('--config-option') - proxycmds.append('servers:global:http-proxy-host='+host) - if port: - proxycmds.append('--config-option') - proxycmds.append('servers:global:http-proxy-port='+port) - for item in etc: - proxycmds.append(item) - -def whichsvn(): - '''Returns a path to the subversion exe file, if any is found. - Searches the current path after adding likely places where GSAS-II - might install svn. - - :returns: None if svn is not found or an absolute path to the subversion - executable file. - ''' - # use a previosuly cached svn location - global svnLocCache - if svnLocCache: return svnLocCache - # prepare to find svn - is_exe = lambda fpath: os.path.isfile(fpath) and os.access(fpath, os.X_OK) - svnprog = 'svn' - if sys.platform.startswith('win'): svnprog += '.exe' - host,port,etc = getsvnProxy() - if GetConfigValue('debug') and host: - print('DBG_Using proxy host {} port {}'.format(host,port)) - if GetConfigValue('svn_exec'): - exe_file = GetConfigValue('svn_exec') - print('Using ',exe_file) - if is_exe(exe_file): - try: - p = subprocess.Popen([exe_file,'help'],stdout=subprocess.PIPE) - res = p.stdout.read() - if not res: return - p.communicate() - svnLocCache = os.path.abspath(exe_file) - return svnLocCache - except: - pass - # add likely places to find subversion when installed with GSAS-II - pathlist = os.environ["PATH"].split(os.pathsep) - pathlist.insert(0,os.path.split(sys.executable)[0]) - pathlist.insert(1,path2GSAS2) - for rpt in ('..','bin'),('..','Library','bin'),('svn','bin'),('svn',),('.'): - pt = os.path.normpath(os.path.join(path2GSAS2,*rpt)) - if os.path.exists(pt): - pathlist.insert(0,pt) - # search path for svn or svn.exe - for path in pathlist: - exe_file = os.path.join(path, svnprog) - if is_exe(exe_file): - try: - p = subprocess.Popen([exe_file,'help'],stdout=subprocess.PIPE) - res = p.stdout.read() - if not res: return - p.communicate() - svnLocCache = os.path.abspath(exe_file) - return svnLocCache - except: - pass - svnLocCache = None - -svn_version = None -def svnVersion(svn=None): - '''Get the version number of the current subversion executable. - The result is cached, as this takes a bit of time to run and - is done a fair number of times. - - :returns: a string with a version number such as "1.6.6" or None if - subversion is not found. - - ''' - global svn_version - if svn_version is not None: - return svn_version - if not svn: svn = whichsvn() - if not svn: return - - cmd = [svn,'--version','--quiet'] - s = subprocess.Popen(cmd, - stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print ('subversion error!\nout=%s'%out) - print ('err=%s'%err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - return None - svn_version = out.strip() - return svn_version - -def svnVersionNumber(svn=None): - '''Get the version number of the current subversion executable - - :returns: a fractional version number such as 1.6 or None if - subversion is not found. - - ''' - ver = svnVersion(svn) - if not ver: return - M,m = ver.split('.')[:2] - return int(M)+int(m)/10. - -def svnGetLog(fpath=os.path.split(__file__)[0],version=None): - '''Get the revision log information for a specific version of the specified package - - :param str fpath: path to repository dictionary, defaults to directory where - the current file is located. - :param int version: the version number to be looked up or None (default) - for the latest version. - - :returns: a dictionary with keys (one hopes) 'author', 'date', 'msg', and 'revision' - - ''' - import xml.etree.ElementTree as ET - svn = whichsvn() - if not svn: return - if version is not None: - vstr = '-r'+str(version) - else: - vstr = '-rHEAD' - - cmd = [svn,'log',fpath,'--xml',vstr] - if proxycmds: cmd += proxycmds - s = subprocess.Popen(cmd, - stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print ('out=%s'%out) - print ('err=%s'%err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - return None - x = ET.fromstring(out) - d = {} - for i in x.iter('logentry'): - d = {'revision':i.attrib.get('revision','?')} - for j in i: - d[j.tag] = j.text - break # only need the first - return d - -svnLastError = '' -def svnGetRev(fpath=os.path.split(__file__)[0],local=True,verbose=True): - '''Obtain the version number for the either the last update of the local version - or contacts the subversion server to get the latest update version (# of Head). - - :param str fpath: path to repository dictionary, defaults to directory where - the current file is located - :param bool local: determines the type of version number, where - True (default): returns the latest installed update - False: returns the version number of Head on the server - - :Returns: the version number as an str or - None if there is a subversion error (likely because the path is - not a repository or svn is not found). The error message is placed in - global variable svnLastError - ''' - - import xml.etree.ElementTree as ET - svn = whichsvn() - if not svn: return - if local: - cmd = [svn,'info',fpath,'--xml'] - else: - cmd = [svn,'info',fpath,'--xml','-rHEAD'] - if svnVersionNumber() >= 1.6: - cmd += ['--non-interactive', '--trust-server-cert'] - if proxycmds: cmd += proxycmds - # if GetConfigValue('debug'): - # s = 'subversion command:\n ' - # for i in cmd: s += i + ' ' - # print(s) - s = subprocess.Popen(cmd, stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - if verbose: - print ('svn failed\n%s'%out) - print ('err=%s'%err) - print('\nsvn command:',' '.join(cmd)) - global svnLastError - svnLastError = err - return None - x = ET.fromstring(out) - for i in x.iter('entry'): - rev = i.attrib.get('revision') - if rev: return rev - -def svnFindLocalChanges(fpath=os.path.split(__file__)[0]): - '''Returns a list of files that were changed locally. If no files are changed, - the list has length 0 - - :param fpath: path to repository dictionary, defaults to directory where - the current file is located - - :returns: None if there is a subversion error (likely because the path is - not a repository or svn is not found) - - ''' - import xml.etree.ElementTree as ET - svn = whichsvn() - if not svn: return - cmd = [svn,'status',fpath,'--xml'] - if proxycmds: cmd += proxycmds - s = subprocess.Popen(cmd, - stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: return None - x = ET.fromstring(out) - changed = [] - for i in x.iter('entry'): - if i.find('wc-status').attrib.get('item') == 'modified': - changed.append(i.attrib.get('path')) - return changed - -def svnCleanup(fpath=os.path.split(__file__)[0],verbose=True): - '''This runs svn cleanup on a selected local directory. - - :param str fpath: path to repository dictionary, defaults to directory where - the current file is located - ''' - svn = whichsvn() - if not svn: return - if verbose: print(u"Performing svn cleanup at "+fpath) - cmd = [svn,'cleanup',fpath] - if verbose: showsvncmd(cmd) - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print(60*"=") - print("****** An error was noted, see below *********") - print(60*"=") - print(err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - #raise Exception('svn cleanup failed') - return False - elif verbose: - print(out) - return True - -def svnUpdateDir(fpath=os.path.split(__file__)[0],version=None,verbose=True): - '''This performs an update of the files in a local directory from a server. - - :param str fpath: path to repository dictionary, defaults to directory where - the current file is located - :param version: the number of the version to be loaded. Used only - cast as a string, but should be an integer or something that corresponds to a - string representation of an integer value when cast. A value of None (default) - causes the latest version on the server to be used. - ''' - svn = whichsvn() - if not svn: return - if version: - verstr = '-r' + str(version) - else: - verstr = '-rHEAD' - if verbose: print(u"Updating files at "+fpath) - cmd = [svn,'update',fpath,verstr, - '--non-interactive', - '--accept','theirs-conflict','--force'] - if svnVersionNumber() >= 1.6: - cmd += ['--trust-server-cert'] - if proxycmds: cmd += proxycmds - #if verbose or GetConfigValue('debug'): - if verbose: - s = 'subversion command:\n ' - for i in cmd: s += i + ' ' - print(s) - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print(60*"=") - print("****** An error was noted, see below *********") - print(60*"=") - print(err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - if svnCleanup(fpath): - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print(60*"=") - print("****** Drat, failed again: *********") - print(60*"=") - print(err) - else: - return - if 'Checksum' in err: # deal with Checksum problem - err = svnChecksumPatch(svn,fpath,verstr) - if err: - print('error from svnChecksumPatch\n\t',err) - else: - return - raise Exception('svn update failed') - elif verbose: - print(out) - -def showsvncmd(cmd): - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - -def svnChecksumPatch(svn,fpath,verstr): - '''This performs a fix when svn cannot finish an update because of - a Checksum mismatch error. This seems to be happening on OS X for - unclear reasons. - ''' - print('\nAttempting patch for svn Checksum mismatch error\n') - svnCleanup(fpath) - cmd = [svn,'update','--set-depth','empty', - os.path.join(fpath,'bindist')] - showsvncmd(cmd) - if svnVersionNumber() >= 1.6: - cmd += ['--non-interactive', '--trust-server-cert'] - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - #if err: print('error=',err) - cmd = [svn,'switch',g2home+'/trunk/bindist', - os.path.join(fpath,'bindist'), - '--non-interactive', '--trust-server-cert', '--accept', - 'theirs-conflict', '--force', '-rHEAD', '--ignore-ancestry'] - showsvncmd(cmd) - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - DownloadG2Binaries(g2home,verbose=True) - cmd = [svn,'update','--set-depth','infinity', - os.path.join(fpath,'bindist')] - if svnVersionNumber() >= 1.6: - cmd += ['--non-interactive', '--trust-server-cert'] - showsvncmd(cmd) - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - #if err: print('error=',err) - cmd = [svn,'update',fpath,verstr, - '--non-interactive', - '--accept','theirs-conflict','--force'] - if svnVersionNumber() >= 1.6: - cmd += ['--trust-server-cert'] - if proxycmds: cmd += proxycmds - showsvncmd(cmd) - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - #if err: print('error=',err) - return err - -def svnUpgrade(fpath=os.path.split(__file__)[0]): - '''This reformats subversion files, which may be needed if an upgrade of subversion is - done. - - :param str fpath: path to repository dictionary, defaults to directory where - the current file is located - ''' - svn = whichsvn() - if not svn: return - cmd = [svn,'upgrade',fpath,'--non-interactive'] - if proxycmds: cmd += proxycmds - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print("svn upgrade did not happen (this is probably OK). Messages:") - print (err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - -def svnUpdateProcess(version=None,projectfile=None,branch=None): - '''perform an update of GSAS-II in a separate python process''' - if not projectfile: - projectfile = '' - else: - projectfile = os.path.realpath(projectfile) - print ('restart using %s'%projectfile) - if branch: - version = branch - elif not version: - version = '' - else: - version = str(version) - # start the upgrade in a separate interpreter (avoids loading .pyd files) - ex = sys.executable - if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable - if os.path.exists(ex+'w'): ex += 'w' - proc = subprocess.Popen([ex,__file__,projectfile,version]) - if sys.platform != "win32": - proc.wait() - sys.exit() - -def svnSwitchDir(rpath,filename,baseURL,loadpath=None,verbose=True): - '''This performs a switch command to move files between subversion trees. - Note that if the files were previously downloaded, - the switch command will update the files to the newest version. - - :param str rpath: path to locate files, relative to the GSAS-II - installation path (defaults to path2GSAS2) - :param str URL: the repository URL - :param str loadpath: the prefix for the path, if specified. Defaults to path2GSAS2 - :param bool verbose: if True (default) diagnostics are printed - ''' - svn = whichsvn() - if not svn: return - URL = baseURL[:] - if baseURL[-1] != '/': - URL = baseURL + '/' + filename - else: - URL = baseURL + filename - if loadpath: - fpath = os.path.join(loadpath,rpath,filename) - svntmp = os.path.join(loadpath,'.svn','tmp') - else: - fpath = os.path.join(path2GSAS2,rpath,filename) - svntmp = os.path.join(path2GSAS2,'.svn','tmp') - # fix up problems with missing empty directories - if not os.path.exists(fpath): - print('Repairing missing directory',fpath) - cmd = [svn,'revert',fpath] - s = subprocess.Popen(cmd,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if out: print(out) - if err: print(err) - if not os.path.exists(svntmp): - print('Repairing missing directory',svntmp) - cmd = ['mkdir',svntmp] - s = subprocess.Popen(cmd,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if out: print(out) - if err: print(err) - - cmd = [svn,'switch',URL,fpath, - '--non-interactive','--trust-server-cert', - '--accept','theirs-conflict','--force','-rHEAD'] - if svnVersionNumber(svn) > 1.6: cmd += ['--ignore-ancestry'] - if proxycmds: cmd += proxycmds - if verbose: - print(u"Loading files to "+fpath+u"\n from "+URL) - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print(60*"=") - print ("****** An error was noted, see below *********") - print(60*"=") - print ('out=%s'%out) - print ('err=%s'%err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - if svnCleanup(fpath): - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print(60*"=") - print("****** Drat, failed again: *********") - print(60*"=") - print(err) - else: - return True - return False - if verbose: - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - print('\n=== Output from svn switch'+(43*'=')) - print(out.strip()) - print((70*'=')+'\n') - return True - -def svnInstallDir(URL,loadpath): - '''Load a subversion tree into a specified directory - - :param str URL: the repository URL - :param str loadpath: path to locate files - - ''' - svn = whichsvn() - if not svn: return - cmd = [svn,'co',URL,loadpath,'--non-interactive'] - if svnVersionNumber() >= 1.6: cmd += ['--trust-server-cert'] - print("Loading files from "+URL) - if proxycmds: cmd += proxycmds - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) #this fails too easily - if err: - print(60*"=") - print ("****** An error was noted, see below *********") - print(60*"=") - print (err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - if svnCleanup(loadpath): - s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print(60*"=") - print("****** Drat, failed again: *********") - print(60*"=") - print(err) - return False - else: - return False - print ("Files installed at: "+loadpath) - return True - -def svnGetFileStatus(fpath=os.path.split(__file__)[0],version=None): - '''Compare file status to repository (svn status -u) - - :returns: updatecount,modcount,locked where - updatecount is the number of files waiting to be updated from - repository - modcount is the number of files that have been modified locally - locked is the number of files tagged as locked - ''' - import xml.etree.ElementTree as ET - svn = whichsvn() - if version is not None: - vstr = '-r'+str(version) - else: - vstr = '-rHEAD' - cmd = [svn,'st',fpath,'--xml','-u',vstr] - if svnVersionNumber() >= 1.6: - cmd += ['--non-interactive', '--trust-server-cert'] - if proxycmds: cmd += proxycmds - s = subprocess.Popen(cmd, - stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out,err = MakeByte2str(s.communicate()) - if err: - print ('out=%s'%out) - print ('err=%s'%err) - s = '\nsvn command: ' - for i in cmd: s += i + ' ' - print(s) - return None - - locked = 0 - updatecount = 0 - modcount = 0 - x = ET.fromstring(out) - for i0 in x.iter('entry'): - #filename = i0.attrib.get('path','?') - #wc_rev = '' - status = '' - switched = '' - for i1 in i0.iter('wc-status'): - #wc_rev = i1.attrib.get('revision','') - status = i1.attrib.get('item','') - switched = i1.attrib.get('switched','') - if i1.attrib.get('wc-locked',''): locked += 1 - if status == "unversioned": continue - if switched == "true": continue - if status == "modified": - modcount += 1 - elif status == "normal": - updatecount += 1 - #file_rev = '' - #for i2 in i1.iter('commit'): - # file_rev = i2.attrib.get('revision','') - #local_status = '' - #for i1 in i0.iter('repos-status'): - # local_status = i1.attrib.get('item','') - #print(filename,wc_rev,file_rev,status,local_status,switched) - return updatecount,modcount,locked - -def svnList(URL,verbose=True): - '''Get a list of subdirectories from and svn repository - ''' - svn = whichsvn() - if not svn: - print('**** unable to load files: svn not found ****') - return '' - # get binaries matching the required type -- other than for the numpy version - cmd = [svn, 'list', URL,'--non-interactive', '--trust-server-cert'] - if proxycmds: cmd += proxycmds - if verbose: - s = 'Running svn command:\n ' - for i in cmd: s += i + ' ' - print(s) - p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - res,err = MakeByte2str(p.communicate()) - return res - -def DownloadG2Binaries(g2home,verbose=True): - '''Download GSAS-II binaries from appropriate section of the - GSAS-II svn repository based on the platform, numpy and Python - version - ''' - bindir = GetBinaryPrefix() - #npver = 'n{}.{}'.format(*np.__version__.split('.')[0:2]) - inpver = intver(np.__version__) - svn = whichsvn() - if not svn: - print('**** unable to load files: svn not found ****') - return '' - # get binaries matching the required type -- other than for the numpy version - cmd = [svn, 'list', g2home + '/Binaries/','--non-interactive', '--trust-server-cert'] - if proxycmds: cmd += proxycmds - if verbose: - s = 'Running svn command:\n ' - for i in cmd: s += i + ' ' - print(s) - p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - res,err = MakeByte2str(p.communicate()) - versions = {} - for d in res.split(): - if d.startswith(bindir): - v = intver(d.rstrip('/').split('_')[3].lstrip('n')) - versions[v] = d - intVersionsList = sorted(versions.keys()) - if not intVersionsList: - print('No binaries located matching',bindir) - return - elif inpver < min(intVersionsList): - vsel = min(intVersionsList) - print('Warning: The current numpy version, {}, is older than\n\tthe oldest dist version, {}' - .format(np.__version__,fmtver(vsel))) - elif inpver >= max(intVersionsList): - vsel = max(intVersionsList) - if verbose: print( - 'FYI: The current numpy version, {}, is newer than the newest dist version {}' - .format(np.__version__,fmtver(vsel))) - else: - vsel = min(intVersionsList) - for v in intVersionsList: - if v <= inpver: - vsel = v - else: - if verbose: print( - 'FYI: Selecting dist version {} as the current numpy version, {},\n\tis older than the next dist version {}' - .format(fmtver(vsel),np.__version__,fmtver(v))) - break - distdir = g2home + '/Binaries/' + versions[vsel] - # switch reset command: distdir = g2home + '/trunk/bindist' - svnSwitchDir('bindist','',distdir,verbose=verbose) - return os.path.join(path2GSAS2,'bindist') - -# def svnTestBranch(loc=None): -# '''Returns the name of the branch directory if the installation has been switched. -# Returns none, if not a branch -# the test 2frame branch. False otherwise -# ''' -# if loc is None: loc = path2GSAS2 -# svn = whichsvn() -# if not svn: -# print('**** unable to load files: svn not found ****') -# return '' -# cmd = [svn, 'info', loc] -# if proxycmds: cmd += proxycmds -# p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) -# res,err = MakeByte2str(p.communicate()) -# for l in res.split('\n'): -# if "Relative URL:" in l: break -# if "/branch/" in l: -# return l[l.find("/branch/")+8:].strip() -# else: -# return None - -def svnSwitch2branch(branch=None,loc=None,svnHome=None): - '''Switch to a subversion branch if specified. Switches to trunk otherwise. - ''' - if svnHome is None: svnHome = g2home - svnURL = svnHome + '/trunk' - if branch: - if svnHome.endswith('/'): - svnURL = svnHome[:-1] - else: - svnURL = svnHome - if branch.startswith('/'): - svnURL += branch - else: - svnURL += '/' + branch - svnSwitchDir('','',svnURL,loadpath=loc) -#============================================================================== -#============================================================================== - def runScript(cmds=[], wait=False, G2frame=None): '''run a shell script of commands in an external process - + :param list cmds: a list of str's, each ietm containing a shell (cmd.exe or bash) command - :param bool wait: if True indicates the commands should be run and then - the script should return. If False, then the currently running Python + :param bool wait: if True indicates the commands should be run and then + the script should return. If False, then the currently running Python will exit. Default is False :param wx.Frame G2frame: provides the location of the current .gpx file - to be used to restart GSAS-II after running the commands, if wait + to be used to restart GSAS-II after running the commands, if wait is False. Default is None which prevents restarting GSAS-II regardless of the value of wait. ''' @@ -1763,7 +1045,7 @@ def runScript(cmds=[], wait=False, G2frame=None): suffix = '.sh' else: suffix = '.bat' - + fp = tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False) shellname = fp.name for line in cmds: @@ -1775,7 +1057,7 @@ def runScript(cmds=[], wait=False, G2frame=None): projectfile = '' if G2frame.GSASprojectfile: projectfile = os.path.realpath(G2frame.GSASprojectfile) - main = os.path.join(path2GSAS2,'GSASII.py') + main = os.path.join(path2GSAS2,'G2.py') ex = sys.executable if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable if os.path.exists(ex+'w'): ex += 'w' @@ -1797,11 +1079,11 @@ def runScript(cmds=[], wait=False, G2frame=None): def IPyBreak_base(userMsg=None): '''A routine that invokes an IPython session at the calling location - This routine is only used when debug=True is set in the configuration + This routine is only used when debug=True is set in the configuration settings ''' savehook = sys.excepthook # save the exception hook - try: + try: from IPython.terminal.embed import InteractiveShellEmbed except ImportError: try: @@ -1830,15 +1112,16 @@ def exceptHook(*args): '''A routine to be called when an exception occurs. It prints the traceback with fancy formatting and then calls an IPython shell with the environment of the exception location. - + This routine is only used when debug=True is set in the configuration settings ''' try: - from IPython.core import ultratb + #from IPython.core import ultratb + import IPython.core.ultratb except: pass - try: + try: from IPython.terminal.embed import InteractiveShellEmbed import IPython.core if sys.platform.startswith('win'): @@ -1883,10 +1166,10 @@ def DoNothing(): '''A routine that does nothing. This is called in place of IPyBreak and pdbBreak except when the debug option is set True in the configuration settings ''' - pass + pass def InvokeDebugOpts(): - 'Called in GSASII.py to set up debug options' + 'Called to set up debug options' if any('SPYDER' in name for name in os.environ): print('Running from Spyder, keeping breakpoint() active & skipping exception trapping') elif GetConfigValue('debug'): @@ -1895,6 +1178,7 @@ def InvokeDebugOpts(): global pdbBreak pdbBreak = pdb.set_trace import IPython + IPython global IPyBreak IPyBreak = IPyBreak_base sys.excepthook = exceptHook @@ -1905,8 +1189,45 @@ def InvokeDebugOpts(): else: # not in spyder or debug enabled, hide breakpoints os.environ['PYTHONBREAKPOINT'] = '0' -def TestSPG(fpth): - '''Test if pyspg.[so,.pyd] can be run from a location in the path +def TestSPG(): + '''Test if pyspg.[so,.pyd] can be run from a location in the existing path + Do not modify the path if not. + ''' + def showVersion(): + try: + f = os.path.join(os.path.dirname(pyspg.__file__),'GSASIIversion.txt') + with open(f,'r') as fp: + version = fp.readline().strip() + vnum = fp.readline().strip() + print(f' Binary ver: {vnum}, {version}') + except: + if GetConfigValue('debug'): + print(' Binaries: undated') + try: + from . import pyspg + pyspg + showVersion() + return True + except ImportError: + pass + try: + import pyspg + pyspg + except ImportError: + return False + try: + pyspg.sgforpy('P -1') + except Exception as err: + print(70*'=') + print(f'Module pyspg in {pyspg.__file__} could not be run\nerror msg: {err}') + print(70*'=') + return False + showVersion() + return True + +def pathhack_TestSPG(fpth): + '''Test if pyspg.[so,.pyd] can be run from a specified location. If so + modify the path to include it. ''' try: if not os.path.exists(fpth): return False @@ -1946,158 +1267,121 @@ def TestSPG(fpth): return False sys.path = savpath return True - -def SetBinaryPath(printInfo=False, loadBinary=False): + +def SetBinaryPath(showConfigMsg=False): ''' - Add location of GSAS-II shared libraries (binaries: .so or - .pyd files) to path - - This routine must be executed after GSASIIpath is imported - and before any other GSAS-II imports are done, since - they assume binary files are in path - - :param bool printInfo: When True, information is printed to show - has happened (default is False) - :param bool loadBinary: no longer in use. This is now done in - :func:`GSASIIdataGUI.ShowVersions`. + Add location of GSAS-II shared libraries (binaries: .so or + .pyd files) to path (when needed). When GSAS-II is installed by + pixi, no change in the path is needed. + + This routine must be executed after GSASIIpath is imported + and before any other GSAS-II imports are done, since + they may assume binary files are in path + + :param bool showConfigMsg: When True, config info is shown (default is False) ''' - # run this routine only once no matter how many times it is called - # using the following globals to check for repeated calls + # cache the results of this routine so that repeated calls + # only search for binary routines once global BinaryPathLoaded,binaryPath,BinaryPathFailed if BinaryPathLoaded or BinaryPathFailed: return try: - inpver = intver(np.__version__) + intver(np.__version__) except (AttributeError,TypeError): # happens on building docs return - if path2GSAS2 not in sys.path: - sys.path.insert(0,path2GSAS2) # make sure current path is used - binpath = None - binprfx = GetBinaryPrefix() - # places to look for the GSAS-II binary directory - binseapath = [os.path.abspath(sys.path[0])] # where Python is installed - binseapath += [os.path.abspath(os.path.dirname(__file__))] # directory where this file is found - binseapath += [os.path.dirname(binseapath[-1])] # parent of above directory - binseapath += [os.path.expanduser('~/.GSASII')] # directory in user's home - def appendIfExists(searchpathlist,loc,subdir): - newpath = os.path.join(loc,subdir) - if os.path.exists(newpath): - if newpath in searchpathlist: return - searchpathlist.append(newpath) - searched = [] - for loc in binseapath: - if loc in searched: continue - searched.append(loc) - # Look at bin directory (created by a local compile) before looking for standard dist files - searchpathlist = [] - appendIfExists(searchpathlist,loc,'bin') - appendIfExists(searchpathlist,loc,'bindist') - appendIfExists(searchpathlist,loc,'GSASII-bin') - # also look for directories named by platform etc in loc/AllBinaries or loc - versions = {} - namedpath = glob.glob(os.path.join(loc,'AllBinaries',binprfx+'*')) - namedpath += glob.glob(os.path.join(loc,'GSASII-bin',binprfx+'*')) - for d in namedpath: - d = os.path.realpath(d) - v = intver(d.rstrip('/').split('_')[-1].lstrip('n')) - versions[v] = d - vmin = None - vmax = None - # try to order the search in a way that makes sense - for v in sorted(versions.keys()): - if v <= inpver: - vmin = v - elif v > inpver: - vmax = v - break - if vmin in versions and versions[vmin] not in searchpathlist: - searchpathlist.append(versions[vmin]) - if vmax in versions and versions[vmax] not in searchpathlist: - searchpathlist.append(versions[vmax]) - for fpth in searchpathlist: - if TestSPG(fpth): - binpath = fpth # got one that works, look no farther! - break - else: - continue - break - if binpath: # were GSAS-II binaries found? - binaryPath = binpath + try: + from GSASII import pypowder + pypowder + binaryPath = None # special value to indicate that binaries have been installed into package + if showConfigMsg: + print(f'GSAS-II binaries co-located with GSAS-II: {os.path.dirname(__file__)}') BinaryPathLoaded = True - else: - print('*** ERROR: Unable to find GSAS-II binaries. Much of GSAS-II cannot function') - BinaryPathFailed = True - return None - - # add the data import and export directory to the search path - if binpath not in sys.path: sys.path.insert(0,binpath) - if printInfo: print(f'GSAS-II binary directory: {binpath}') - newpath = os.path.join(path2GSAS2,'imports') - if newpath not in sys.path: sys.path.append(newpath) - newpath = os.path.join(path2GSAS2,'exports') - if newpath not in sys.path: sys.path.append(newpath) - LoadConfig(printInfo) + LoadConfig(showConfigMsg) + return + except ImportError: + pass -def LoadConfig(printInfo=True): - '''Read configuration settings from config.py, if present - ''' - global configDict try: - import config - configDict = config.__dict__ - import inspect - vals = [True for i in inspect.getmembers(config) if '__' not in i[0]] - if printInfo: - print (str(len(vals))+' values read from config file '+os.path.abspath(config.__file__)) + from . import pathHacking except ImportError: - configDict = {'Clip_on':True} - except Exception as err: - print(60*'*',"\nError reading config.py file") - if printInfo: - import traceback - print(traceback.format_exc()) - print(60*'*') - configDict = {'Clip_on':True} - -def WriteIniConfi(configDict): - '''Write the configDict information to the GSAS-II ini settings - into file ~/.GSASII/config.ini. This routine will eventually be - plumbed into GSASIIctrlGUI.SaveConfigVars. + print('Binary load failed and module pathHacking not present') + BinaryPathFailed = True + return + + LoadConfig(showConfigMsg) + BinaryPathFailed = pathHacking._path_discovery(showConfigMsg) + +def WriteConfig(configDict): + '''Write the configDict information to the GSAS-II ini settings + into file ~/.GSASII/config.ini. Called from + :func:`GSASIIctrlGUI.SaveConfigVars`. ''' import configparser - - localdir = os.path.expanduser('~/.GSASII') + + localdir = os.path.expanduser(os.path.normpath('~/.GSASII')) if not os.path.exists(localdir): try: - os.mkdir(g2local) + os.mkdir(localdir) print(f'Created directory {localdir}') - except: - print(f'Error trying to create directory {localdir}') + except Exception as msg: + print(f'Error trying to create directory {localdir}\n{msg}') return True cfgfile = os.path.join(localdir,'config.ini') cfgP = configparser.ConfigParser() + if os.path.exists(cfgfile): + cfgP.read(cfgfile) # read previous file so other sections are retained cfgP['GUI settings'] = configDict # Write the configuration file with open(cfgfile, 'w') as configfile: cfgP.write(configfile) - print(f"Configuraton settings saved as {cfgfile}") + print(f"Configuration settings saved as {cfgfile}") + +def LoadConfig(printInfo=True): + '''Read configuration settings from ~/.GSASII/config.ini, if present. + Place the values into global dict configDict. + + :param bool printInfo: if printInfo is True (default) then a message + is shown with the number of settings read (upon startup). + ''' + def XferConfigIni(): + '''copy the contents of the config.py file to file ~/.GSASII/config.ini. + This "patch code" used for master->main transition and can eventually + be removed. + ''' + import types + configDict = {} + try: + import config + #import config_example as config + for i in config.__dict__: + if i.startswith('__') and i.endswith('__'): continue + if isinstance(config.__dict__[i],types.ModuleType): continue + configDict.update({i:str(config.__dict__[i])}) + except ImportError: + print("New install: start without a config.py file") + return + except Exception as err: + print("Error reading config.py file\n",err) + return + print(f"Contents of {config.__file__} to be written from config.py...") + WriteConfig(configDict) -def LoadIniConfig(): - ''' - Not currently in use, but intended to replace LoadConfig. - ''' import configparser global configDict - configDict = {'Clip_on':True} - cfgfile = os.path.expanduser('~/.GSASII/config.ini') + configDict = {} + cfgfile = os.path.expanduser(os.path.normpath('~/.GSASII/config.ini')) if not os.path.exists(cfgfile): print(f'N.B. Configuration file {cfgfile} does not exist') - return + # patch 2/7/25: transform GSAS-II config.py contents to config.ini + XferConfigIni() try: - import config_example - except ImportError as err: - print("Error importing config_example.py file\n",err) - return + from . import config_example + except ImportError: + try: + import GSASII.config_example as config_example + except ImportError as err: + print("Error importing config_example.py file\n",err) + return # get the original capitalization (lost by configparser) capsDict = {key.lower():key for key in config_example.__dict__ if not key.startswith('__')} @@ -2111,7 +1395,11 @@ def LoadIniConfig(): return # Access values from the configuration file - cfgG = cfg['GUI settings'] + try: + cfgG = cfg['GUI settings'] + except KeyError: + cfgG = {} + for key in cfgG: key = key.lower() # not needed... but in case configparser ever changes capKey = capsDict.get(key) @@ -2119,11 +1407,9 @@ def LoadIniConfig(): print(f'Item {key} not defined in config_example') continue try: - print('\n',key,capKey) - print(cfgG[key]) if cfgG[key] == 'None': configDict[capKey] = None - elif key.endswith('_pos') or key.endswith('_size'): # list of integers + elif key.endswith('_pos') or key.endswith('_size'): # list of integers configDict[capKey] = tuple([int(i) for i in cfgG[key].strip('()').split(',')]) elif key.endswith('_location') or key.endswith('_directory') or key.endswith('_exec'): # None (above) or str @@ -2134,7 +1420,7 @@ def LoadIniConfig(): res = [] else: res = [i.strip("'").replace(r'\\','\\') for i in s.split(', ')] - configDict[capKey] = res + configDict[capKey] = res elif isinstance(config_example.__dict__[capKey],bool): configDict[capKey] = cfgG.getboolean(key) elif isinstance(config_example.__dict__[capKey],float): @@ -2148,43 +1434,10 @@ def LoadIniConfig(): continue except: continue - print(f'{config_example.__dict__[capKey]!r}') - print(f'{configDict[capKey]!r}') - -# def MacStartGSASII(g2script,project=''): -# '''Start a new instance of GSAS-II by opening a new terminal window and starting -# a new GSAS-II process. Used on Mac OS X only. - -# :param str g2script: file name for the GSASII.py script -# :param str project: GSAS-II project (.gpx) file to be opened, default is blank -# which opens a new project -# ''' -# if project and os.path.splitext(project)[1] != '.gpx': -# print(f'file {project} cannot be used. Not GSAS-II project (.gpx) file') -# return -# if project and not os.path.exists(project): -# print(f'file {project} cannot be found.') -# return -# elif project: -# project = os.path.abspath(project) -# if not os.path.exists(project): -# print(f'lost project {project} with abspath') -# raise Exception(f'lost project {project} with abspath') -# g2script = os.path.abspath(g2script) -# pythonapp = sys.executable -# if os.path.exists(pythonapp+'w'): pythonapp += 'w' -# script = f''' -# set python to "{pythonapp}" -# set appwithpath to "{g2script}" -# set filename to "{project}" -# set filename to the quoted form of the POSIX path of filename - -# tell application "Terminal" -# activate -# do script python & " " & appwithpath & " " & filename & "; exit" -# end tell -# ''' -# subprocess.Popen(["osascript","-e",script]) + if printInfo: + print (f'{len(configDict)} values read from {cfgfile}') + # make sure this value is set + configDict['Clip_on'] = configDict.get('Clip_on',True) def MacRunScript(script): '''Start a bash script in a new terminal window. @@ -2209,7 +1462,7 @@ def MacRunScript(script): # conda/pip routines def findConda(): '''Determines if GSAS-II has been installed as g2conda or gsas2full - with conda located relative to this file. + with conda located relative to this file. We could also look for conda relative to the python (sys.executable) image, but I don't want to muck around with python that someone else installed. @@ -2225,19 +1478,20 @@ def findConda(): return conda,activate else: return None - + def condaTest(requireAPI=False): '''Returns True if it appears that Python is being run under Anaconda Python with conda present. Tests for conda environment vars and that the conda package is installed in the current environment. :returns: True, if running under Conda - ''' + ''' if not all([(i in os.environ) for i in ('CONDA_DEFAULT_ENV','CONDA_EXE', 'CONDA_PREFIX', 'CONDA_PYTHON_EXE')]): return False if requireAPI: # is the conda package available? try: import conda.cli.python_api + conda.cli.python_api except: print('You do not have the conda package installed in this environment', '\nConsider using the "conda install conda" command') @@ -2245,7 +1499,7 @@ def condaTest(requireAPI=False): # There is no foolproof way to check if someone activates conda # but then calls a different Python using its path... - # ...If we are in the base environment then the conda Python + # ...If we are in the base environment then the conda Python # should be the same path as the one currently being run: if os.environ['CONDA_DEFAULT_ENV'] == 'base': try: @@ -2261,17 +1515,17 @@ def condaTest(requireAPI=False): dir2 = os.path.dirname(sys.executable) if sys.platform != "win32": # python in .../bin/.. dir1 = os.path.dirname(dir1) - dir2 = os.path.dirname(dir2) + dir2 = os.path.dirname(dir2) return commonPath(dir1,dir2) def condaInstall(packageList): '''Installs one or more packages using the anaconda conda package - manager. Can be used to install multiple packages and optionally + manager. Can be used to install multiple packages and optionally use channels. :param list packageList: a list of strings with name(s) of packages - and optionally conda options. - Examples:: + and optionally conda options. + Examples:: packageList=['gsl'] packageList=['-c','conda-forge','wxpython'] @@ -2306,7 +1560,7 @@ def condaInstall(packageList): print(f"\nConda error occurred, see below\n{msg}") return "error occurred" return None - + def fullsplit(fil,prev=None): '''recursive routine to split all levels of directory names ''' @@ -2322,8 +1576,8 @@ def fullsplit(fil,prev=None): return out def commonPath(dir1,dir2): - '''Check if two directories share a path. Note that paths - are considered the same if either directory is a subdirectory + '''Check if two directories share a path. Note that paths + are considered the same if either directory is a subdirectory of the other, but not if they are in different subdirectories /a/b/c shares a path with /a/b/c/d but /a/b/c/d and /a/b/c/e do not. @@ -2337,11 +1591,11 @@ def commonPath(dir1,dir2): def pipInstall(packageList): '''Installs one or more packages using the pip package installer. Use of this should be avoided if conda can be used (see :func:`condaTest` - to test for conda). Can be used to install multiple packages together. - One can use pip options, but this is probably not needed. + to test for conda). Can be used to install multiple packages together. + One can use pip options, but this is probably not needed. :param list packageList: a list of strings with name(s) of packages - Examples:: + Examples:: packageList=['gsl'] packageList=['wxpython','matplotlib','scipy'] @@ -2351,34 +1605,39 @@ def pipInstall(packageList): :returns: None if the the command ran normally, or an error message if it did not. ''' + # update conda package specs (pkg=1.2.3) to pip package specs (pkg==1.2.3) + # at present no other specifiers are used. + for i,val in enumerate(packageList): + if '=' in val and '==' not in val: + packageList[i] = packageList[i].replace('=','==') try: subprocess.check_call([sys.executable, '-m', 'pip', 'install']+packageList) except Exception as msg: return msg return None - + def condaEnvCreate(envname, packageList, force=False): '''Create a Python interpreter in a new conda environment. Use this - when there is a potential conflict between packages and it would + when there is a potential conflict between packages and it would be better to keep the packages separate (which is one of the reasons - conda supports environments). Note that conda should be run from the - base environment; this attempts to deal with issues if it is not. + conda supports environments). Note that conda should be run from the + base environment; this attempts to deal with issues if it is not. Currently, this is used only to install diffpy.PDFfit2. - :param str envname: the name of the environment to be created. + :param str envname: the name of the environment to be created. If the environment exists, it will be overwritten only if force is True. - :param list packageList: a list of conda install create command + :param list packageList: a list of conda install create command options, such as:: ['python=3.7', 'conda', 'gsl', 'diffpy.pdffit2', '-c', 'conda-forge', '-c', 'diffpy'] - :param bool force: if False (default) an error will be generated + :param bool force: if False (default) an error will be generated if an environment exists :returns: (status,msg) where status is True if an error occurs and - msg is a string with error information if status is True or the + msg is a string with error information if status is True or the location of the newly-created Python interpreter. ''' if not all([(i in os.environ) for i in ('CONDA_DEFAULT_ENV', @@ -2422,13 +1681,13 @@ def condaEnvCreate(envname, packageList, force=False): except Exception as msg: print("Error occurred, see below\n",msg) return True,'Error: '+str(msg) - + def addCondaPkg(): - '''Install the conda API into the current conda environment using the + '''Install the conda API into the current conda environment using the command line, so that the API can be used in the current Python interpreter Attempts to do this without a shell failed on the Mac because it seems that - the environment was inherited; seems to work w/o shell on Windows. + the environment was inherited; seems to work w/o shell on Windows. ''' if not all([(i in os.environ) for i in ('CONDA_DEFAULT_ENV','CONDA_EXE', 'CONDA_PREFIX', 'CONDA_PYTHON_EXE')]): @@ -2437,29 +1696,32 @@ def addCondaPkg(): currenv = os.environ['CONDA_DEFAULT_ENV'] if sys.platform == "win32": cmd = [os.environ['CONDA_EXE'],'install','conda','-n',currenv,'-y'] - p = subprocess.Popen(cmd, + with subprocess.Popen(cmd, #stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + encoding='UTF-8') as p: + out,err = p.communicate() else: script = 'source ' + os.path.join( os.path.dirname(os.environ['CONDA_PYTHON_EXE']), 'activate') + ' base; ' script += 'conda install conda -n '+currenv+' -y' - p = subprocess.Popen(script,shell=True,env={}, + with subprocess.Popen(script,shell=True,env={}, #stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out,err = MakeByte2str(p.communicate()) - #print('Output from adding conda:\n',out) - if err: - print('Note error/warning:') - print(err) + stderr=subprocess.PIPE, + encoding='UTF-8') as p: + out,err = p.communicate() + + if out is not None and GetConfigValue('debug'): print('Output from adding conda:\n',out) + if err and err is not None: + print('Note error/warning from running conda:\n',err) if currenv == "base": print('\nUnexpected action: adding conda to base environment???') #============================================================================== #============================================================================== # routines for reorg of GSAS-II directory layout def getIconFile(imgfile): - '''Looks in either the main GSAS-II install location (old) or subdirectory + '''Looks in either the main GSAS-II install location (old) or subdirectory icons (after reorg) for an icon :returns: the full path for the icon file @@ -2481,33 +1743,41 @@ def makeScriptShortcut(): The new shortcut is then tested. :returns: returns the name of the created file if successful. None - indicates an error. + indicates an error. ''' import datetime as dt + if not HowIsG2Installed().startswith('git'): + print('GSAS-II installed directly, shortcut likely not needed') + return None for p in sys.path: if 'site-packages' in p: break else: print('No site-packages directory found in Python path') return newfil = os.path.join(p,'G2script.py') - fp = open(newfil,'w') - fp.write(f'#Created in makeScriptShortcut from {__file__}') - fp.write(dt.datetime.strftime(dt.datetime.now(), - " at %Y-%m-%dT%H:%M\n")) + with open(newfil,'w') as fp: + fp.write(f'#Created in makeScriptShortcut from {__file__}') + fp.write(dt.datetime.strftime(dt.datetime.now(), + " at %Y-%m-%dT%H:%M\n")) - fp.write(f"""import sys,os + fp.write(f""" +import sys,os Path2GSASII=r'{path2GSAS2}' if os.path.exists(os.path.join(Path2GSASII,'GSASIIscriptable.py')): print('setting up GSASIIscriptable from',Path2GSASII) - if Path2GSASII not in sys.path: - sys.path.insert(0,Path2GSASII) - from GSASIIscriptable import * + if os.path.dirname(Path2GSASII) not in sys.path: + sys.path.insert(0,os.path.dirname(Path2GSASII)) + try: + from GSASII.GSASIIscriptable import * + except: + print('Import of GSASIIscriptable failed.\\nRerun "Install GSASIIscriptable shortcut" from inside GSAS-II?') + sys.exit() else: print('GSASIIscriptable not found in ',Path2GSASII) print('Rerun "Install GSASIIscriptable shortcut" from inside GSAS-II') sys.exit() -""") - fp.close() + """) + fp.close() print('Created file',newfil) try: import G2script @@ -2543,28 +1813,28 @@ def makeScriptShortcut(): def postURL(URL,postdict,getcookie=None,usecookie=None, timeout=None,retry=2,mode='get'): - '''Posts a set of values as from a web form using the "get" or "post" - protocols. + '''Posts a set of values as from a web form using the "get" or "post" + protocols. If access fails to an https site, the access is retried with http. - :param str URL: the URL to post; typically something + :param str URL: the URL to post; typically something like 'http://www.../dir/page?' :param dict postdict: contains keywords and values, such as {'centrosymmetry': '0', 'crystalsystem': '0', ...} :param dict getcookie: dict to save cookies created in call, or None - (default) if not needed. - :param dict usecookie: dict containing cookies to be used in call, + (default) if not needed. + :param dict usecookie: dict containing cookies to be used in call, or None (default) if not needed. - :param int timeout: specifies a timeout period for the get or post (default - is None, which means the timeout period is set by the server). The value - when specified is the time in seconds to wait before giving up on the + :param int timeout: specifies a timeout period for the get or post (default + is None, which means the timeout period is set by the server). The value + when specified is the time in seconds to wait before giving up on the request. :param int retry: the number of times to retry the request, if it times out. This is only used if timeout is specified. The default is 2. Note that if retry is left at the default value (2), The timeout is increased by 25% for the second try. :param str mode: either 'get' (default) or 'post'. Determines how - the request will be submitted. + the request will be submitted. :returns: a string with the response from the web server or None if access fails. ''' @@ -2580,7 +1850,7 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, reqopt = requests.get else: reqopt = requests.post - + repeat = True count = 0 while repeat: @@ -2602,7 +1872,7 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, else: print('request to {} failed. Reason={}'.format(URL,r.reason)) except requests.exceptions.ConnectionError as msg: - if 'time' in str(msg) and 'out' in str(msg): + if 'time' in str(msg) and 'out' in str(msg): print(f'server timeout accessing {URL}') if GetConfigValue('debug'): print('full error=',msg) if timeout is not None and count < retry: @@ -2643,9 +1913,125 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, else: return None +#=========================================================================== +# duplicated from GSASIIfiles to avoid using an import below +# perhaps the references to G2fil.openInNewTerm() should be +# changed to reference this here. +def openInNewTerm(project=None,g2script=None,pythonapp=sys.executable): + '''Open a new and independent GSAS-II session in separate terminal + or console window and as a separate process that will continue + even if the calling process exits. + Intended to work on all platforms. + + This could be used to run other scripts inside python other than GSAS-II + + :param str project: the name of an optional parameter to be + passed to the script (usually a .gpx file to be opened in + a new GSAS-II session) + :param str g2script: the script to be run. If None (default) + the G2.py file in the same directory as this file will + be used. + :param str pythonapp: the Python interpreter to be used. + Defaults to sys.executable which is usually what is wanted. + :param str terminal: a name for a preferred terminal emulator + ''' + #import subprocess + if g2script is None: + g2script = os.path.join(os.path.dirname(__file__),'G2.py') + + if sys.platform == "darwin": + if project: + script = f''' +set python to "{pythonapp}" +set appwithpath to "{g2script}" +set filename to "{project}" +set filename to the quoted form of the POSIX path of filename + +tell application "Terminal" + activate + do script python & " " & appwithpath & " " & filename & "; exit" +end tell +''' + else: + script = f''' +set python to "{pythonapp}" +set appwithpath to "{g2script}" + +tell application "Terminal" + activate + do script python & " " & appwithpath & " " & "; exit" +end tell +''' + subprocess.Popen(["osascript","-e",script]) + elif sys.platform.startswith("win"): + cmds = [pythonapp, g2script] + if project: cmds += [project] + subprocess.Popen(cmds,creationflags=subprocess.CREATE_NEW_CONSOLE) + else: + import shutil + script = '' + # try a bunch of common terminal emulators in Linux + # there does not appear to be a good way to way to specify this + # perhaps this should be a GSAS-II config option + for term in ("lxterminal", "gnome-terminal", 'konsole', "xterm", + "terminator", "terminology", "tilix"): + try: + found = shutil.which(term) + if not found: continue + except AttributeError: + print(f'shutil.which() failed (why?); assuming {term} present') + found = True + if term == "gnome-terminal": + #terminal = 'gnome-terminal -t "GSAS-II console" --' + cmds = [term,'--title','"GSAS-II console"','--'] + script = "echo; echo Press Enter to close window; read line" + break + elif term == "lxterminal": + #terminal = 'lxterminal -t "GSAS-II console" -e' + cmds = [term,'-t','"GSAS-II console"','-e'] + script = "echo;echo Press Enter to close window; read line" + break + elif term == "xterm": + #terminal = 'xterm -title "GSAS-II console" -hold -e' + cmds = [term,'-title','"GSAS-II console"','-hold','-e'] + script = "echo; echo This window can now be closed" + break + elif term == "terminator": + cmds = [term,'-T','"GSAS-II console"','-x'] + script = "echo;echo Press Enter to close window; read line" + break + elif term == "konsole": + cmds = [term,'-p','tabtitle="GSAS-II console"','--hold','-e'] + script = "echo; echo This window can now be closed" + break + elif term == "tilix": + cmds = [term,'-t','"GSAS-II console"','-e'] + script = "echo;echo Press Enter to close window; read line" + break + elif term == "terminology": + cmds = [term,'-T="GSAS-II console"','--hold','-e'] + script = "echo; echo This window can now be closed" + break + else: + print("No known terminal was found to use, Can't start {}") + return + + fil = '/tmp/GSAS2-launch.sh' + cmds += ['/bin/sh',fil] + fp = open(fil,'w') + if project: + fp.write(f"{pythonapp} {g2script} {project}\n") + else: + fp.write(f"{pythonapp} {g2script}\n") + fp.write(f"rm {fil}\n") + if script: + fp.write(f"{script}\n") + fp.close() + subprocess.Popen(cmds,start_new_session=True) + if __name__ == '__main__': - '''What follows is called to update (or downdate) GSAS-II in a - separate process. + '''What follows is called to update (or downdate) GSAS-II in a + separate process. ''' # check what type of update is being called for import git @@ -2655,8 +2041,7 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, regressversion = None help = False project = None - version = None - + for arg in sys.argv[1:]: if '--git-fetch' in arg: # pulls latest updates from server but does not apply them if preupdateType or updateType: @@ -2664,6 +2049,12 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, help = True break updateType = 'fetch' + elif '--github-tags' in arg: # gets latest tags from github + if preupdateType or updateType or gitUpdate: + print(f'previous option conflicts with {arg}') + help = True + break + updateType = 'tags' elif '--git-reset' in arg: # restores locally changed GSAS-II files to distributed versions also updates gitUpdate = True if preupdateType: @@ -2712,7 +2103,7 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, try: regressversion = g2repo.commit(gitversion).hexsha except git.BadName: - print(f'invalid version specified ({version}) for GitHub regression') + print(f'invalid version specified ({regressversion}) for GitHub regression') help = True break if updateType: @@ -2724,57 +2115,72 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, elif '--help' in arg: help = True break - elif os.path.exists(arg): # svn args parsed later; this is just checking + elif os.path.exists(arg): # this is just checking project = arg pass - else: # for old-style svn update - if arg.isdecimal() or not arg: - #version = arg - pass - else: - print(f'unknown arg {arg}') - help = True - if gitUpdate and version: - print('Conflicting arguments (git & svn opts combined?)') + else: + print(f'unknown arg {arg}') help = True if help or len(sys.argv) == 1: print('''Options when running GSASIIpath.py standalone -to update/regress repository from svn repository: - python GSASIIpath.py - where is an optional path reference to a .gpx file - and is a specific GSAS-II version to install - (default is latest) - to update/regress repository from git repository: python GSASIIpath.py option where option will be one or more of the following: --git-fetch downloads lastest changes from repo any other options will be ignored - --git-stash="message" saves local changes + --git-stash="message" saves local changes - --git-reset discards local changes + --git-reset discards local changes --git-update --git-regress=version + --github-tags saves most recent tag info from GitHub + and where is an optional path reference to a .gpx file Note: --git-reset and --git-stash cannot be used together. Likewise --git-update and --git-regress cannot be used together. - However either --git-reset or --git-stash can be used + However either --git-reset or --git-stash can be used with either --git-update or --git-regress. + --git-fetch cannot be used with any other options. + --github-tags cannot be used with any other options. ''') sys.exit() + if updateType == 'tags': + # get the most recent tag numbers from the GitHub site. Do this + # via GitHub when git access is not available. Use here + # allows this to be done in the background. + import requests + url='https://github.com/AdvancedPhotonSource/GSAS-II/tags' + releases = requests.get(url=url) + taglist = [tag.split('"')[0] for tag in releases.text.split('AdvancedPhotonSource/GSAS-II/releases/tag/')[1:]] + lastver = sorted([t for t in taglist if 'v' in t])[-1] + lastnum = sorted([t for t in taglist if 'v' not in t],key=int)[-1] + #print('tags=',lastver,lastnum) + # add tag info to config file + import configparser + cfgfile = os.path.expanduser(os.path.normpath('~/.GSASII/config.ini')) + cfg = configparser.ConfigParser() + cfg.read(cfgfile) + if 'version info' not in cfg: + cfg.add_section('version info') + cfg['version info'].update( + {'lastVersionTag':lastver,'lastVersionNumber':lastnum}) + with open(cfgfile, 'w') as configfile: + cfg.write(configfile) + sys.exit() if updateType == 'fetch': # download the latest updates from GitHub to the local repository - # in background while GSAS-II runs no updates are applied + # in background while GSAS-II runs no updates are applied. + # invoked by gitGetUpdate(mode='Background') logfile = os.path.join(os.path.expanduser('~'),'GSASII_bkgUpdate.log') mode = 'a' # don't let log file get too large (20K bytes) @@ -2786,39 +2192,56 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, except: print('background git update was unable to open log file') sys.exit() - fp.write('Starting background git update') - fp.write(dt.datetime.strftime(dt.datetime.now(), - " at %Y-%m-%dT%H:%M\n")) try: import git except: - fp.write('git import failed') + fp.write('Git background import failed') + fp.write(dt.datetime.strftime(dt.datetime.now(), + " at %Y-%m-%dT%H:%M\n")) fp.close() sys.exit() try: g2repo = openGitRepo(path2GSAS2) + prevremotecommits = None + if not g2repo.head.is_detached: + head = g2repo.head.ref + tracking = head.tracking_branch() + prevremotecommits = [i.hexsha for i in head.commit.iter_items(g2repo, f'{head.path}..{tracking.path}')] g2repo.remote().fetch() - fp.write('Updates fetched\n') + if not g2repo.head.is_detached: + head = g2repo.head.ref + tracking = head.tracking_branch() + remotecommits = [i.hexsha for i in head.commit.iter_items(g2repo, f'{head.path}..{tracking.path}')] + new = len(remotecommits)-len(prevremotecommits) + msg = f'{new} new update(s) found and downloaded, so {len(remotecommits)} total are available to install' + #fp.write(f'{msg}\n') + if new > 0: print(msg) except Exception as msg: + fp.write('Background git update message') + fp.write(dt.datetime.strftime(dt.datetime.now(), + " at %Y-%m-%dT%H:%M\n")) fp.write(f'Update failed with message {msg}\n') if g2repo.head.is_detached: + fp.write('Background git update message') + fp.write(dt.datetime.strftime(dt.datetime.now(), + " at %Y-%m-%dT%H:%M\n")) fp.write('Status: reverted to an old install\n') - else: - try: - rc,lc,_ = gitCheckForUpdates(False,g2repo) - if len(rc) == 0: - fp.write('Status: no unapplied commits\n') - else: - fp.write(f'Status: unapplied commits now {len(rc)}\n') - except Exception as msg: - fp.write(f'\ngitCheckForUpdates failed with message {msg}\n') - fp.write('update done at') - fp.write(dt.datetime.strftime(dt.datetime.now(), - " at %Y-%m-%dT%H:%M\n\n")) + # else: + # try: + # rc,lc,_ = gitCheckForUpdates(False,g2repo) + # if len(rc) == 0: + # fp.write('Status: no unapplied commits\n') + # else: + # fp.write(f'Status: unapplied commits now {len(rc)}\n') + # except Exception as msg: + # fp.write(f'\ngitCheckForUpdates failed with message {msg}\n') + # fp.write('update done at') + # fp.write(dt.datetime.strftime(dt.datetime.now(), + # " at %Y-%m-%dT%H:%M\n\n")) fp.close() sys.exit() - + if gitUpdate: import time time.sleep(1) # delay to give the main process a chance to exit @@ -2835,14 +2258,14 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, print(f'Update failed with message {msg}\n') sys.exit() print('git repo opened') - + if preupdateType == 'reset': # --git-reset (preupdateType = 'reset') print('Restoring locally-updated GSAS-II files to original status') - openGitRepo(path2GSAS2).git.reset('--hard','origin/master') + openGitRepo(path2GSAS2).git.reset('--hard','origin/main') try: - if g2repo.active_branch.name != 'master': - g2repo.git.switch('master') + if g2repo.active_branch.name != 'main': + g2repo.git.switch('main') except TypeError: # fails on detached head pass @@ -2853,7 +2276,7 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, g2repo.git.stash(f'-m"{message}"') else: g2repo.git.stash() - + # Update to the latest GSAS-II version. This assumes that a fetch has # been done prior, or this will only update to the last time that # it was done. @@ -2864,7 +2287,7 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, sys.exit() print('Updating to latest GSAS-II version') if g2repo.head.is_detached: - g2repo.git.switch('master') + g2repo.git.switch('main') g2repo.git.merge('--ff-only') print('git: updated to latest version') @@ -2879,49 +2302,22 @@ def postURL(URL,postdict,getcookie=None,usecookie=None, g2repo.git.checkout(regressversion) if gitUpdate: + # path hack for restart, when needed + #import importlib.util + #try: + # importlib.util.find_spec('GSASII.GSASIIGUI') + #except ModuleNotFoundError: + # print('Adding GSAS-II location to Python system path') + # sys.path.insert(0,os.path.dirname(os.path.dirname(__file__))) + # now restart GSAS-II with the new version - # G2scrpt = os.path.join(path2GSAS2,'GSASII.py') + # G2scrpt = os.path.join(path2GSAS2,'G2.py') if project: print(f"Restart GSAS-II with project file {project!r}") - # subprocess.Popen([sys.executable,G2scrpt,project]) - else: - print("Restart GSAS-II without a project file ") - # subprocess.Popen([sys.executable,G2scrpt]) - import GSASIIfiles - GSASIIfiles.openInNewTerm(project) - print ('exiting update process') - sys.exit() - - else: - # this is the old svn update process - LoadConfig() - import time - time.sleep(1) # delay to give the main process a chance to exit - # perform an update and restart GSAS-II - try: - project,version = sys.argv[1:3] - except ValueError: - project = None - version = 'trunk' - loc = os.path.dirname(__file__) - if version == 'trunk': - svnSwitch2branch('') - elif '/' in version: - svnSwitch2branch(version) - elif version: - print("Regress to version "+str(version)) - svnUpdateDir(loc,version=version) - else: - print("Update to current version") - svnUpdateDir(loc) - ex = sys.executable - if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable - if os.path.exists(ex+'w'): ex += 'w' - if project: - print("Restart GSAS-II with project file "+str(project)) - subprocess.Popen([ex,os.path.join(loc,'GSASII.py'),project]) else: print("Restart GSAS-II without a project file ") - subprocess.Popen([ex,os.path.join(loc,'GSASII.py')]) + #from . import GSASIIfiles + #GSASIIfiles.openInNewTerm(project) + openInNewTerm(project) print ('exiting update process') sys.exit() diff --git a/GSASII/GSASIIphsGUI.py b/GSASII/GSASIIphsGUI.py index 0db3de14a..6e1ffdd85 100644 --- a/GSASII/GSASIIphsGUI.py +++ b/GSASII/GSASIIphsGUI.py @@ -4,13 +4,13 @@ Main routine here is :func:`UpdatePhaseData`, which displays the phase information (called from :func:`GSASIIdataGUI:SelectDataTreeItem`). -Other top-level routines are: +Other top-level routines are: :func:`GetSpGrpfromUser` (called locally only); -:func:`FindBondsDraw` and :func:`FindBondsDrawCell` (called locally and in GSASIIplot); +:func:`FindBondsDraw` and :func:`FindBondsDrawCell` (called locally and in GSASIIplot); :func:`SetPhaseWindow` (called locally and in GSASIIddataGUI and GSASIIrestrGUI, multiple locations) to control scrolling. -Routines for Phase dataframes follow. +Routines for Phase dataframes follow. ''' from __future__ import division, print_function import platform @@ -25,36 +25,38 @@ import sys import random as ran import subprocess as subp -import distutils.file_util as disfile +#import platform +import shutil + +import numpy as np +import numpy.linalg as nl +import numpy.ma as ma import scipy.optimize as so -import GSASIIpath -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIElem as G2elem -import GSASIIElemGUI as G2elemGUI -import GSASIIddataGUI as G2ddG -import GSASIIplot as G2plt -import GSASIIpwdplot as G2pwpl +from . import GSASIIpath +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIElem as G2elem +from . import GSASIIElemGUI as G2elemGUI +from . import GSASIIddataGUI as G2ddG +from . import GSASIIplot as G2plt +from . import GSASIIpwdplot as G2pwpl # if GSASIIpath.GetConfigValue('debug'): # print('Debug reloading',G2plt) # import imp # imp.reload(G2plt) -import GSASIIdataGUI as G2gd -import GSASIImiscGUI as G2IO -import GSASIIstrMain as G2stMn -import GSASIIstrIO as G2stIO -import GSASIImath as G2mth -import GSASIIpwd as G2pwd -import GSASIIobj as G2obj -import GSASIIctrlGUI as G2G -import GSASIIfiles as G2fil -import GSASIIconstrGUI as G2cnstG -import numpy as np -import numpy.linalg as nl -import numpy.ma as ma -import atmdata -import ISODISTORT as ISO -import platform +from . import GSASIIdataGUI as G2gd +from . import GSASIImiscGUI as G2IO +from . import GSASIIstrMain as G2stMn +from . import GSASIIstrIO as G2stIO +from . import GSASIImath as G2mth +from . import GSASIIpwd as G2pwd +from . import GSASIIobj as G2obj +from . import GSASIIctrlGUI as G2G +from . import GSASIIfiles as G2fil +from . import GSASIIconstrGUI as G2cnstG +from . import atmdata +from . import ISODISTORT as ISO +from . import SUBGROUPS try: wx.NewIdRef @@ -89,7 +91,7 @@ prevSpnId = None GkDelta = chr(0x0394) -Angstr = chr(0x00c5) +Angstr = chr(0x00c5) RMCmisc = {} ranDrwDict = {} @@ -195,12 +197,12 @@ def OnCancel(self,event): #============================================================================== class SphereEnclosure(wx.Dialog): ''' Add atoms within sphere of enclosure to drawing - + :param wx.Frame parent: reference to parent frame (or None) :param general: general data (includes drawing data) :param atoms: drawing atoms data :param indx: list of selected atoms (may be empty) - + ''' def __init__(self,parent,general,drawing,indx): wx.Dialog.__init__(self,parent,wx.ID_ANY,'Supply sphere info', @@ -214,14 +216,14 @@ def __init__(self,parent,general,drawing,indx): self.atomTypes = [[item,False] for item in self.General['AtomTypes']] self.CenterOnParent() self.Draw() - + def Draw(self): - + def OnAtomType(event): Obj = event.GetEventObject() Id = Ind[Obj.GetId()] self.atomTypes[Id][1] = Obj.GetValue() - + self.panel.Destroy() self.panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -262,7 +264,7 @@ def OnAtomType(event): Ind[atm.GetId()] = i atSizer.Add(atm,0,WACV) mainSizer.Add(atSizer,0) - + OkBtn = wx.Button(self.panel,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, self.OnOk) cancelBtn = wx.Button(self.panel,-1,"Cancel") @@ -273,12 +275,12 @@ def OnAtomType(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetSelection(self): used = [] for atm in self.atomTypes: @@ -295,18 +297,18 @@ def OnCancel(self,event): parent = self.GetParent() parent.Raise() self.EndModal(wx.ID_CANCEL) - + #============================================================================== class TransformDialog(wx.Dialog): ''' Phase transformation X' = M*(X-U)+V - + :param wx.Frame parent: reference to parent frame (or None) :param phase: parent phase data - - #NB: commonNames & commonTrans defined in GSASIIdataGUI = G2gd + + #NB: commonNames & commonTrans defined in GSASIIdataGUI = G2gd ''' def __init__(self,parent,phase,Trans=np.eye(3),Uvec=np.zeros(3),Vvec=np.zeros(3),ifMag=False,BNSlatt=''): - wx.Dialog.__init__(self,parent,wx.ID_ANY,'Setup phase transformation', + wx.Dialog.__init__(self,parent,wx.ID_ANY,'Setup phase transformation', pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.Phase = copy.deepcopy(phase) #will be a new phase! @@ -335,7 +337,7 @@ def __init__(self,parent,phase,Trans=np.eye(3),Uvec=np.zeros(3),Vvec=np.zeros(3) self.CenterOnParent() def Draw(self): - + def OnCommon(event): Obj = event.GetEventObject() self.Common = Obj.GetValue() @@ -361,7 +363,7 @@ def OnCommon(event): self.Phase['General']['SGData'] = SGData SGTxt.SetLabel(self.newSpGrp) OnTest(event) - + def OnSpaceGroup(event): event.Skip() SpcGp = GetSpGrpfromUser(self.panel,self.newSpGrp) @@ -396,7 +398,7 @@ def OnSpaceGroup(event): self.SGData['SpnFlp'] = Nops*[1,] del self.oldSGdata['MAXMAGN'] wx.CallAfter(self.Draw) - + def OnShowOps(event): text,table = G2spc.SGPrint(self.SGData,AddInv=True) if self.ifMag: @@ -418,16 +420,16 @@ def OnTest(event): else: self.newCell = G2lat.TransformCell(self.oldCell[:6],self.Trans) wx.CallAfter(self.Draw) - + def OnMag(event): self.ifMag = True self.BNSlatt = self.SGData['SGLatt'] G2spc.SetMagnetic(self.SGData) wx.CallAfter(self.Draw) - + def OnConstr(event): self.ifConstr = constr.GetValue() - + def OnBNSlatt(event): Obj = event.GetEventObject() self.BNSlatt = Obj.GetValue() @@ -437,15 +439,15 @@ def OnBNSlatt(event): self.SGData['BNSlattsym'] = [self.BNSlatt,BNSsym[self.BNSlatt]] self.SGData['SGSpin'] = [1,]*len(self.SGData['SGSpin']) wx.CallAfter(self.Draw) - + def OnMtrans(event): Obj = event.GetEventObject() self.Mtrans = Obj.GetValue() - + def OnSpinOp(event): Obj = event.GetEventObject() isym = Indx[Obj.GetId()]+1 - spCode = {'red':-1,'black':1} + spCode = {'red':-1,'black':1} self.SGData['SGSpin'][isym] = spCode[Obj.GetValue()] G2spc.CheckSpin(isym,self.SGData) G2spc.SetMagnetic(self.SGData) @@ -461,7 +463,7 @@ def OnSpinOp(event): else: mag = wx.Button(self.panel,label='Make new phase magnetic?') mag.Bind(wx.EVT_BUTTON,OnMag) - mainSizer.Add(mag,0) + mainSizer.Add(mag,0) MatSizer = wx.BoxSizer(wx.HORIZONTAL) transSizer = wx.BoxSizer(wx.VERTICAL) transSizer.Add((5,5),0) @@ -509,7 +511,7 @@ def OnSpinOp(event): sgSizer.Add(wx.StaticText(self.panel,label=' Target space group: '),0,WACV) SGTxt = wx.Button(self.panel,wx.ID_ANY,self.newSpGrp,size=(100,-1)) SGTxt.Bind(wx.EVT_BUTTON,OnSpaceGroup) - sgSizer.Add(SGTxt,0,WACV) + sgSizer.Add(SGTxt,0,WACV) showOps = wx.Button(self.panel,label=' Show operators?') showOps.Bind(wx.EVT_BUTTON,OnShowOps) sgSizer.Add(showOps,0,WACV) @@ -531,13 +533,13 @@ def OnSpinOp(event): BNS.SetValue(self.BNSlatt) BNS.Bind(wx.EVT_COMBOBOX,OnBNSlatt) BNSizer.Add(BNS,0,WACV) - + spinColor = ['black','red'] spCode = {-1:'red',1:'black'} for isym,sym in enumerate(GenSym[1:]): - BNSizer.Add(wx.StaticText(self.panel,label=' %s: '%(sym.strip())),0,WACV) + BNSizer.Add(wx.StaticText(self.panel,label=' %s: '%(sym.strip())),0,WACV) spinOp = wx.ComboBox(self.panel,value=spCode[self.SGData['SGSpin'][isym+1]],choices=spinColor, - style=wx.CB_READONLY|wx.CB_DROPDOWN) + style=wx.CB_READONLY|wx.CB_DROPDOWN) Indx[spinOp.GetId()] = isym spinOp.Bind(wx.EVT_COMBOBOX,OnSpinOp) BNSizer.Add(spinOp,0,WACV) @@ -566,12 +568,12 @@ def OnSpinOp(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def TestMat(self): VC = nl.det(self.Trans) if VC < 0.: @@ -585,7 +587,7 @@ def TestMat(self): style=wx.ICON_ERROR) return False return True - + def GetSelection(self): self.Phase['General']['SGData'] = self.SGData if self.ifMag: @@ -595,10 +597,10 @@ def GetSelection(self): if not self.TestMat(): return None if self.Mtrans: - self.Phase['General']['Cell'][1:] = G2lat.TransformCell(self.oldCell[:6],self.Trans.T) + self.Phase['General']['Cell'][1:] = G2lat.TransformCell(self.oldCell[:6],self.Trans.T) return self.Phase,self.Trans.T,self.Uvec,self.Vvec,self.ifMag,self.ifConstr,self.Common else: - self.Phase['General']['Cell'][1:] = G2lat.TransformCell(self.oldCell[:6],self.Trans) + self.Phase['General']['Cell'][1:] = G2lat.TransformCell(self.oldCell[:6],self.Trans) return self.Phase,self.Trans,self.Uvec,self.Vvec,self.ifMag,self.ifConstr,self.Common def OnOk(self,event): @@ -610,7 +612,7 @@ def OnCancel(self,event): parent = self.GetParent() parent.Raise() self.EndModal(wx.ID_CANCEL) - + #============================================================================== class UseMagAtomDialog(wx.Dialog): '''Get user selected magnetic atoms after cell transformation @@ -619,7 +621,7 @@ def __init__(self,parent,Name,Atoms,atCodes,atMxyz,ifMag=True,ifOK=False,ifDelet title = 'Subgroup atom list' if ifMag: title = 'Magnetic atom selection' - wx.Dialog.__init__(self,parent,wx.ID_ANY,title, + wx.Dialog.__init__(self,parent,wx.ID_ANY,title, pos=wx.DefaultPosition,size=(450,275), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) self.panel = wxscroll.ScrolledPanel(self) #just a dummy - gets destroyed in Draw! @@ -633,15 +635,15 @@ def __init__(self,parent,Name,Atoms,atCodes,atMxyz,ifMag=True,ifOK=False,ifDelet self.ifDelete = ifDelete self.Use = len(self.Atoms)*[True,] self.Draw() - + def Draw(self): - + def OnUseChk(event): Obj = event.GetEventObject() iuse = Indx[Obj.GetId()] self.Use[iuse] = not self.Use[iuse] Obj.SetValue(self.Use[iuse]) - + self.panel.Destroy() self.panel = wxscroll.ScrolledPanel(self,style = wx.DEFAULT_DIALOG_STYLE) Indx = {} @@ -649,7 +651,7 @@ def OnUseChk(event): Xstr = ['X','Y','Z'] mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(wx.StaticText(self.panel,label='For: %s'%self.Name),0) - + if self.ifMag: mainSizer.Add(wx.StaticText(self.panel,label=' Name, x, y, z, allowed moments, mag. site sym:'),0) else: @@ -674,12 +676,12 @@ def OnUseChk(event): text = ' %5s %10.5f %10.5f %10.5f (%s,%s,%s) %s '%(atom[0],atom[3],atom[4],atom[5],mstr[0],mstr[1],mstr[2],mxyz[0]) atmSizer.Add(wx.StaticText(self.panel,label=text),0,WACV) mainSizer.Add(atmSizer) - + btnSizer = wx.BoxSizer(wx.HORIZONTAL) if self.ifOK: OKBtn = wx.Button(self.panel,-1,"OK") OKBtn.Bind(wx.EVT_BUTTON, self.OnNo) - btnSizer.Add(OKBtn) + btnSizer.Add(OKBtn) else: YesBtn = wx.Button(self.panel,-1,"Yes") YesBtn.Bind(wx.EVT_BUTTON, self.OnYes) @@ -695,7 +697,7 @@ def OnUseChk(event): btnSizer.Add((20,20),1) btnSizer.Add(DeleteBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) size = np.array(self.GetSize()) @@ -703,7 +705,7 @@ def OnUseChk(event): self.panel.SetAutoLayout(1) size = [size[0]-5,size[1]-20] #this fiddling is needed for older wx! self.panel.SetSize(size) - + def GetSelection(self): useAtoms = [] useatCodes = [] @@ -722,22 +724,22 @@ def OnNo(self,event): parent = self.GetParent() parent.Raise() self.EndModal(wx.ID_NO) - + def OnDelete(self,event): parent = self.GetParent() parent.Raise() self.EndModal(wx.ID_DELETE) - - + + #============================================================================== class RotationDialog(wx.Dialog): ''' Get Rotate & translate matrix & vector - currently not used needs rethinking - possible use to rotate a group of atoms about some vector/origin + translation - + ''' def __init__(self,parent): - wx.Dialog.__init__(self,parent,wx.ID_ANY,'Atom group rotation/translation', + wx.Dialog.__init__(self,parent,wx.ID_ANY,'Atom group rotation/translation', pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.Trans = np.eye(3) @@ -751,7 +753,7 @@ def Draw(self): def OnExpand(event): self.Expand = expand.GetValue() - + def OnRotAngle(event): event.Skip() self.rotAngle = float(rotangle.GetValue()) @@ -759,7 +761,7 @@ def OnRotAngle(event): Q = G2mth.AVdeg2Q(self.rotAngle,self.rotVec) self.Trans = G2mth.Q2Mat(Q) self.Draw() - + def OnRotVec(event): event.Skip() vals = rotvec.GetValue() @@ -769,7 +771,7 @@ def OnRotVec(event): Q = G2mth.AVdeg2Q(self.rotAngle,self.rotVec) self.Trans = G2mth.Q2Mat(Q) self.Draw() - + self.panel.Destroy() self.panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -812,9 +814,9 @@ def OnRotVec(event): style=wx.CB_READONLY|wx.CB_DROPDOWN) expand.Bind(wx.EVT_COMBOBOX,OnExpand) expandBox.Add(expand,0,WACV) - expandBox.Add(wx.StaticText(self.panel,label=' and find unique atoms '),0,WACV) + expandBox.Add(wx.StaticText(self.panel,label=' and find unique atoms '),0,WACV) mainSizer.Add(expandBox) - + OkBtn = wx.Button(self.panel,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, self.OnOk) cancelBtn = wx.Button(self.panel,-1,"Cancel") @@ -825,7 +827,7 @@ def OnRotVec(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() @@ -842,14 +844,14 @@ def OnOk(self,event): def OnCancel(self,event): parent = self.GetParent() parent.Raise() - self.EndModal(wx.ID_CANCEL) - + self.EndModal(wx.ID_CANCEL) + #============================================================================== class DIFFaXcontrols(wx.Dialog): ''' Solicit items needed to prepare DIFFaX control.dif file ''' def __init__(self,parent,ctrls,parms=None): - wx.Dialog.__init__(self,parent,wx.ID_ANY,'DIFFaX controls', + wx.Dialog.__init__(self,parent,wx.ID_ANY,'DIFFaX controls', pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw! self.ctrls = ctrls @@ -866,25 +868,25 @@ def __init__(self,parent,ctrls,parms=None): self.parmStep = 2 self.Inst = 'Gaussian' self.Draw() - + def Draw(self): - + def OnCalcType(event): self.calcType = calcType.GetValue() wx.CallAfter(self.Draw) - + def OnPlane(event): self.plane = plane.GetValue() - + def OnMaxL(event): self.lmax = lmax.GetValue() - + def OnParmSel(event): self.Parm = parmsel.GetValue() - + def OnNumStep(event): self.parmStep = int(numStep.GetValue()) - + def OnParmRange(event): event.Skip() vals = parmrange.GetValue().split() @@ -894,10 +896,10 @@ def OnParmRange(event): vals = self.parmRange parmrange.SetValue('%.3f %.3f'%(vals[0],vals[1])) self.parmRange = vals - + def OnInstSel(event): self.Inst = instsel.GetValue() - + self.panel.Destroy() self.panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -934,7 +936,7 @@ def OnInstSel(event): style=wx.CB_READONLY|wx.CB_DROPDOWN) numStep.Bind(wx.EVT_COMBOBOX,OnNumStep) parmRange.Add(numStep,0,WACV) - mainSizer.Add(parmRange) + mainSizer.Add(parmRange) if 'selected' in self.calcType: planeSizer = wx.BoxSizer(wx.HORIZONTAL) planeSizer.Add(wx.StaticText(self.panel,label=' Select plane: '),0,WACV) @@ -946,7 +948,7 @@ def OnInstSel(event): lmax = wx.ComboBox(self.panel,value=self.lmax,choices=self.lmaxChoice, style=wx.CB_READONLY|wx.CB_DROPDOWN) lmax.Bind(wx.EVT_COMBOBOX,OnMaxL) - planeSizer.Add(lmax,0,WACV) + planeSizer.Add(lmax,0,WACV) mainSizer.Add(planeSizer) else: instChoice = ['None','Mean Gaussian','Gaussian',] @@ -967,12 +969,12 @@ def OnInstSel(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetSelection(self): if 'powder' in self.calcType: return 'PWDR',self.Inst,self.Parm,self.parmRange,self.parmStep @@ -991,23 +993,23 @@ def OnCancel(self,event): #============================================================================== class AddHatomDialog(wx.Dialog): - '''H atom addition dialog. After :meth:`ShowModal` returns, the results + '''H atom addition dialog. After :meth:`ShowModal` returns, the results are found in dict :attr:`self.data`, which is accessed using :meth:`GetData`. - + :param wx.Frame parent: reference to parent frame (or None) :param dict Neigh: a dict of atom names with list of atom name, dist pairs for neighboring atoms :param dict phase: a dict containing the phase as defined by - :ref:`Phase Tree Item ` + :ref:`Phase Tree Item ` ''' def __init__(self,parent,Neigh,phase): - wx.Dialog.__init__(self,parent,wx.ID_ANY,'H atom add', + wx.Dialog.__init__(self,parent,wx.ID_ANY,'H atom add', pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = wxscroll.ScrolledPanel(self) #just a dummy - gets destroyed in Draw! self.Neigh = Neigh self.phase = phase self.Hatoms = [] self.Draw(self.Neigh,self.phase) - + def Draw(self,Neigh,phase): '''Creates the contents of the dialog. Normally called by :meth:`__init__`. @@ -1019,12 +1021,12 @@ def OnHSelect(event): obj.SetValue(False) Obj.SetValue(True) self.Neigh[item][2] = i - + def OnBond(event): Obj = event.GetEventObject() inei,ibond = Indx[Obj.GetId()] self.Neigh[inei][1][0][ibond][2] = Obj.GetValue() - + self.panel.Destroy() self.panel = wxscroll.ScrolledPanel(self,style = wx.DEFAULT_DIALOG_STYLE) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -1057,8 +1059,8 @@ def OnBond(event): Bond = wx.CheckBox(self.panel,-1,label=': %s, %.3f'%(bond[0],bond[1])) Bond.SetValue(bond[2]) Indx[Bond.GetId()] = [inei,ib] - Bond.Bind(wx.EVT_CHECKBOX,OnBond) - lineSizer.Add(Bond,0,WACV) + Bond.Bind(wx.EVT_CHECKBOX,OnBond) + lineSizer.Add(Bond,0,WACV) dataSizer.Add(lineSizer,0,WACV|wx.RIGHT,10) mainSizer.Add(dataSizer,0,wx.LEFT,5) @@ -1079,7 +1081,7 @@ def OnBond(event): self.panel.SetAutoLayout(1) size = [size[0]-5,size[1]-20] #this fiddling is needed for older wx! self.panel.SetSize(size) - + def GetData(self): 'Returns the values from the dialog' for neigh in self.Neigh: @@ -1088,12 +1090,12 @@ def GetData(self): neigh[1][1][1][ibond] = 0 #deselected bond neigh[1][1][1] = [a for a in neigh[1][1][1] if a] return self.Neigh #has #Hs to add for each entry - + def OnOk(self,event): 'Called when the OK button is pressed' parent = self.GetParent() parent.Raise() - self.EndModal(wx.ID_OK) + self.EndModal(wx.ID_OK) def OnCancel(self,event): parent = self.GetParent() @@ -1121,7 +1123,7 @@ def OnCancel(self,event): # if generalData['Type'] in ['nuclear','faulted',]: # if oldatom: # opr = oldatom[5] -# if atom[9] == 'A': +# if atom[9] == 'A': # X,U = G2spc.ApplyStringOps(opr,SGData,atom[3:6],atom[11:17]) # atomInfo = [atom[:2]+list(X)+oldatom[5:9]+atom[9:11]+list(U)+oldatom[17:]][0] # else: @@ -1140,7 +1142,7 @@ def OnCancel(self,event): # Mom = G2spc.ApplyStringOpsMom(opr,SGData,SSGData,mom) # else: # Mom = G2spc.ApplyStringOpsMom(opr,SGData,None,mom) -# if atom[12] == 'A': +# if atom[12] == 'A': # X,U = G2spc.ApplyStringOps(opr,SGData,atom[3:6],atom[14:20]) # atomInfo = [atom[:2]+list(X)+list(Mom)+oldatom[8:12]+atom[12:14]+list(U)+oldatom[20:]][0] # else: @@ -1176,13 +1178,13 @@ def getPawleydRange(G2frame,data): Inst = Histograms[item]['Instrument Parameters'][0] if 'T' in Inst['Type'][1]: dmin,dmax = [G2lat.Pos2dsp(Inst,t) for t in Histograms[item]['Limits'][1]] - else: + else: dmax,dmin = [G2lat.Pos2dsp(Inst,t) for t in Histograms[item]['Limits'][1]] - if dmaxAll is None: + if dmaxAll is None: dmaxAll = dmax else: dmaxAll = max(dmaxAll,dmax) - if dminAll is None: + if dminAll is None: dminAll = dmin else: dminAll = min(dminAll,dmin) @@ -1192,18 +1194,18 @@ def getPawleydRange(G2frame,data): if dmaxAll is None: dmaxAll = 100. if dminAll is None: dminAll = 0.25 return dminAll,dmaxAll,nhist,lbl - + def getAtomSelections(AtmTbl,cn=0,action='action',includeView=False,ask=True): '''get selected atoms from table or ask user if none are selected - + :param list AtmTbl: atom or draw atom table :param int cn: atom name position :param str action: description for prompt, when needed - :param bool includeView: if True, the viewpoint is included + :param bool includeView: if True, the viewpoint is included as an option in the selection dialog - :returns: indx (list) selected atoms from indices in table. - If includeView is True, indx can contain index n (where there - are n atoms in table). This is indicates the viewpoint. + :returns: indx (list) selected atoms from indices in table. + If includeView is True, indx can contain index n (where there + are n atoms in table). This is indicates the viewpoint. ''' indx = AtmTbl.GetSelectedRows() indx += [row for row,col in AtmTbl.GetSelectedCells()] @@ -1230,17 +1232,17 @@ def getAtomSelections(AtmTbl,cn=0,action='action',includeView=False,ask=True): def SetPhaseWindow(phasePage,mainSizer=None,Scroll=0): '''Finish off processing for all items going into a phase notebook page - This connects the sizer to the Panel/ScrolledWindow that is assigned - as the notebook's page for a tab. + This connects the sizer to the Panel/ScrolledWindow that is assigned + as the notebook's page for a tab. - Note that a wx.ScrolledWindow is used for most tab pages, with the + Note that a wx.ScrolledWindow is used for most tab pages, with the exception of Atoms, drawAtoms, G2frame.MapPeaks and G2frame.PawleyRefl, where a wx.Panel is used with a single Grid inside. This allows the grid - to handle scrolling. + to handle scrolling. - When a wx.ScrolledWindows is used, scrolling is turned on here. The - optional Scroll parameter is used to restore the scroll position to - the previous position so that the window can be redrawn without + When a wx.ScrolledWindows is used, scrolling is turned on here. The + optional Scroll parameter is used to restore the scroll position to + the previous position so that the window can be redrawn without disruption. ''' if mainSizer is not None: @@ -1250,17 +1252,17 @@ def SetPhaseWindow(phasePage,mainSizer=None,Scroll=0): phasePage.SetScrollRate(10,10) phasePage.SendSizeEvent() phasePage.Scroll(0,Scroll) - + def GetSpGrpfromUser(parent,SpGrp): helptext = '''\t\t\tGSAS-II space group information - + Space groups are entered here as given in Volume I or Volume A of the International Tables using the short Hermann-Mauguin symbol,except that spaces are placed between axial fields (e.g. "P 4/m m m", "F D 3 M" or "p -3 1 m"). NB: the cubic "bar" in "F d -3 m" is unnecessary, and upper/lower case is not required. Where a centrosymmetric tetragonal or cubic space group has alternate origin settings, -Origin choice 2 (with the center of symmetry at the origin, which gives an -x,-y,-z +Origin choice 2 (with the center of symmetry at the origin, which gives an -x,-y,-z symmetry operator) is always used. Refer to the relevant pages in IT I or A to find the offset in atom positions between the two choices. @@ -1302,8 +1304,8 @@ def GetSpGrpfromUser(parent,SpGrp): finally: dlg.Destroy() return SpcGp - - + + def FindBondsDraw(data): '''Generally used routine where cell is from data ''' @@ -1332,8 +1334,8 @@ def getAtomRadii(data): def FindCoordinationByLabel(data): '''Map out molecular connectivity by determining the atoms bonded - to each atom, by label. The atoms bonded to each atom in the asymmetric - unit is determined and returned in a dict. Works best + to each atom, by label. The atoms bonded to each atom in the asymmetric + unit is determined and returned in a dict. Works best ''' generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] @@ -1365,13 +1367,13 @@ def FindCoordinationByLabel(data): if error: print('Warning, duplicated atom labels:',error) return neighborArray - + def FindCoordination(ind,data,neighborArray,coordsArray,cmx=0,targets=None): '''Find atoms coordinating atom ind, speed-up version. This only searches to atoms already added to the Draw Array, though we might want - to search to all atoms in the asymmetric unity (which would mean searching against + to search to all atoms in the asymmetric unity (which would mean searching against atomsAll, but would also require a reformat of atom entry to match difference in - format between atoms and drawatoms. + format between atoms and drawatoms. ''' generalData = data['General'] Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) @@ -1415,7 +1417,7 @@ def FindCoordination(ind,data,neighborArray,coordsArray,cmx=0,targets=None): mom = np.array(atom[cmx:cmx+3]) if SGData['SGGray']: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M) - else: + else: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M)*SGData['SpnFlp'][opNum-1] atom[cs-1] = str(item[2])+'+' atom[cs+5:cs+5+6] = item[1] @@ -1428,7 +1430,7 @@ def FindCoordination(ind,data,neighborArray,coordsArray,cmx=0,targets=None): newAtomList.append(atom) return newAtomList -def FindBondsDrawCell(data,cell): +def FindBondsDrawCell(data,cell): '''uses numpy & masks - very fast even for proteins! allows different cell as input from seq. refinements ''' @@ -1442,7 +1444,7 @@ def FindBondsDrawCell(data,cell): indH = atomTypes.index('H') radii[indH] = 0.5 except: - pass + pass for atom in atomData: atom[-2] = [] #clear out old bonds/polyhedra atom[-1] = [] @@ -1505,12 +1507,12 @@ def FindBondsDrawCell(data,cell): norm /= np.sqrt(np.sum(norm**2)) Faces.append([face,norm]) atomData[i][-1] = Faces - + def VoidMap(data,aMax=1,bMax=1,cMax=1,gridspacing=.25,probeRadius=.5, aMin=0,bMin=0,cMin=0): '''Compute points where there are no atoms within probeRadius A. - All atoms in the Atoms list are considered, provided their - occupancy is non-zero. + All atoms in the Atoms list are considered, provided their + occupancy is non-zero. :param dict data: Phase data array :param float aMax: Maximum along the *a* direction (fractional units). @@ -1531,7 +1533,7 @@ def VoidMap(data,aMax=1,bMax=1,cMax=1,gridspacing=.25,probeRadius=.5, ''' VDWdict = dict(zip(data['General']['AtomTypes'],data['General']['vdWRadii'])) - cell = data['General']['Cell'][1:7] + cell = data['General']['Cell'][1:7] Amat,Bmat = G2lat.cell2AB(cell) # orthogonalization matrix SGData = data['General']['SGData'] surroundingCells = G2lat.CellBlock(1) @@ -1546,7 +1548,7 @@ def VoidMap(data,aMax=1,bMax=1,cMax=1,gridspacing=.25,probeRadius=.5, cx,ct,cs,cia = data['General']['AtomPtrs'] nind = len(data['Atoms']) - pgbar = wx.ProgressDialog('Fill unit cell for %d atoms'%nind,'Atoms done=',nind+1, + pgbar = wx.ProgressDialog('Fill unit cell for %d atoms'%nind,'Atoms done=',nind+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -1567,7 +1569,7 @@ def VoidMap(data,aMax=1,bMax=1,cMax=1,gridspacing=.25,probeRadius=.5, for item in result: for scell in surroundingCells: XYZ = item[0] + scell - if np.any((XYZ < cellMin, XYZ > cellMax)): continue + if np.any((XYZ < cellMin, XYZ > cellMax)): continue lgclArray = np.logical_and(lgclArray,np.sqrt(np.sum(np.inner(Amat,coordGrd-XYZ)**2,axis=0))>radius) GoOn = pgbar.Update(i,newmsg='Atoms done=%d'%(i)) if not GoOn[0]: @@ -1575,7 +1577,7 @@ def VoidMap(data,aMax=1,bMax=1,cMax=1,gridspacing=.25,probeRadius=.5, pgbar.Destroy() print('found ',len(coordGrd[lgclArray]),'points gridspacing,probeRadius=',gridspacing,probeRadius) return coordGrd[lgclArray] - + def SetDrawingDefaults(drawingData): """Add required items into data['drawing'] array if not present. This does not add all the items in SetupDrawingData, but it seems that this is not a problem. Perhaps the @@ -1595,25 +1597,28 @@ def SetDrawingDefaults(drawingData): if key not in drawingData: drawingData[key] = defaultDrawing[key] def updateAddRBorientText(G2frame,testRBObj,Bmat): - '''Update all origin/orientation text on the Add RB panel or + '''Update all origin/orientation text on the Add RB panel or on main RB Models page in response to Alt+mouse movement ''' A,V = G2mth.Q2AVdeg(testRBObj['rbObj']['Orient'][0]) testRBObj['rbObj']['OrientVec'][0] = A testRBObj['rbObj']['OrientVec'][1:] = np.inner(Bmat,V) for i,val in enumerate(testRBObj['rbObj']['OrientVec']): - G2frame.testRBObjSizers['OrientVecSiz'][i].SetValue(val) + G2frame.testRBObjSizers['OrientVecSiz'][i].ChangeValue(val) +# G2frame.testRBObjSizers['OrientVecSiz'][i].SetValue(val) try: - G2frame.testRBObjSizers['OrientVecSiz'][4].SetValue( +# G2frame.testRBObjSizers['OrientVecSiz'][4].SetValue( + G2frame.testRBObjSizers['OrientVecSiz'][4].ChangeValue( int(10*testRBObj['rbObj']['OrientVec'][0])) except: pass for i,sizer in enumerate(G2frame.testRBObjSizers['Xsizers']): - sizer.SetValue(testRBObj['rbObj']['Orig'][0][i]) + sizer.ChangeValue(testRBObj['rbObj']['Orig'][0][i]) +# sizer.SetValue(testRBObj['rbObj']['Orig'][0][i]) # redraw asymmetric unit when called on an existing body if G2frame.testRBObjSizers.get('OnOrien') is None: return G2frame.testRBObjSizers['OnOrien'](mode=testRBObj['rbObj']['drawMode']) - + def UpdatePhaseData(G2frame,Item,data): '''Create the data display window contents when a phase is clicked on in the main (data tree) window. @@ -1664,7 +1669,7 @@ def SetupGeneral(): def UpdateGeneral(Scroll=0,SkipDraw=False): '''Draw the controls for the General phase data subpage ''' - + """ This is the default dictionary structure for phase data (taken from GSASII.py) 'General':{ @@ -1678,8 +1683,8 @@ def UpdateGeneral(Scroll=0,SkipDraw=False): 'Atoms':[] 'Drawing':{} """ - def NameSizer(): - + def NameSizer(): + def SetDefaultSSsymbol(): if generalData['SGData']['SGLaue'] in '-1': return '(abg)' @@ -1692,39 +1697,13 @@ def SetDefaultSSsymbol(): return '(00g)' else: return '(00g)' - + def OnPhaseName(event): + 'called when the phase name is changed in "General"' event.Skip() - oldName = generalData['Name'] - phaseRIdList,usedHistograms = G2frame.GetPhaseInfofromTree() - phaseNameList = usedHistograms.keys() # phase names in use newName = NameTxt.GetValue().strip() - if newName and newName != oldName: - newName = G2obj.MakeUniqueLabel(newName,list(phaseNameList)) - generalData['Name'] = newName - G2frame.G2plotNB.Rename(oldName,generalData['Name']) - G2frame.GPXtree.SetItemText(Item,generalData['Name']) - # change phase name key in Reflection Lists for each histogram - for hist in data['Histograms']: - ht = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist) - rt = G2gd.GetGPXtreeItemId(G2frame,ht,'Reflection Lists') - if not rt: continue - RfList = G2frame.GPXtree.GetItemPyData(rt) - if oldName not in RfList: - print('Warning: '+oldName+' not in Reflection List for '+ - hist) - continue - RfList[newName] = RfList[oldName] - del RfList[oldName] - NameTxt.SetValue(newName) - # rename Restraints - resId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints') - Restraints = G2frame.GPXtree.GetItemPyData(resId) - i = G2gd.GetGPXtreeItemId(G2frame,resId,oldName) - if i: G2frame.GPXtree.SetItemText(i,newName) - if len(Restraints): - Restraints[newName] = Restraints[oldName] - del Restraints[oldName] + renamePhaseNameTop(G2frame,data,Item,generalData,newName) + NameTxt.SetValue(newName) def OnPhaseType(event): if not len(generalData['AtomTypes']): #can change only if no atoms! @@ -1734,7 +1713,7 @@ def OnPhaseType(event): G2frame.Bind(wx.EVT_MENU, OnLoadDIFFaX, id=G2G.wxID_LOADDIFFAX) G2frame.Bind(wx.EVT_MENU, OnSimulate, id=G2G.wxID_LAYERSIMULATE) G2frame.Bind(wx.EVT_MENU, OnSeqSimulate, id=G2G.wxID_SEQUENCESIMULATE) - G2frame.Bind(wx.EVT_MENU, OnFitLayers, id=G2G.wxID_LAYERSFIT) + G2frame.Bind(wx.EVT_MENU, OnFitLayers, id=G2G.wxID_LAYERSFIT) if 'Wave Data' in pages: pass # G2frame.phaseDisplay.DeletePage(pages.index('Wave Data')) @@ -1772,8 +1751,8 @@ def OnPhaseType(event): wx.CallAfter(UpdateGeneral) else: G2frame.ErrorDialog('Phase type change error','Can change phase type only if there are no atoms') - TypeTxt.SetValue(generalData['Type']) - + TypeTxt.SetValue(generalData['Type']) + def OnSpaceGroup(event): if generalData['SGData']['SGFixed']: msg = 'Fixed cif generated magnetic space group' @@ -1836,7 +1815,7 @@ def OnSpaceGroup(event): data['Histograms'][hist]['HStrain'] = [NDij*[0.0,],NDij*[False,]] if data['Drawing']: data['Drawing']['Atoms'] = [] wx.CallAfter(UpdateGeneral) - + def OnModulated(event): if not len(generalData['AtomTypes']): #can change only if no atoms! pages = [G2frame.phaseDisplay.GetPageText(PageNum) for PageNum in range(G2frame.phaseDisplay.GetPageCount())] @@ -1895,8 +1874,8 @@ def OnModulated(event): wx.CallAfter(UpdateGeneral) else: G2frame.ErrorDialog('Modulation type change error','Can change modulation only if there are no atoms') - modulated.SetValue(generalData['Modulated']) - + modulated.SetValue(generalData['Modulated']) + nameSizer = wx.BoxSizer(wx.HORIZONTAL) nameSizer.Add(wx.StaticText(General,-1,' Phase name: '),0,WACV) # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) @@ -1919,9 +1898,9 @@ def OnModulated(event): modulated = wx.CheckBox(General,label='Modulated? ') modulated.SetValue(generalData['Modulated']) modulated.Bind(wx.EVT_CHECKBOX,OnModulated) - nameSizer.Add(modulated,0,WACV) + nameSizer.Add(modulated,0,WACV) return nameSizer - + def CellSizer(): cellGUIlist = [[['m3','m3m'],4,zip([" Unit cell: a = "," Vol = "],["%.5f","%.3f"],[True,False],[0,0])], [['3R','3mR'],6,zip([" a = "," alpha = "," Vol = "],["%.5f","%.3f","%.3f"],[True,True,False],[0,3,0])], @@ -1937,10 +1916,10 @@ def CellSizer(): [['-1'],7,zip([" a = "," b = "," c = "," Vol = "," alpha = "," beta = "," gamma = "], ["%.5f","%.5f","%.5f","%.3f","%.3f","%.3f","%.3f"], [True,True,True,False,True,True,True],[0,1,2,0,3,4,5])]] - + def OnCellRef(event): generalData['Cell'][0] = cellRef.GetValue() - + def OnCellChange(invalid,value,tc): SGData = generalData['SGData'] laue = SGData['SGLaue'] @@ -1967,7 +1946,7 @@ def OnCellChange(invalid,value,tc): else: cell[4] = cell[5] = cell[6] = value Obj.SetValue(cell[4]) - elif laue in ['3','3m1','31m','6/m','6/mmm','4/m','4/mmm']: + elif laue in ['3','3m1','31m','6/m','6/mmm','4/m','4/mmm']: cell[4] = cell[5] = 90. cell[6] = 120. if laue in ['4/m','4/mmm']: @@ -2008,7 +1987,7 @@ def OnCellChange(invalid,value,tc): Obj.SetValue(cell[6]) else: cell[ObjId+1] = value - Obj.SetValue(cell[1+ObjId]) + Obj.SetValue(cell[1+ObjId]) cell[7] = G2lat.calc_V(G2lat.cell2A(cell[1:7])) volVal.SetValue("%.3f"%(cell[7])) density,mattCoeff = G2mth.getDensity(generalData) @@ -2016,7 +1995,7 @@ def OnCellChange(invalid,value,tc): denSizer[1].SetValue('%.3f'%(density)) if denSizer[2]: denSizer[2].SetValue('%.3f'%(mattCoeff)) - + cell = generalData['Cell'] laue = generalData['SGData']['SGLaue'] if laue == '2/m': @@ -2045,9 +2024,9 @@ def OnCellChange(invalid,value,tc): volVal = G2G.ReadOnlyTextCtrl(General,value=(fmt%(cell[7]))) cellSizer.Add(volVal,0,WACV) return cellSizer - + def ElemSizer(): - + def OnIsotope(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] @@ -2068,8 +2047,8 @@ def onDefColor(event): Changes default color for element in all phases N.B. Change is not saved; will go back to original color when GSAS-II is restarted. - Changes colors of matching atoms in Draw Atoms table for - current phase -- only. + Changes colors of matching atoms in Draw Atoms table for + current phase -- only. ''' if not hasattr(event.GetEventObject(),'atomNum'): return anum = event.GetEventObject().atomNum @@ -2087,7 +2066,7 @@ def onDefColor(event): atom[cs+2] = RGB wx.CallAfter(UpdateGeneral) dlg.Destroy() - + elemSizer = wx.FlexGridSizer(0,len(generalData['AtomTypes'])+1,1,1) elemSizer.Add(wx.StaticText(General,label=' Elements'),0,WACV) for elem in generalData['AtomTypes']: @@ -2139,25 +2118,25 @@ def onDefColor(event): xmin=0.5,xmax=3.0,nDig=(10,2)) elemSizer.Add(gfacTxt,0,WACV) return elemSizer - + def DenSizer(): - + generalData['Mass'] = G2mth.getMass(generalData) density,mattCoeff = G2mth.getDensity(generalData) denSizer = wx.BoxSizer(wx.HORIZONTAL) denSizer.Add(wx.StaticText(General,-1,' Density: '),0,WACV) denTxt = G2G.ReadOnlyTextCtrl(General,-1,'%.3f'%(density)) denSizer.Add(denTxt,0,WACV) - mattTxt = None + mattTxt = None if generalData['Type'] == 'macromolecular' and generalData['Mass'] > 0.0: denSizer.Add(wx.StaticText(General,-1,' Matthews coeff.: '), 0,WACV) mattTxt = G2G.ReadOnlyTextCtrl(General,-1,'%.3f'%(mattCoeff)) denSizer.Add(mattTxt,0,WACV) return denSizer,denTxt,mattTxt - + def MagSizer(): - + def OnSpinOp(event): if SGData['SGFixed']: msg = 'Fixed cif generated spins' @@ -2167,11 +2146,11 @@ def OnSpinOp(event): return Obj = event.GetEventObject() isym = Indx[Obj.GetId()]+1 - spCode = {'red':-1,'black':1} + spCode = {'red':-1,'black':1} SGData['SGSpin'][isym] = spCode[Obj.GetValue()] G2spc.CheckSpin(isym,SGData) wx.CallAfter(UpdateGeneral) - + def OnBNSlatt(event): Obj = event.GetEventObject() BNSlatt = Obj.GetValue() @@ -2193,7 +2172,7 @@ def OnBNSlatt(event): generalData['SGData'] = SGData G2spc.UpdateSytSym(data) wx.CallAfter(UpdateGeneral) - + def OnShowSpins(event): msg = 'Magnetic space group information' text,table = G2spc.SGPrint(SGData,AddInv=not SGData['SGFixed']) @@ -2204,8 +2183,8 @@ def OnShowSpins(event): text[3] += "1'" G2G.SGMagSpinBox(General,msg,text,table,SGData['SGCen'],OprNames, SGData['SpnFlp'],SGData['SGGray']& (not SGData['SGFixed'])).Show() - - SGData = generalData['SGData'] + + SGData = generalData['SGData'] GenSym,GenFlg,BNSsym = G2spc.GetGenSym(SGData) if 'BNSlattsym' not in SGData: SGData['BNSlattsym'] = [SGData['SGLatt'],[0,0,0]] @@ -2223,7 +2202,7 @@ def OnShowSpins(event): else: if not len(GenSym): # or SGData['SGGray']: spinSizer.Add(wx.StaticText(General,label=' No spin inversion allowed'),0,WACV) - OprNames,SpnFlp = G2spc.GenMagOps(SGData) + OprNames,SpnFlp = G2spc.GenMagOps(SGData) else: spinSizer.Add(wx.StaticText(General,label=' BNS lattice: '),0,WACV) BNSkeys = [SGData['SGLatt'],]+list(BNSsym.keys()) @@ -2241,9 +2220,9 @@ def OnShowSpins(event): spinColor = ['black','red'] spCode = {-1:'red',1:'black'} for isym,sym in enumerate(GenSym[1:]): - spinSizer.Add(wx.StaticText(General,label=' %s: '%(sym.strip())),0,WACV) + spinSizer.Add(wx.StaticText(General,label=' %s: '%(sym.strip())),0,WACV) spinOp = wx.ComboBox(General,value=spCode[SGData['SGSpin'][isym+1]],choices=spinColor, - style=wx.CB_READONLY|wx.CB_DROPDOWN) + style=wx.CB_READONLY|wx.CB_DROPDOWN) Indx[spinOp.GetId()] = isym spinOp.Bind(wx.EVT_COMBOBOX,OnSpinOp) spinSizer.Add(spinOp,0,WACV) @@ -2263,15 +2242,15 @@ def OnShowSpins(event): dminSizer.Add(dminVal,0,WACV) magSizer.Add(dminSizer,0) return magSizer - + def ModulatedSizer(name): - + def OnShowSOps(event): SSGData = generalData['SSGData'] text,table = G2spc.SSGPrint(generalData['SGData'],SSGData,not SGData['SGFixed']) msg = 'Superspace Group Information' G2G.SGMessageBox(General,msg,text,table,SGData.get('SpnFlp',[])).ShowModal() - + def OnSuperGp(event): #for HKLF needs to reject SSgps not agreeing with modVec! 'Respond to selection of a modulation group' wx.BeginBusyCursor() @@ -2321,13 +2300,13 @@ def OnSuperGp(event): #for HKLF needs to reject SSgps not agreeing with modVec Text = '\n'.join([E+'\nSuperspace Group entry ignored']) G2G.G2MessageBox(General,Text,'Superspace Group Error') wx.CallAfter(UpdateGeneral) - + def OnVecRef(event): generalData['SuperVec'][1] = Ref.GetValue() - + def OnMax(event): generalData['SuperVec'][2] = int(Max.GetValue()) - + Indx = {} ssSizer = wx.BoxSizer(wx.VERTICAL) modSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -2355,7 +2334,7 @@ def OnMax(event): indChoice = ['1',] Max = wx.ComboBox(General,-1,value='%d'%(generalData['SuperVec'][2]),choices=indChoice, style=wx.CB_READONLY|wx.CB_DROPDOWN) - Max.Bind(wx.EVT_COMBOBOX,OnMax) + Max.Bind(wx.EVT_COMBOBOX,OnMax) modSizer.Add(Max,0,WACV) ssSizer.Add(modSizer,0) vecSizer = wx.FlexGridSizer(1,5,5,5) @@ -2378,7 +2357,7 @@ def OnMax(event): vecSizer.Add(Ref,0,WACV) ssSizer.Add(vecSizer) return ssSizer - + def PawleySizer(): # find d-space range in used histograms def enablePawley(*args): @@ -2394,9 +2373,9 @@ def enablePawley(*args): G2G.G2MessageBox(G2frame,title='Note:', msg='Use Pawley Create in Operations menu of Pawley'+ ' Reflections tab to complete the Pawley setup') - + dmin,dmax,nhist,lbl = getPawleydRange(G2frame,data) - + pawleySizer = wx.BoxSizer(wx.HORIZONTAL) pawleySizer.Add(wx.StaticText(General,label=' Pawley controls: '),0,WACV) if nhist == 0: # no data, no Pawley @@ -2432,18 +2411,18 @@ def enablePawley(*args): for c in PawleyCtrlsList: c.Enable(generalData['doPawley']) return pawleyOuter - + def MapSizer(): - + def OnMapType(event): Map['MapType'] = mapType.GetValue() if 'delt-F' in Map['MapType']: data['Drawing']['contourColor'] = 'RdYlGn' else: data['Drawing']['contourColor'] = 'YlGnBu' - + def OnRefList(event): - if not refsList: + if not refsList: G2G.G2MessageBox(G2frame,'No reflections') return dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select reflection sets to use', @@ -2480,7 +2459,7 @@ def OnDysnomia(event): else: if 'Dysnomia' in pages: G2frame.phaseDisplay.DeletePage(pages.index('Dysnomia')) - + #patch if 'cutOff' not in Map: Map['cutOff'] = 100.0 @@ -2520,20 +2499,20 @@ def OnDysnomia(event): Dysno.SetValue(generalData['doDysnomia']) Dysno.Bind(wx.EVT_CHECKBOX,OnDysnomia) line2Sizer.Add(Dysno,0,WACV) - hlpText = '''Dysnomia uses the maximum entropy method + hlpText = '''Dysnomia uses the maximum entropy method to compute intensities for unobserved reflections. ''' hlp = G2G.HelpButton(General,hlpText) line2Sizer.Add(hlp,0,WACV) mapSizer.Add(line2Sizer,0) return mapSizer - + def FlipSizer(): #patches if 'k-Max' not in Flip: Flip['k-Max'] = 20. if 'Resolution' in Flip: Flip['GridStep'] = Flip['Resolution'] - + def OnRefList(event): dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select reflection sets to use', 'Use data',refsList) @@ -2548,15 +2527,15 @@ def OnRefList(event): return finally: dlg.Destroy() - wx.CallAfter(UpdateGeneral,General.GetScrollPos(wx.VERTICAL)) - + wx.CallAfter(UpdateGeneral,General.GetScrollPos(wx.VERTICAL)) + def OnNormElem(event): PE = G2elemGUI.PickElement(G2frame,ifNone=True) if PE.ShowModal() == wx.ID_OK: Flip['Norm element'] = PE.Elem.strip() normElem.SetLabel(Flip['Norm element']) - PE.Destroy() - + PE.Destroy() + def OnTestHKL(event): event.Skip() Obj = event.GetEventObject() @@ -2602,35 +2581,35 @@ def OnTestHKL(event): if len(Flip['testHKL']) < 5: Flip['testHKL'] += [[1,1,1],[0,2,0],[1,2,3]] HKL = Flip['testHKL'] - for ih,hkl in enumerate(Flip['testHKL']): + for ih,hkl in enumerate(Flip['testHKL']): # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) hkl = wx.TextCtrl(General,value='%3d %3d %3d'%(HKL[ih][0],HKL[ih][1],HKL[ih][2]), style=wx.TE_PROCESS_ENTER,name='hkl%d'%(ih)) - hkl.Bind(wx.EVT_TEXT_ENTER,OnTestHKL) + hkl.Bind(wx.EVT_TEXT_ENTER,OnTestHKL) hkl.Bind(wx.EVT_KILL_FOCUS,OnTestHKL) line3Sizer.Add(hkl,0,WACV) flipSizer.Add(line3Sizer) return flipSizer - + def MCSASizer(): - + def OnRefList(event): MCSAdata['Data source'] = refList.GetValue() - + def OnCycles(event): MCSAdata['Cycles'] = int(cycles.GetValue()) - + def OnAlist(event): MCSAdata['Algorithm'] = Alist.GetValue() - OnShowTsched() + OnShowTsched() wx.CallAfter(UpdateGeneral,General.GetScrollPos(wx.VERTICAL)) - + def OnRanStart(event): MCSAdata['ranStart'] = ranStart.GetValue() - + # def OnAutoRan(event): # MCSAdata['autoRan'] = autoRan.GetValue() - + def OnAnneal(event): event.Skip() Obj = event.GetEventObject() @@ -2649,18 +2628,18 @@ def OnAnneal(event): MCSAdata['Annealing'][ind] = val Obj.SetValue(fmt%(MCSAdata['Annealing'][ind])) except ValueError: - MCSAdata['Annealing'][ind] = None + MCSAdata['Annealing'][ind] = None Obj.SetValue(str(MCSAdata['Annealing'][ind])) - + def ShowTsched(invalid,value,tc): OnShowTsched() - + def OnShowTsched(): if MCSAdata['Algorithm'] in ['fast','log']: Y = G2mth.makeTsched(MCSAdata) XY = [np.arange(len(Y)),np.log10(Y)] G2plt.PlotXY(G2frame,[XY,],labelX='T-step',labelY='log(T)',newPlot=True,lines=True,Title='Annealing schedule') - + # OnShowTsched() refList = [] if len(data['Pawley ref']): @@ -2683,7 +2662,7 @@ def OnShowTsched(): Cchoice = [str(2**i) for i in range(13)] cycles = wx.ComboBox(General,-1,value=str(MCSAdata.get('Cycles',1)),choices=Cchoice, style=wx.CB_READONLY|wx.CB_DROPDOWN) - cycles.Bind(wx.EVT_COMBOBOX,OnCycles) + cycles.Bind(wx.EVT_COMBOBOX,OnCycles) line2Sizer.Add(cycles,0,WACV) line2Sizer.Add((5,0),) ranStart = wx.CheckBox(General,-1,label=' MC/SA Refine at ') @@ -2722,7 +2701,7 @@ def OnShowTsched(): line3Sizer.Add(wx.StaticText(General,label=' Annealing schedule: '),0,WACV) if 'Basin Hopping' in MCSAdata['Algorithm']: line3Sizer.Add(wx.StaticText(General,label=' Test temp: '),0,WACV) - line3Sizer.Add(G2G.ValidatedTxtCtrl(General,MCSAdata['Annealing'],0,nDig=(10,5)),0,WACV) + line3Sizer.Add(G2G.ValidatedTxtCtrl(General,MCSAdata['Annealing'],0,nDig=(10,5)),0,WACV) else: line3Sizer.Add(wx.StaticText(General,label=' Start temp: '),0,WACV) line3Sizer.Add(G2G.ValidatedTxtCtrl(General,MCSAdata['Annealing'],0,nDig=(10,5),OnLeave=ShowTsched),0,WACV) @@ -2730,17 +2709,17 @@ def OnShowTsched(): line3Sizer.Add(G2G.ValidatedTxtCtrl(General,MCSAdata['Annealing'],1,nDig=(10,5),OnLeave=ShowTsched),0,WACV) line3Sizer.Add(wx.StaticText(General,label=' No. trials: '),0,WACV) line3Sizer.Add(G2G.ValidatedTxtCtrl(General,MCSAdata['Annealing'],2),0,WACV) - mcsaSizer.Add(line3Sizer) + mcsaSizer.Add(line3Sizer) return mcsaSizer - + def compareSizer(): - + def OnOatmOsel(event): generalData['Compare']['Oatoms'] = oatmsel.GetStringSelection() - + def OnTatmOsel(event): generalData['Compare']['Tatoms'] = tatmsel.GetStringSelection() - + def OnCompPlots(event): pName = generalData['Name'] Oatoms = generalData['Compare']['Oatoms'] @@ -2759,19 +2738,19 @@ def OnCompPlots(event): Bonds['Obonds'] = np.array(Bonds['Obonds']) Bmean = np.mean(Bonds['Obonds']) Bstd = np.std(Bonds['Obonds']) - title = '%s-%s Octahedral bond lengths'%(Oatoms,Tatoms) + title = '%s-%s Octahedral bond lengths'%(Oatoms,Tatoms) G2plt.PlotBarGraph(G2frame,Bonds['Obonds'],Xname=r'$Bond, \AA$',Title=title, PlotName='Oct %s Bond for %s'%(bName,pName)) Tilts['Otilts'] = np.array(Tilts['Otilts']) Tmean = np.mean(Tilts['Otilts']) - Tstd = np.std(Tilts['Otilts']) + Tstd = np.std(Tilts['Otilts']) G2plt.PlotBarGraph(G2frame,Tilts['Otilts'],Xname='Tilts, deg', Title='Octahedral %s tilts'%Oatoms,PlotName='Oct %s Tilts for %s'%(bName,pName)) dVects['Ovec'] = np.reshape(np.array(dVects['Ovec']),(-1,3)) - for ix,aX in enumerate(['X','Y','Z']): + for ix,aX in enumerate(['X','Y','Z']): G2plt.PlotBarGraph(G2frame,dVects['Ovec'].T[ix],Xname=r'$%s%s, \AA$'%(GkDelta,aX), Title='%s Octahedral distortion'%Oatoms,PlotName='Oct %s %s-Delta for %s'%(bName,aX,pName)) - Vects['Ovec'] = np.array(Vects['Ovec']) #3D plot of tilt vectors + Vects['Ovec'] = np.array(Vects['Ovec']) #3D plot of tilt vectors X = Vects['Ovec'].T[0] Y = Vects['Ovec'].T[1] Z = Vects['Ovec'].T[2] @@ -2780,7 +2759,7 @@ def OnCompPlots(event): Title=r'%s Octahedral tilt vectors'%Oatoms,PlotName='Oct %s tilts for %s'%(bName,pName)) print(' %s-%s bond distance: %.3f(%d)'%(Oatoms,Tatoms,Bmean,Bstd*1000)) print(' %s tilt angle: %.2f(%d)'%(Oatoms,Tmean,Tstd*100)) - + if len(Bonds['Tbonds']): print('Tetrahedra:') Bonds['Tbonds'] = np.array(Bonds['Tbonds']) @@ -2797,7 +2776,7 @@ def OnCompPlots(event): dVects['Tvec'] = np.reshape(np.array(dVects['Tvec']),(-1,3)) for ix,aX in enumerate(['X','Y','Z']): G2plt.PlotBarGraph(G2frame,dVects['Tvec'].T[ix],Xname=r'$%s%s, \AA$'%(GkDelta,aX), - Title='%s Tetrahedral distortion'%Oatoms,PlotName='Tet %s %s-Delta for %s'%(bName,aX,pName)) + Title='%s Tetrahedral distortion'%Oatoms,PlotName='Tet %s %s-Delta for %s'%(bName,aX,pName)) Vects['Tvec'] = np.array(Vects['Tvec']) X = Vects['Tvec'].T[0] Y = Vects['Tvec'].T[1] @@ -2807,7 +2786,7 @@ def OnCompPlots(event): Title=r'%s Tetrahedral tilt vectors'%Oatoms,PlotName='Tet %s tilts for %s'%(bName,pName)) print(' %s-%s bond distance: %.3f(%d)'%(Oatoms,Tatoms,Bmean,Bstd*1000)) print(' %s tilt angle: %.2f(%d)'%(Oatoms,Tmean,Tstd*100)) - + Oatoms = generalData['Compare']['Oatoms'] Tatoms = generalData['Compare']['Tatoms'] @@ -2815,23 +2794,23 @@ def OnCompPlots(event): atTypes = generalData['AtomTypes'] compSizer = wx.BoxSizer(wx.VERTICAL) compSizer.Add(wx.StaticText(General,label=' Compare polyhedra to ideal octahedra/tetrahedra:'),0) - + atmselSizer = wx.BoxSizer(wx.HORIZONTAL) atmselSizer.Add(wx.StaticText(General,label=' Select origin atom type: '),0,WACV) oatmsel = wx.ComboBox(General,choices=atTypes,style=wx.CB_READONLY|wx.CB_DROPDOWN) oatmsel.SetStringSelection(generalData['Compare']['Oatoms']) oatmsel.Bind(wx.EVT_COMBOBOX,OnOatmOsel) atmselSizer.Add(oatmsel,0,WACV) - + atmselSizer.Add(wx.StaticText(General,label=' Select target atom type: '),0,WACV) tatmsel = wx.ComboBox(General,choices=atTypes,style=wx.CB_READONLY|wx.CB_DROPDOWN) tatmsel.SetStringSelection(generalData['Compare']['Tatoms']) tatmsel.Bind(wx.EVT_COMBOBOX,OnTatmOsel) atmselSizer.Add(tatmsel,0,WACV) - + atmselSizer.Add(wx.StaticText(General,label=' Sampling fraction:: '),0,WACV) atmselSizer.Add(G2G.ValidatedTxtCtrl(General,generalData['Compare'],'Sampling',nDig=(8,3),xmin=0.0,xmax=1.0,),0,WACV) - + try: if len(generalData['Compare']['Bonds'][bName]['Obonds']) or len(generalData['Compare']['Bonds'][bName]['Tbonds']): plotBtn = wx.Button(General,label='Show plots?') @@ -2853,9 +2832,9 @@ def OnCompPlots(event): G2frame.phaseDisplay.DeletePage(pages.index('Wave Data')) Map = generalData['Map'] Flip = generalData['Flip'] - MCSAdata = generalData['MCSA controls'] + MCSAdata = generalData['MCSA controls'] PWDR = any(['PWDR' in item for item in data['Histograms'].keys()]) -#patches +#patches if 'Pawley dmax' not in data['General']: data['General']['Pawley dmax'] = 100.0 if 'SGFixed' not in data['General']['SGData']: @@ -2879,16 +2858,16 @@ def OnCompPlots(event): mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add((5,5),0) mainSizer.Add(NameSizer(),0,wx.EXPAND) - mainSizer.Add((5,5),0) + mainSizer.Add((5,5),0) mainSizer.Add(CellSizer(),0) mainSizer.Add((5,5),0) - + Indx = {} denSizer = None if len(generalData['AtomTypes']): denSizer = DenSizer() mainSizer.Add(denSizer[0]) - mainSizer.Add((5,5),0) + mainSizer.Add((5,5),0) mainSizer.Add(ElemSizer()) G2G.HorizontalLine(mainSizer,General) if generalData['Type'] == 'magnetic': @@ -2918,7 +2897,7 @@ def OnCompPlots(event): mainSizer.Add(MapSizer()) G2G.HorizontalLine(mainSizer,General) - + mainSizer.Add(FlipSizer()) if generalData['Type'] in ['nuclear','macromolecular','faulted',]: G2G.HorizontalLine(mainSizer,General) @@ -2928,18 +2907,28 @@ def OnCompPlots(event): G2frame.dataWindow.GeneralCalc.Enable(G2G.wxID_COMPARESTRUCTURE,True) G2G.HorizontalLine(mainSizer,General) mainSizer.Add(compareSizer()) - if SkipDraw: + if SkipDraw: mainSizer.Clear(True) return G2frame.GetStatusBar().SetStatusText('',1) SetPhaseWindow(General,mainSizer,Scroll=Scroll) - + def OnTransform(event): Trans = np.eye(3) Uvec = np.zeros(3) Vvec = np.zeros(3) ifMag = False BNSlatt = '' + ifRB = False + RBModels = data['RBModels'] + for item in RBModels: + if RBModels[item]: + ifRB = True + if ifRB: + msg = 'Rigid Bodies present' + text = 'Rigid bodies must be deleted before transform can be done' + wx.MessageBox(text,caption=msg,style=wx.ICON_EXCLAMATION) + return while True: dlg = TransformDialog(G2frame,data,Trans,Uvec,Vvec,ifMag,BNSlatt) try: @@ -2952,7 +2941,7 @@ def OnTransform(event): SGData = newPhase['General']['SGData'] if ifMag: BNSlatt = SGData['BNSlattsym'][0] - + SGData['GenSym'],SGData['GenFlg'],BNSsym = G2spc.GetGenSym(SGData) if '_' in BNSlatt: SGData['BNSlattsym'] = [BNSlatt,BNSsym[BNSlatt]] @@ -2995,7 +2984,7 @@ def OnTransform(event): SGData['fromParent'] = [Trans,Uvec,Vvec] #save these Atoms = newPhase['Atoms'] if ifMag: - atMxyz = [] + atMxyz = [] for atom in Atoms: if data['General']['Super']: atom += [{'SS1':{'waveType':'Fourier','Sfrac':[],'Spos':[],'Sadp':[],'Smag':[]}}] @@ -3016,7 +3005,7 @@ def OnTransform(event): dlg.Destroy() else: break - + NShkl = len(G2spc.MustrainNames(SGData)) NDij = len(G2spc.HStrainNames(SGData)) UseList = newPhase['Histograms'] @@ -3034,10 +3023,10 @@ def OnTransform(event): if ifConstr: G2cnstG.TransConstraints(G2frame,data,newPhase,Trans,Vvec,atCodes) #data is old phase G2frame.GPXtree.SelectItem(sub) - + def OnISOSearch(event): - '''Search for a higher symmetry structure consistent with the - current phase using the ISOCIF web service + '''Search for a higher symmetry structure consistent with the + current phase using the ISOCIF web service ''' def _showWebPage(event): 'Show a web page when the user presses the "show" button' @@ -3045,7 +3034,8 @@ def _showWebPage(event): txt = event.GetEventObject().page tmp = tempfile.NamedTemporaryFile(suffix='.html', delete=False) - open(tmp.name,'w').write(txt.replace( + with open(tmp.name,'w') as fp: + fp.write(txt.replace( '', '', )) @@ -3054,11 +3044,12 @@ def _showWebPage(event): import tempfile import re import requests - import G2export_CIF + from GSASII.exports import G2export_CIF isosite="https://stokes.byu.edu/iso/" upscript='isocifuploadfile.php' isoscript='isocifform.php' - isoCite = '''For use of this supergroup search, please cite H. T. Stokes, D. M. Hatch, and B. J. Campbell, ISOCIF, ISOTROPY Software Suite, iso.byu.edu.''' + isoSubCite = ('For use of this supergroup search, please cite:\n'+ + G2G.GetCite('ISOTROPY, ISODISTORT, ISOCIF...',wrap=60,indent=5)) latTol,coordTol,occTol = 0.001, 0.01, 0.1 oacomp,occomp = G2mth.phaseContents(data) ophsnam = data['General']['Name'] @@ -3088,7 +3079,7 @@ def _showWebPage(event): isofile = f.split('VALUE')[1].split('"')[1] finally: os.unlink(tmp.name) - + # CIF uploaded, now process values1 = {'submit':'OK','input':'uploadcif','filename':isofile} r1 = requests.post(isosite+isoscript, data=values1) @@ -3101,7 +3092,8 @@ def _showWebPage(event): flags=re.IGNORECASE)[0] except IndexError: tmp1 = tempfile.NamedTemporaryFile(suffix='.html') - open(tmp1.name,'w').write(r1.text.replace( + with open(tmp1.name,'w') as fp: + fp.write(r1.text.replace( '', '', )) @@ -3129,7 +3121,7 @@ def _showWebPage(event): 'occupancy tolerance'], values=[latTol,coordTol,occTol], limits=3*[[0.,2.]],formats=3*['%.5g'], - header=isoCite) + header=isoSubCite) res = dlg.ShowModal() latTol,coordTol,occTol = dlg.GetValues() dlg.Destroy() @@ -3173,7 +3165,7 @@ def _showWebPage(event): msg += f", vol={newPhase['General']['Cell'][7]:.2f} A^3" msg += f", density={G2mth.getDensity(newPhase['General'])[0]:.2f} g/cm^3." msg += f"\nAsymmetric unit {G2mth.fmtPhaseContents(nacomp)}." - + msg += f"\n\nOriginal structure is in space group {data['General']['SGData']['SpGrp']}" msg += f" and has {len(data['Atoms'])} atoms. " msg += f"Unit cell {G2mth.fmtPhaseContents(occomp)}" @@ -3185,7 +3177,7 @@ def _showWebPage(event): mainSizer.Add(txt) mainSizer.Add((-1,10)) showSizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_ANY,label='Show') + btn = wx.Button(dlg, wx.ID_ANY,label='Show') btn.page = r2.text btn.Bind(wx.EVT_BUTTON,_showWebPage) showSizer.Add(btn) @@ -3199,20 +3191,20 @@ def _showWebPage(event): 'Choose an action:')) mainSizer.Add((-1,10)) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_CANCEL, label="Quit") + btn = wx.Button(dlg, wx.ID_CANCEL, label="Quit") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) btnsizer.Add((5,5)) - btn = wx.Button(dlg, wx.ID_CLOSE, label="Change tolerances") + btn = wx.Button(dlg, wx.ID_CLOSE, label="Change tolerances") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CLOSE)) btnsizer.Add(btn) btnsizer.Add((5,5)) - btn = wx.Button(dlg, wx.ID_OK, label="Create GSAS-II project\nwith this supergroup") + btn = wx.Button(dlg, wx.ID_OK, label="Create GSAS-II project\nwith this supergroup") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK)) btn.SetDefault() btnsizer.Add(btn) mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) - + mainSizer.Fit(dlg) dlg.CenterOnParent() ans = dlg.ShowModal() @@ -3235,7 +3227,7 @@ def _showWebPage(event): return finally: dlg.Destroy() - + # now create a new .gpx file G2frame.OnFileSave(None) # save project on disk to restore to this later orgFilName = G2frame.GSASprojectfile @@ -3249,7 +3241,7 @@ def _showWebPage(event): Restraints[ophsnam]['Angle']['Angles'] = [] f = saveIsoNewPhase(G2frame,data,newPhase,orgFilName) - wx.MessageBox(f"{isoCite}\n\nCreated project in space group {sglbl} as file {f}", + wx.MessageBox(f"{isoSubCite}\n\nCreated project in space group {sglbl} as file {f}", style=wx.ICON_INFORMATION,caption='File created') for i in fileList: os.unlink(i) # cleanup tmp web pages def _GetPhase(): @@ -3259,13 +3251,13 @@ def _GetPhase(): def _ShowPhase(): 'After search complete and project is reloaded, reopen tree to the original phase' phId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') - G2frame.GPXtree.Expand(phId) + G2frame.GPXtree.Expand(phId) phId = G2gd.GetGPXtreeItemId(G2frame,phId,ophsnam) G2frame.GPXtree.SelectItem(phId) wx.CallLater(100,_GetPhase) - + def OnSuperSearch(event): - '''Search for a supergroup matching the current phase using the + '''Search for a supergroup matching the current phase using the Bilbao Pseudosymmetry search (PSEUDO) program ''' def _showWebPage(event): @@ -3274,7 +3266,8 @@ def _showWebPage(event): num = event.GetEventObject().IndexNum tmp = tempfile.NamedTemporaryFile(suffix='.html', delete=False) - open(tmp.name,'w').write(pagelist[num].replace( + with open(tmp.name,'w') as fp: + fp.write(pagelist[num].replace( '', '', )) @@ -3292,7 +3285,9 @@ def _selectSuperGroups(rowdict,csdict,msg,depth=0,key=0): mainSizer = wx.BoxSizer(wx.VERTICAL) dlg.SetSizer(mainSizer) mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY, - SUBGROUPS.BilbaoSymSearchCite)) + f'''Using the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) +program; Please cite: +{G2G.GetCite('Bilbao: PSEUDO',wrap=70,indent=5)}''')) mainSizer.Add((-1,5)) G2G.HorizontalLine(mainSizer,dlg) txt = wx.StaticText(dlg,wx.ID_ANY, @@ -3304,7 +3299,7 @@ def _selectSuperGroups(rowdict,csdict,msg,depth=0,key=0): mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY, f'This is a {depth}-level supergroup of the original parent model')) showSizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_ANY,label='Show') + btn = wx.Button(dlg, wx.ID_ANY,label='Show') btn.IndexNum = key btn.Bind(wx.EVT_BUTTON,_showWebPage) showSizer.Add(btn) @@ -3338,11 +3333,11 @@ def _selectSuperGroups(rowdict,csdict,msg,depth=0,key=0): spanel.SetSizer(sSizer) mainSizer.Add(spanel,1,wx.ALL|wx.EXPAND,1) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_CANCEL, label="Quit") + btn = wx.Button(dlg, wx.ID_CANCEL, label="Quit") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) btnsizer.Add((5,5)) - btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") + btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK)) btnsizer.Add(btn) mainSizer.Add((-1,10)) @@ -3355,7 +3350,7 @@ def _selectSuperGroups(rowdict,csdict,msg,depth=0,key=0): ans = dlg.ShowModal() return ans def _selectHiSymCell(rowdict,csdict): - '''Present the user with a set of higher symmetry cells that are + '''Present the user with a set of higher symmetry cells that are consistent with the starting cell. Used with monoclinic and triclinic starting cells only. ''' @@ -3365,11 +3360,13 @@ def _selectHiSymCell(rowdict,csdict): mainSizer = wx.BoxSizer(wx.VERTICAL) dlg.SetSizer(mainSizer) mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY, - SUBGROUPS.BilbaoSymSearchCite)) + f'''Using the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) +program; Please cite: +{G2G.GetCite('Bilbao: PSEUDO',wrap=70,indent=5)}''')) mainSizer.Add((-1,5)) G2G.HorizontalLine(mainSizer,dlg) showSizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_ANY,label='Show') + btn = wx.Button(dlg, wx.ID_ANY,label='Show') btn.IndexNum = 0 btn.Bind(wx.EVT_BUTTON,_showWebPage) showSizer.Add(btn) @@ -3402,11 +3399,11 @@ def _selectHiSymCell(rowdict,csdict): spanel.SetSizer(sSizer) mainSizer.Add(spanel,1,wx.ALL|wx.EXPAND,1) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_CANCEL, label="Quit") + btn = wx.Button(dlg, wx.ID_CANCEL, label="Quit") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) btnsizer.Add((5,5)) - btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") + btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_OK)) btnsizer.Add(btn) mainSizer.Add((-1,10)) @@ -3425,7 +3422,7 @@ def _GetPhase(): def _ShowPhase(): 'After search complete and project is reloaded, reopen tree to the original phase' phId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') - G2frame.GPXtree.Expand(phId) + G2frame.GPXtree.Expand(phId) phId = G2gd.GetGPXtreeItemId(G2frame,phId,ophsnam) G2frame.GPXtree.SelectItem(phId) def _testSuperGroups(ophsnam,rowdict,csdict,valsdict,savedcookies,pagelist): @@ -3435,7 +3432,7 @@ def _testSuperGroups(ophsnam,rowdict,csdict,valsdict,savedcookies,pagelist): f'Searching for supergroup(s) consistent with phase {ophsnam}', len(csdict)+2,parent=G2frame, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) - try: + try: pgbar.CenterOnParent() structDict = SUBGROUPS.BilbaoSymSearch2(valsdict,csdict,rowdict,savedcookies, pagelist=pagelist,dlg=pgbar,ophsnam=ophsnam) @@ -3444,20 +3441,20 @@ def _testSuperGroups(ophsnam,rowdict,csdict,valsdict,savedcookies,pagelist): '\ndone') if not GoOn: return wx.GetApp().Yield() - finally: + finally: pgbar.Destroy() return structDict def showSuperResults(G2frame,msgs,pagelist,fileList,ReSearch,parentpage,msg=None): - '''Show a summary with info from a search of supergroups in + '''Show a summary with info from a search of supergroups in :func:`OnSuperSearch` (in :func:`UpdatePhaseData`) ''' - import SUBGROUPS def _showWebPage(event): import tempfile f = event.GetEventObject().webFile tmp = tempfile.NamedTemporaryFile(suffix='.html', delete=False) - open(tmp.name,'w').write(f.replace( + with open(tmp.name,'w') as fp: + fp.write(f.replace( '', '', )) @@ -3468,7 +3465,10 @@ def _showWebPage(event): style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) mainSizer = wx.BoxSizer(wx.VERTICAL) dlg.SetSizer(mainSizer) - mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY,SUBGROUPS.BilbaoSymSearchCite)) + mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY, + f'''Using the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) +program; Please cite: +{G2G.GetCite('Bilbao: PSEUDO',wrap=70,indent=5)}''')) if msg: txt = wx.StaticText(dlg,wx.ID_ANY,'Starting from '+msg.replace('\n',' ')) txt.Wrap(width) @@ -3476,7 +3476,7 @@ def _showWebPage(event): mainSizer.Add(txt) mainSizer.Add((-1,5)) showSizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_ANY,label='Show') + btn = wx.Button(dlg, wx.ID_ANY,label='Show') btn.webFile = parentpage btn.Bind(wx.EVT_BUTTON,_showWebPage) showSizer.Add(btn) @@ -3515,7 +3515,7 @@ def _showWebPage(event): txtSizer.Add((-1,5)) showSizer = wx.BoxSizer(wx.HORIZONTAL) if '@' in num: showSizer.Add((20,-1)) - btn = wx.Button(spanel, wx.ID_ANY,label='Show') + btn = wx.Button(spanel, wx.ID_ANY,label='Show') btn.webFile = pagelist[num] btn.Bind(wx.EVT_BUTTON,_showWebPage) showSizer.Add(btn) @@ -3538,7 +3538,7 @@ def _showWebPage(event): spanel.SetSizer(sSizer) mainSizer.Add(spanel,1,wx.ALL|wx.EXPAND,1) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") + btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) mainSizer.Add((-1,10)) @@ -3554,14 +3554,16 @@ def _showWebPage(event): def _showSummary(G2frame,msgs,gpxList): '''Summarize the final results from all steps''' - + width = 500 dlg = wx.Dialog(G2frame,wx.ID_ANY,'Final Supergroup Search Results', style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) mainSizer = wx.BoxSizer(wx.VERTICAL) dlg.SetSizer(mainSizer) mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY, - SUBGROUPS.BilbaoSymSearchCite)) + f'''Using the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) +program; Please cite: +{G2G.GetCite('Bilbao: PSEUDO',wrap=70,indent=5)}''')) mainSizer.Add((-1,10)) mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY, f'From the starting model, {len(gpxList)} possible supergroups were located.')) @@ -3585,7 +3587,7 @@ def _showSummary(G2frame,msgs,gpxList): spanel.SetSizer(txtSizer) mainSizer.Add(spanel,1,wx.ALL|wx.EXPAND,1) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") + btn = wx.Button(dlg, wx.ID_CLOSE, label="Continue") btn.Bind(wx.EVT_BUTTON,lambda event: dlg.EndModal(wx.ID_CANCEL)) btnsizer.Add(btn) mainSizer.Add((-1,10)) @@ -3606,7 +3608,6 @@ def fmtCell(cell): return s #### processing for OnSuperSearch starts here #### - import SUBGROUPS fileList = [] ReSearch = {} gpxList = [] @@ -3616,7 +3617,7 @@ def fmtCell(cell): style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT, parent=G2frame) pgbar.CenterOnParent() - try: + try: G2frame.OnFileSave(None) # save project on disk to restore to this later orgFilName = G2frame.GSASprojectfile # get restraints for later use (to clear them) @@ -3677,7 +3678,7 @@ def fmtCell(cell): pagelist = {} valsdict,csdict,rowdict,savedcookies = SUBGROUPS.BilbaoSymSearch1( sgnum,newPhase,pagelist=pagelist) - finally: + finally: pgbar.Destroy() # process initial PSEUDO results @@ -3688,7 +3689,7 @@ def fmtCell(cell): 1+len(rowdict), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE, parent=G2frame) - try: + try: pgbar.CenterOnParent() wx.GetApp().Yield() valsdict,csdict,rowdict,savedcookies = SUBGROUPS.BilbaoSymSearch1( @@ -3710,7 +3711,7 @@ def fmtCell(cell): 1+len(rowdict), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT, parent=G2frame) - try: + try: pgbar.CenterOnParent() wx.GetApp().Yield() @@ -3761,7 +3762,7 @@ def fmtCell(cell): ans = showSuperResults(G2frame,msgs,pagelist,fileList,ReSearch,pagelist[0],msgs[0]) for i in fileList: os.unlink(i) # cleanup tmp web pages fileList = [] - + # repeat search on any identified (& selected) supergroups repeatcount = 0 while ReSearch: @@ -3781,7 +3782,7 @@ def fmtCell(cell): 1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE, parent=G2frame) - try: + try: pgbar.CenterOnParent() wx.GetApp().Yield() valsdict,csdict,rowdict,savedcookies = SUBGROUPS.BilbaoReSymSearch( @@ -3809,7 +3810,7 @@ def fmtCell(cell): NextSearch['use_'+nkey] = True msgs[nkey] = msgs.pop(k) ReSearch = NextSearch - + for i in fileList: os.unlink(i) # cleanup tmp web pages # show final message @@ -3822,15 +3823,14 @@ def fmtCell(cell): 'No possible supergroups were found to match the starting model.', 'Search complete') print('Search done, no supergroups located') - + # Restore the original saved project wx.CallLater(100,_GetPhase) - + def OnTransform2Std(event): '''Uses the Bilbao web site to transform a space group and coordinates to a standard setting ''' - import SUBGROUPS generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] sgnum,sgsym,xmat,xoff = SUBGROUPS.GetStdSGset(generalData['SGData']) @@ -3938,20 +3938,20 @@ def OnTransform2Std(event): After {n}, {nvol:.2f} A^3 """ G2G.G2MessageBox(G2frame,msg,'Transform complete') - + # Restore the original saved project G2frame.OnFileOpen(None,filename=orgFilName,askSave=False) # reopen tree to the original phase def _ShowPhase(): phId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') - G2frame.GPXtree.Expand(phId) + G2frame.GPXtree.Expand(phId) phId = G2gd.GetGPXtreeItemId(G2frame,phId,ophsnam) G2frame.GPXtree.SelectItem(phId) wx.CallLater(100,_ShowPhase) - + def OnCompareCells(event): G2G.Load2Cells(G2frame,data) - + def OnCompare(event): generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] @@ -3967,9 +3967,9 @@ def OnCompare(event): dlg.Destroy() Natm = len(data['Atoms']) iAtm= 0 - pgbar = wx.ProgressDialog('Process polyhedron compare for %d atoms'%Natm,'Atoms done=',Natm+1, + pgbar = wx.ProgressDialog('Process polyhedron compare for %d atoms'%Natm,'Atoms done=',Natm+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) - Tilts = generalData['Compare']['Tilts'] + Tilts = generalData['Compare']['Tilts'] Tilts.update({bName:{'Otilts':[],'Ttilts':[]}}) Bonds = generalData['Compare']['Bonds'] Bonds.update({bName:{'Obonds':[],'Tbonds':[]}}) @@ -4012,9 +4012,9 @@ def OnCompare(event): print(' Compare finished; nOct = %d, nTet = %d, nOther = %d'%(nOct,nTet,nElse)) pgbar.Destroy() wx.CallAfter(UpdateGeneral,General.GetScrollPos(wx.VERTICAL)) - + def OnUseBilbao(event): - '''Select and apply a transformation matrix from the Bilbao web site + '''Select and apply a transformation matrix from the Bilbao web site to create a new phase ''' PatternName = data['magPhases'] @@ -4064,7 +4064,7 @@ def OnUseBilbao(event): del newPhase['magPhases'] generalData = newPhase['General'] generalData['Name'] = phaseName - generalData['SGData'] = copy.deepcopy(magchoice['SGData']) + generalData['SGData'] = copy.deepcopy(magchoice['SGData']) generalData['Cell'][1:] = magchoice['Cell'][:] generalData['MagDmin'] = 1.0 SGData = generalData['SGData'] @@ -4122,16 +4122,16 @@ def OnUseBilbao(event): G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases'),text=phaseName) G2frame.GPXtree.SetItemPyData(sub,newPhase) newPhase['Drawing'] = [] - if ifMag: + if ifMag: G2cnstG.TransConstraints(G2frame,data,newPhase,magchoice['Trans'],vvec,atCodes) #data is old phase G2frame.newGPXfile = phaseName.replace('.','_')+'.gpx' #'.' in file names is a bad idea UCdata[5] = [] #clear away other mag choices from chem phase in new project G2frame.GPXtree.SetItemPyData(UnitCellsId,UCdata) G2frame.OnFileSaveas(event) G2frame.GPXtree.SelectItem(sub) - + def OnApplySubgroups(event): - '''Select and apply a transformation matrix from the Bilbao web site + '''Select and apply a transformation matrix from the Bilbao web site to replace current phase with a subgroup ''' PatternName = data['magPhases'] @@ -4175,17 +4175,19 @@ def OnApplySubgroups(event): resId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints') Restraints = G2frame.GPXtree.GetItemPyData(resId) resId = G2gd.GetGPXtreeItemId(G2frame,resId,phsnam) - Restraints[phsnam]['Bond']['Bonds'] = [] - Restraints[phsnam]['Angle']['Angles'] = [] - savedRestraints = Restraints[phsnam] + savedRestraints = None + if phsnam in Restraints: + Restraints[phsnam]['Bond']['Bonds'] = [] + Restraints[phsnam]['Angle']['Angles'] = [] + savedRestraints = Restraints[phsnam] + del Restraints[phsnam] orgData = copy.deepcopy(data) - del Restraints[phsnam] for sel in sels: data.update(copy.deepcopy(orgData)) # get rid of prev phase magchoice = subKeep[sel] spg = magchoice['SGData']['SpGrp'].replace(' ','') #subId = subIds[sel] - # generate the new phase + # generate the new phase newPhase = copy.deepcopy(data) generalData = newPhase['General'] generalData['SGData'] = copy.deepcopy(magchoice['SGData']) @@ -4235,8 +4237,8 @@ def OnApplySubgroups(event): RfList[newName] = [] if phsnam in RfList: del RfList[phsnam] - # copy cleared restraints - Restraints[generalData['Name']] = savedRestraints + if savedRestraints: # restore cleared restraints + Restraints[generalData['Name']] = savedRestraints if resId: G2frame.GPXtree.SetItemText(resId,newName) data.update(newPhase) #clear away prev subgroup choices @@ -4254,7 +4256,7 @@ def OnApplySubgroups(event): # reopen tree to the original phase def _ShowPhase(): phId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') - G2frame.GPXtree.Expand(phId) + G2frame.GPXtree.Expand(phId) phId = G2gd.GetGPXtreeItemId(G2frame,phId,phsnam) G2frame.GPXtree.SelectItem(phId) wx.CallLater(100,_ShowPhase) @@ -4267,7 +4269,7 @@ def RefreshAtomGrid(event): r,c = event.GetRow(),event.GetCol() if r < 0 and c < 0: for row in range(Atoms.GetNumberRows()): - Atoms.SelectRow(row,True) + Atoms.SelectRow(row,True) return if r < 0: #double click on col label! Change all atoms! noSkip = True @@ -4396,7 +4398,7 @@ def RefreshAtomGrid(event): Atoms.SetCellStyle(r,ci,WHITE,False) else: #'A' --> 'I' Uij = atomData[r][ui:ui+6] - Uiso = (Uij[0]+Uij[1]+Uij[2])/3.0 + Uiso = (Uij[0]+Uij[1]+Uij[2])/3.0 atomData[r][us] = Uiso Atoms.SetCellStyle(r,us,WHITE,False) for i in range(6): @@ -4411,12 +4413,12 @@ def RefreshAtomGrid(event): atomData[r][c] = parms.replace(excl,'') else: atomData[r][c] = parms - else: + else: atomData[r][c] = parms if 'Atoms' in data['Drawing']: G2mth.DrawAtomsReplaceByID(data,ui+6,atomData[r],ID) wx.CallAfter(Paint) - + def ChangeAtomCell(event): def chkUij(Uij,CSI): #needs to do something!!! @@ -4498,7 +4500,8 @@ def chkUij(Uij,CSI): #needs to do something!!! if 'Atoms' in data['Drawing'] and replot: ci = colLabels.index('I/A') G2mth.DrawAtomsReplaceByID(data,ci+8,atomData[r],ID) - G2plt.PlotStructure(G2frame,data) + G2frame.GetStatusBar().SetStatusText('Structure changed: Do "Edit Atoms/Update draw atoms" to refresh structure drawing',1) + # G2plt.PlotStructure(G2frame,data) if SGData['SpGrp'] != 'P 1': #no need to update P 1 structures! wx.CallAfter(Paint) @@ -4507,7 +4510,7 @@ def AtomTypeSelect(event): if Atoms.GetColLabelValue(c) == 'Type': PE = G2elemGUI.PickElement(G2frame,ifMag=ifMag) if PE.ShowModal() == wx.ID_OK: - if PE.Elem != 'None': + if PE.Elem != 'None': atomData[r][c] = PE.Elem.strip() name = atomData[r][c] if len(name) in [2,4]: @@ -4523,7 +4526,8 @@ def AtomTypeSelect(event): ID = atomData[r][ci+8] if 'Atoms' in data['Drawing']: G2mth.DrawAtomsReplaceByID(data,ci+8,atomData[r],ID) - G2plt.PlotStructure(G2frame,data) + G2frame.GetStatusBar().SetStatusText('Structure changed: Do "Edit Atoms/Update draw atoms" to refresh structure drawing',1) + # G2plt.PlotStructure(G2frame,data) SetupGeneral() else: event.Skip() @@ -4537,7 +4541,7 @@ def RowSelect(event): Atoms.ClearSelection() elif c < 0: #only row clicks ci = colLabels.index('I/A') - if event.ControlDown() and not event.ShiftDown(): + if event.ControlDown() and not event.ShiftDown(): if r in Atoms.GetSelectedRows(): Atoms.DeselectRow(r) else: @@ -4555,7 +4559,7 @@ def RowSelect(event): G2frame.ErrorDialog('Atom move error','Atoms in rigid bodies can not be moved') Atoms.frm = -1 Atoms.ClearSelection() - else: + else: if Atoms.frm < 0: #pick atom to be moved Atoms.frm = r Atoms.SelectRow(r,True) @@ -4570,11 +4574,12 @@ def RowSelect(event): UpdateDrawAtoms() wx.CallAfter(Paint) else: - G2frame.GetStatusBar().SetStatusText('Use right mouse click to brng up Atom editing options',1) + G2frame.GetStatusBar().SetStatusText('Use right mouse click to brng up Atom editing options',1) Atoms.ClearSelection() Atoms.SelectRow(r,True) - G2plt.PlotStructure(G2frame,data) - + G2frame.GetStatusBar().SetStatusText('Structure changed: Do "Edit Atoms/Update draw atoms" to refresh structure drawing',1) + # G2plt.PlotStructure(G2frame,data) + def ChangeSelection(event): r,c = event.GetRow(),event.GetCol() if r < 0 and c < 0: @@ -4589,7 +4594,7 @@ def ChangeSelection(event): Atoms.DeselectCol(c) else: Atoms.SelectCol(c,True) - + def Paint(Scroll=0): 'Place atom info into the table' table = [] @@ -4602,7 +4607,7 @@ def Paint(Scroll=0): Atoms.SetTable(atomTable, True, useFracEdit=False) # Paint may be called after the Grid has been deleted except: return - Atoms.frm = -1 + Atoms.frm = -1 colType = colLabels.index('Type') colR = colLabels.index('refine') colSS = colLabels.index('site sym') @@ -4671,8 +4676,8 @@ def Paint(Scroll=0): Atoms.SetCellStyle(row,ci,WHITE,False) saveCSI = CSI[0][i] Atoms.SetCellTextColour(row,ci,BLACK) - if 'F' in rbExcl: - Atoms.SetCellStyle(row,colF,VERY_LIGHT_GREY,True) + # if 'F' in rbExcl: + # Atoms.SetCellStyle(row,colF,VERY_LIGHT_GREY,True) if 'X' in rbExcl: for c in range(0,colX+3): if c != colR: @@ -4692,7 +4697,7 @@ def Paint(Scroll=0): Atoms.SetScrollRate(10,10) # allow grid to scroll SetPhaseWindow(AtomList,mainSizer,Scroll=Atoms.GetScrollPos(wx.VERTICAL)) -#### FillAtomsGrid main code +#### FillAtomsGrid main code if not data['Drawing']: #if new drawing - no drawing data! SetupDrawingData() generalData = data['General'] @@ -4711,20 +4716,20 @@ def Paint(Scroll=0): atomData = data['Atoms'] resRBData = data['RBModels'].get('Residue',[]) vecRBData = data['RBModels'].get('Vector',[]) - global rbAtmDict + global rbAtmDict rbAtmDict = {} for rbObj in resRBData+vecRBData: exclList = ['FX' for i in range(len(rbObj['Ids']))] rbAtmDict.update(dict(zip(rbObj['Ids'],exclList))) if rbObj['ThermalMotion'][0] != 'None': for id in rbObj['Ids']: - rbAtmDict[id] += 'U' + rbAtmDict[id] += 'U' # exclList will be 'fx' or 'fxu' if TLS used in RB - Items = [G2G.wxID_ATOMSEDITINSERT,G2G.wxID_ATOMSEDITDELETE, + Items = [G2G.wxID_ATOMSEDITINSERT,G2G.wxID_ATOMSEDITDELETE, G2G.wxID_ATOMSMODIFY,G2G.wxID_ATOMSTRANSFORM,G2G.wxID_MAKEMOLECULE, G2G.wxID_ATOMVIEWINSERT,G2G.wxID_ATOMMOVE,G2G.wxID_ADDHATOM] if atomData: - for item in Items: + for item in Items: G2frame.dataWindow.AtomsMenu.Enable(item,True) else: for item in Items: @@ -4788,7 +4793,7 @@ def Paint(Scroll=0): Atoms.Bind(wg.EVT_GRID_CELL_RIGHT_CLICK, onRightClick) Atoms.Bind(wg.EVT_GRID_LABEL_RIGHT_CLICK, onRightClick) Atoms.SetMargins(0,0) - + Paint() def OnAtomAdd(event): @@ -4800,7 +4805,7 @@ def OnAtomAdd(event): event.StopPropagation() if data['Drawing']: G2plt.PlotStructure(G2frame,data) - + def OnAtomViewAdd(event): Elem = 'H' if data['General']['Type'] == 'magnetic': @@ -4816,7 +4821,7 @@ def OnAtomViewAdd(event): data['Drawing']['Atoms'] = [] UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + def AtomAdd(x,y,z,El='H',Name='UNK',update=True): atomData = data['Atoms'] generalData = data['General'] @@ -4839,11 +4844,11 @@ def AtomAdd(x,y,z,El='H',Name='UNK',update=True): {'SS1':{'waveType':'Fourier','Sfrac':[],'Spos':[],'Sadp':[],'Smag':[]}}]) else: atomData.append([Name,El,'',x,y,z,1.,0.,0.,0.,Sytsym,Mult,'I',0.01,0,0,0,0,0,0,atId]) - + SetupGeneral() # might be better to add new atom to Draw Atoms data['Drawing']['Atoms'] = [] -# if 'Atoms' in data['Drawing']: +# if 'Atoms' in data['Drawing']: # DrawAtomAdd(data['Drawing'],atomData[-1]) if update: UpdateDrawAtoms() @@ -4852,7 +4857,7 @@ def AtomAdd(x,y,z,El='H',Name='UNK',update=True): def OnAtomInsert(event): '''Inserts a new atom into list immediately before every selected atom ''' - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) for a in reversed(sorted(indx)): AtomInsert(a,0.,0.,0.) @@ -4861,7 +4866,7 @@ def OnAtomInsert(event): data['Drawing']['Atoms'] = [] UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + def OnAtomViewInsert(event): if 'Drawing' in data: drawData = data['Drawing'] @@ -4869,11 +4874,11 @@ def OnAtomViewInsert(event): AtomAdd(x,y,z) FillAtomsGrid(Atoms) event.StopPropagation() - + def OnHydAtomAdd(event): '''Adds H atoms to fill out coordination sphere for selected atoms ''' - cx,ct,cs,cia = G2mth.getAtomPtrs(data) + cx,ct,cs,cia = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) if not indx: return DisAglCtls = {} @@ -4945,7 +4950,7 @@ def OnHydAtomAdd(event): loc = AtLookUp[AddHydIds[ineigh][0]]+1 if 'O' in neigh[0] and (not len(mapData['rho']) or not 'delt-F' in mapData['MapType']): mapError = True - continue + continue Hxyz,HU = G2mth.AddHydrogens(AtLookUp,generalData,atomData,AddHydIds[ineigh]) for iX,X in enumerate(Hxyz): AtomInsert(loc+iX,X[0],X[1],X[2],'H','H%s'%(neigh[0][1:]+letters[iX])) @@ -4966,7 +4971,7 @@ def OnHydAtomAdd(event): G2plt.PlotStructure(G2frame,data) else: wx.MessageBox('No candidates found',caption='Add H atom Error',style=wx.ICON_EXCLAMATION) - + def OnHydAtomUpdate(event): generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] @@ -4994,9 +4999,9 @@ def OnHydAtomUpdate(event): UpdateDrawAtoms() FillAtomsGrid(Atoms) G2plt.PlotStructure(G2frame,data) - + def OnAtomMove(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) drawData = data['Drawing'] atomData = data['Atoms'] @@ -5016,7 +5021,7 @@ def OnAtomMove(event): G2mth.DrawAtomsReplaceByID(data,ci+8,atomData[indx[0]],ID) G2plt.PlotStructure(G2frame,data) event.StopPropagation() - + def AtomInsert(indx,x,y,z,El='H',Name='UNK'): atomData = data['Atoms'] generalData = data['General'] @@ -5043,7 +5048,7 @@ def AtomInsert(indx,x,y,z,El='H',Name='UNK'): G2plt.PlotStructure(G2frame,data) def AtomDelete(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms) colLabels = [Atoms.GetColLabelValue(c) for c in range(Atoms.GetNumberCols())] delList = '' @@ -5089,9 +5094,9 @@ def AtomDelete(event): if not len(HydIds): G2frame.dataWindow.AtomEdit.Enable(G2G.wxID_UPDATEHATOM,False) event.StopPropagation() - + def AtomRefine(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) if not indx: return colLabels = [Atoms.GetColLabelValue(c) for c in range(Atoms.GetNumberCols())] @@ -5116,7 +5121,7 @@ def AtomRefine(event): dlg.Destroy() def AtomModify(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) if not indx: return atomData = data['Atoms'] @@ -5142,7 +5147,7 @@ def AtomModify(event): if dlg.ShowModal() == wx.ID_OK: if dlg.Elem not in ['None']: El = dlg.Elem.strip() - for r in indx: + for r in indx: if not Atoms.IsReadOnly(r,0): #not if in RB! atomData[r][cid] = El if len(El) in [2,4]: @@ -5157,7 +5162,7 @@ def AtomModify(event): FillAtomsGrid(Atoms) dlg.Destroy() elif parm in ['Name',]: - dlg = wx.MessageDialog(G2frame,'Do you really want to rename the selected atoms?','Rename', + dlg = wx.MessageDialog(G2frame,'Do you really want to rename the selected atoms?','Rename', wx.YES_NO | wx.ICON_QUESTION) try: result = dlg.ShowModal() @@ -5179,7 +5184,7 @@ def AtomModify(event): if dlg.ShowModal() == wx.ID_OK: sel = dlg.GetSelection() parm = choices[sel][0] - for r in indx: + for r in indx: if Atoms.IsReadOnly(r,0): #not if in RB! continue atomData[r][cid] = parm @@ -5198,8 +5203,8 @@ def AtomModify(event): dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for '+parm,val,limits) if dlg.ShowModal() == wx.ID_OK: parm = dlg.GetValue() - for r in indx: - if not Atoms.IsReadOnly(r,0): #not if in RB! + for r in indx: +# if not Atoms.IsReadOnly(r,0): #not if in RB! atomData[r][cid] = parm SetupGeneral() FillAtomsGrid(Atoms) @@ -5210,7 +5215,7 @@ def AtomModify(event): dlg = G2G.SingleFloatDialog(G2frame,'Convert Uiso to Uij','Enter default for Uiso'+parm,val,limits) if dlg.ShowModal() == wx.ID_OK: parm = dlg.GetValue() - for r in indx: + for r in indx: if not Atoms.IsReadOnly(r,0): #not if in RB! atomData[r][ci] = 'A' sytsym = atomData[r][cs] @@ -5218,18 +5223,18 @@ def AtomModify(event): if atomData[r][ci+1] > 0.: atomData[r][ci+2:ci+8] = atomData[r][ci+1]*np.array(CSI[3]) else: - atomData[r][ci+2:ci+8] = parm*np.array(CSI[3]) + atomData[r][ci+2:ci+8] = parm*np.array(CSI[3]) SetupGeneral() FillAtomsGrid(Atoms) dlg.Destroy() - + elif parm in ['x','y','z']: limits = [-1.,1.] val = 0. dlg = G2G.SingleFloatDialog(G2frame,'Atom shift','Enter shift for '+parm,val,limits) if dlg.ShowModal() == wx.ID_OK: parm = dlg.GetValue() - for r in indx: + for r in indx: if not Atoms.IsReadOnly(r,0): #not if in RB! atomData[r][cid] += parm SetupGeneral() @@ -5241,7 +5246,7 @@ def AtomModify(event): dlg = G2G.SingleFloatDialog(G2frame,'Atom moment','Enter new value for '+parm,val,limits) if dlg.ShowModal() == wx.ID_OK: parm = dlg.GetValue() - for r in indx: + for r in indx: if not Atoms.IsReadOnly(r,0): #not if in RB! atomData[r][cid] = parm SetupGeneral() @@ -5253,7 +5258,7 @@ def AtomModify(event): G2plt.PlotStructure(G2frame,data) def AtomTransform(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) if not indx: return generalData = data['General'] @@ -5314,7 +5319,7 @@ def AtomTransform(event): data['Drawing']['Atoms'] = [] UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + # def AtomRotate(event): # '''Currently not used - Bind commented out below # ''' @@ -5323,7 +5328,7 @@ def AtomTransform(event): # 'xz':np.array([[i,0,j] for i in range(3) for j in range(3)])-np.array([1,1,0]), # 'yz':np.array([[0,i,j] for i in range(3) for j in range(3)])-np.array([1,1,0]), # 'xyz':np.array([[i,j,k] for i in range(3) for j in range(3) for k in range(3)])-np.array([1,1,1])} -# cx,ct,cs,ci = G2mth.getAtomPtrs(data) +# cx,ct,cs,ci = G2mth.getAtomPtrs(data) # indx = getAtomSelections(Atoms,ct-1) # if indx: # generalData = data['General'] @@ -5350,7 +5355,7 @@ def AtomTransform(event): # atomData[ind][cx:cx+3] = XYZ # for unit in Unit: # XYZ = np.copy(np.array(atomData[ind][cx:cx+3])) -# XYZ += unit +# XYZ += unit # XYZ -= T # XYZ = np.inner(A,XYZ) #to Cartesian # XYZ = np.inner(M,XYZ) #rotate @@ -5368,7 +5373,7 @@ def AtomTransform(event): # G2frame.ErrorDialog('Select atom',"select one or more atoms then redo") def CollectAtoms(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) Ind = getAtomSelections(Atoms,ct-1) if Ind: choice = ['x=0','y=0','z=0','origin','center'] @@ -5384,11 +5389,11 @@ def CollectAtoms(event): wx.EndBusyCursor() Atoms.ClearSelection() data['Drawing']['Atoms'] = [] - OnReloadDrawAtoms(event) + OnReloadDrawAtoms(event) FillAtomsGrid(Atoms) - - def MakeMolecule(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + + def MakeMolecule(event): + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) DisAglCtls = {} if indx is not None and len(indx) == 1: @@ -5407,11 +5412,11 @@ def MakeMolecule(event): result = G2mth.FindMolecule(indx[0],generalData,atomData) if 'str' in str(type(result)): G2frame.ErrorDialog('Assemble molecule',result) - else: + else: data['Atoms'] = result Atoms.ClearSelection() data['Drawing']['Atoms'] = [] - OnReloadDrawAtoms(event) + OnReloadDrawAtoms(event) FillAtomsGrid(Atoms) # G2frame.ErrorDialog('Distance/Angle calculation','try again but do "Reset" to fill in missing atom types') else: @@ -5427,7 +5432,7 @@ def OnDensity(event): msg += f'\n {typ}: {num}' print(msg) G2G.G2MessageBox(G2frame,msg,'Density') - + def OnSetAll(event): 'set all atoms in table as selected' for row in range(Atoms.GetNumberRows()): @@ -5444,7 +5449,7 @@ def OnSetbyList(event): indx = dlg.GetSelections() dlg.Destroy() if len(indx) == 0: return - Atoms.ClearSelection() + Atoms.ClearSelection() for row in indx: Atoms.SelectRow(row,True) G2plt.PlotStructure(G2frame,data) @@ -5458,23 +5463,23 @@ def SetAtomsViewPoint(event): pos += data['Atoms'][i][cx:cx+3] data['Drawing']['viewPoint'] = [list(pos/len(indx)),[indx[0],0]] G2plt.PlotStructure(G2frame,data) - + def OnDistAnglePrt(event): - 'save distances and angles to a file' + 'save distances and angles to a file' fp = open(os.path.abspath(os.path.splitext(G2frame.GSASprojectfile)[0]+'.disagl'),'w') OnDistAngle(event,fp=fp) fp.close() - + def OnDistAngleHist(event): OnDistAngle(event,hist=True) - + def OnDistAngle(event,fp=None,hist=False): '''Compute distances and angles in response to a menu command or may be called by :func:`OnDistAnglePrt` or :func:`OnDistAngleHist`. This calls :func:`GSASIIstrMain.PrintDistAngle` to compute - bond distances & angles (computed in - :func:`GSASIIstrMain.RetDistAngle`) and + bond distances & angles (computed in + :func:`GSASIIstrMain.RetDistAngle`) and then format the results in a convenient way or plot them using G2plt.PlotBarGraph. ''' @@ -5537,7 +5542,7 @@ def OnDistAngle(event,fp=None,hist=False): DisAglData['covData'] = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Covariance')) try: if hist: - pgbar = wx.ProgressDialog('Distance Angle calculation','Atoms done=',len(Oxyz)+1, + pgbar = wx.ProgressDialog('Distance Angle calculation','Atoms done=',len(Oxyz)+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(DisAglCtls,DisAglData,pgbar) pgbar.Destroy() @@ -5560,23 +5565,25 @@ def OnDistAngle(event,fp=None,hist=False): #reload(G2mth) #print('reloading G2stMn & G2mth') G2stMn.PrintDistAngle(DisAglCtls,DisAglData,fp) - else: + else: G2stMn.PrintDistAngle(DisAglCtls,DisAglData) except KeyError: # inside DistAngle for missing atom types in DisAglCtls G2frame.ErrorDialog('Distance/Angle calculation','try again but do "Reset" to fill in missing atom types') - + def OnFracSplit(event): - 'Split atom frac accordintg to atom type & refined site fraction' - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + 'Split atom frac accordintg to atom type & refined site fraction' + cx,ct,cs,ci = G2mth.getAtomPtrs(data) indx = getAtomSelections(Atoms,ct-1) if indx: + B2 = {} atomData = data['Atoms'] PE = G2elemGUI.PickElement(G2frame,oneOnly=True) if PE.ShowModal() == wx.ID_OK: Atype2 = PE.Elem.strip() AtomInfo2 = G2elem.GetAtomInfo(Atype2) FF2 = G2elem.GetFFtable([Atype2])[Atype2] - B2 = AtomInfo2['Isotopes']['Nat. Abund.']['SL'][0] + for iso in AtomInfo2['Isotopes']: + B2[iso] = AtomInfo2['Isotopes'][iso]['SL'][0] PE.Destroy() SQ = 0.0 PE2 = G2G.SingleFloatDialog(G2frame,'form factor','Enter sinth/lam for atom frac split',SQ) @@ -5586,33 +5593,45 @@ def OnFracSplit(event): ff2 = G2elem.ScatFac(FF2,SQ**2) print(' X-ray site fractions for sin(th)/lam = %.3f'%SQ) for ind in indx: - Aname1 = atomData[ind][ct-1] - Atype1 = G2elem.FixValence(atomData[ind][ct]) - Afrac = atomData[ind][cx+3] - Amult = float(atomData[ind][cx+5]) - AtomInfo1 = G2elem.GetAtomInfo(Atype1) - if Atype1 == Atype2: - print('ERROR - 2nd atom type must be different from selected atom') - continue - FF1 = G2elem.GetFFtable([Atype1])[Atype1] - ff1 = G2elem.ScatFac(FF1,SQ**2) - B1 = AtomInfo1['Isotopes']['Nat. Abund.']['SL'][0] + Aname1 = atomData[ind][ct-1] + Atype1 = G2elem.FixValence(atomData[ind][ct]) + if Atype1 == 'Q': + PE = G2elemGUI.PickElement(G2frame,oneOnly=False) + if PE.ShowModal() == wx.ID_OK: + Atype1 = PE.Elem.strip() + PE.Destroy() + Afrac = atomData[ind][cx+3] + Amult = float(atomData[ind][cx+5]) + AtomInfo1 = G2elem.GetAtomInfo(Atype1) + if Atype1 == Atype2: + print('ERROR - 2nd atom type must be different from selected atom') + continue + FF1 = G2elem.GetFFtable([Atype1])[Atype1] + ff1 = G2elem.ScatFac(FF1,SQ**2) + if ff1 != ff2: frac1 = (ff1*Afrac-ff2)/(ff1-ff2) - bfrac1 = (B1*Afrac-B2)/(B1-B2) print(' For %s: X-ray based site fractions %s = %.3f, %.3f/cell; %s = %.3f, %.3f/cell' \ - %(Aname1,Atype1,frac1,frac1*Amult,Atype2,(1.-frac1),(1.-frac1)*Amult)) - print(' neutron based site fractions %s = %.3f, %.3f/cell; %s = %.3f, %.3f/cell\n' \ - %(Atype1,bfrac1,bfrac1*Amult,Atype2,(1.-bfrac1),(1.-bfrac1)*Amult)) - + %(Aname1,Atype1,frac1,frac1*Amult,Atype2,(1.-frac1),(1.-frac1)*Amult)) + + B1 = AtomInfo1['Isotopes']['Nat. Abund.']['SL'][0] + for iso in B2: + if B1 != B2[iso]: + bfrac1 = (B1*Afrac-B2[iso])/(B1-B2[iso]) + atype2 = Atype2 + if 'nat' not in iso: + atype2 += '_%s'%iso + print(' neutron based site fractions %s = %.3f, %.3f/cell; %s = %.3f, %.3f/cell' \ + %(Atype1,bfrac1,bfrac1*Amult,atype2,(1.-bfrac1),(1.-bfrac1)*Amult)) + def OnValidProtein(event): - + def pickHandler(resName): drawData = data['Drawing'] resid = resIDs[resName] drawData['viewPoint'][0] = atomData[AtLookUp[resid]][cx:cx+3] UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + atomData = data['Atoms'] cx,ct,cs,cia = data['General']['AtomPtrs'] AtLookUp = G2mth.FillAtomLookUp(atomData,cia+8) @@ -5637,8 +5656,63 @@ def OnShowIsoModes(event): #import imp #imp.reload(G2cnstG) G2cnstG.ShowIsoModes(G2frame,data['General']['Name']) - + + def OnReplacePhase(event): + 'Called to replace the current phase with a new phase from a file' + reqrdr = G2frame.dataWindow.ReplaceMenuId.get(event.GetId()) + rdlist = G2frame.OnImportGeneric(reqrdr, + G2frame.ImportPhaseReaderlist,'phase') + if len(rdlist) == 0: return + # rdlist is only expected to have one entry + rd = rdlist[0] + # Strict = True + # if 'rmc6f' in rd.readfilename: + # Strict = False + # idx = -1 + phsnam = data['General']['Name'] + # clear out stuff that in general should not be + # transferred from one phase to another + resId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints') + Restraints = G2frame.GPXtree.GetItemPyData(resId) + # resId = G2gd.GetGPXtreeItemId(G2frame,resId,phsnam) + if phsnam in Restraints: + del Restraints[phsnam] + consId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') + Constraints = G2frame.GPXtree.GetItemPyData(consId) + # TODO: would be cleaner to just delete constraints w/phase data['pId'] + Constraints['Phase'] = [] + del data['magPhases'] + data['MCSA'] = {'Models': + [{'Type': 'MD', 'Coef': [1.0, False, [0.8, 1.2]], 'axis': [0, 0, 1]}], + 'Results': [], 'AtInfo': {}} + data['RMC'] = {'RMCProfile': {}, 'fullrmc': {}, 'PDFfit': {}} + data['ISODISTORT'] = {} + data['Deformations'] = {} + # copy over most of the data from the reader object + # but keep original name, pId & ranId + for key in ['General', 'Atoms', 'Drawing', 'Histograms', 'Pawley ref', 'RBModels']: + data[key] = rd.Phase[key] + # TODO: want to keep Histograms rather than wipe it out? + # If so, need to think about Dij & microstrain: + # UseList = newPhase['Histograms'] + # for hist in UseList: + # # reset Dij & microstrain terms where # of terms changes + # if len(UseList[hist]['Mustrain'][4]) != NShkl: + # UseList[hist]['Mustrain'][4:6] = [NShkl*[0.01,],NShkl*[False,]] + # if len(UseList[hist]['HStrain'][0]) != NDij: + # UseList[hist]['HStrain'] = [NDij*[0.0,],NDij*[False,]] + + # restore existing phase name + newname = rd.Phase['General']['Name'] + data['General']['Name'] = phsnam + # rename phase to new name from file + renamePhaseNameTop(G2frame,data,G2frame.PickId, data['General'],newname) + # force a reload of current tree item + G2frame.PickIdText = [] + wx.CallAfter(G2gd.SelectDataTreeItem,G2frame,G2frame.PickId) + def OnReImport(event): + 'Called to replace the coordinates with "original" values from a file' generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] reqrdr = G2frame.dataWindow.ReImportMenuId.get(event.GetId()) @@ -5684,40 +5758,34 @@ def OnReImport(event): finally: dlg.Destroy() SetupGeneral() - OnReloadDrawAtoms(event) + OnReloadDrawAtoms(event) wx.CallAfter(FillAtomsGrid,Atoms) - -#### Dysnomia (MEM) Data page ############################################################################## + +#### Dysnomia (MEM) Data page ############################################################################## def UpdateDysnomia(): - ''' Present the controls for running Dysnomia + ''' Present the controls for running Dysnomia ''' def OnOptMeth(event): DysData['Optimize'] = OptMeth.GetValue() wx.CallAfter(UpdateDysnomia) - + def OnZmult(event): DysData['Lagrange'][0] = Zmult.GetValue() wx.CallAfter(UpdateDysnomia) - + def OnStart(event): DysData['DenStart'] = Start.GetValue() - + def OnPrior(event): DysData['prior'] = Prior.GetValue() if DysData['prior'] == 'last run': if os.path.isfile(pName+'_prior.pgrid'): os.remove(pName+'_prior.pgrid') os.rename(pName+'.pgrid',pName+'_prior.pgrid') - + def OnFileCheck(event): DysData['clear'] = fileCheck.GetValue() - - cite = ''' For use of Dysnomia, please cite: - Dysnomia, a computer program for maximum-entropy method (MEM) - analysis and its performance in the MEM-based pattern fitting, - K. Moma, T. Ikeda, A.A. Belik & F. Izumi, Powder Diffr. 2013, 28, 184-193. - doi: https://doi.org/10.1017/S088571561300002X - ''' + generalData = data['General'] pName = generalData['Name'].replace(' ','_') Map = generalData['Map'] @@ -5726,7 +5794,7 @@ def OnFileCheck(event): if not pId: wx.MessageBox('You must prepare a fourier map before running Dysnomia','Dysnomia Error', style=wx.ICON_ERROR) - return + return reflSets = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,pId,'Reflection Lists')) reflData = reflSets[generalData['Name']]['RefList'] refDmin = reflData[-1][4] @@ -5752,8 +5820,10 @@ def OnFileCheck(event): topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) wx.CallAfter(G2frame.dataWindow.SetDataSize) - - mainSizer.Add(wx.StaticText(MEMData,label=cite)) + + mainSizer.Add(wx.StaticText(MEMData,label= + ' For use of Dysnomia, please cite:\n'+ + G2G.GetCite('Dysnomia',wrap=60,indent=5))) lineSizer = wx.BoxSizer(wx.HORIZONTAL) lineSizer.Add(wx.StaticText(MEMData,label=' MEM Optimization method: '),0,WACV) OptMeth = wx.ComboBox(MEMData,-1,value=DysData['Optimize'],choices=['ZSPA','L-BFGS'], @@ -5788,7 +5858,7 @@ def OnFileCheck(event): Esizer.Add(wx.StaticText(MEMData,label=' Minimum d-spacing for generated reflections: '),0,WACV) Esizer.Add(Dmin,0,WACV) mainSizer.Add(Esizer) - + if os.path.isfile(pName+'.pgrid'): PriorSizer = wx.BoxSizer(wx.HORIZONTAL) PriorSizer.Add(wx.StaticText(MEMData,label=' Start from densities: '),0,WACV) @@ -5805,7 +5875,7 @@ def OnFileCheck(event): else: DysData['DenStart'] = 'uniform' DysData['prior'] = 'uniform' - + Csizer = wx.BoxSizer(wx.HORIZONTAL) Csizer.Add(wx.StaticText(MEMData,label=' Maximum number of cycles: '),0,WACV) Cyc = G2G.ValidatedTxtCtrl(MEMData,DysData,'Ncyc',xmin=0,xmax=10000,size=(50,20)) @@ -5819,19 +5889,19 @@ def OnFileCheck(event): def OnLoadDysnomia(event): print('Load MEM - might not be implemented') - + def OnSaveDysnomia(event): print('Save MEM - might not be implemented') def OnRunDysnomia(event): - + path2GSAS2 = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) DYSNOMIA = os.path.join(path2GSAS2,'Dysnomia','Dysnomia64.exe') DysData = data['Dysnomia'] - + if not os.path.exists(DYSNOMIA): - wx.MessageBox(''' Dysnomia is not installed. Please download it from - https://jp-minerals.org/dysnomia/en/ + wx.MessageBox(''' Dysnomia is not installed. Please download it from + https://jp-minerals.org/dysnomia/en/ and install it at.'''+DYSNOMIA, caption='Dysnomia not installed',style=wx.ICON_ERROR) return @@ -5843,13 +5913,13 @@ def OnRunDysnomia(event): if not pId: wx.MessageBox('You must prepare a Fourier map before running Dysnomia','Dysnomia Error', style=wx.ICON_ERROR) - return + return reflSets = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,pId,'Reflection Lists')) reflData = reflSets[generalData['Name']]['RefList'] if 'Type' not in Map: wx.MessageBox('You must prepare a Fourier map before running Dysnomia','Dysnomia Error', style=wx.ICON_ERROR) - return + return Type = Map['Type'] MEMtype = 0 if 'N' in Type: @@ -5865,20 +5935,17 @@ def OnRunDysnomia(event): wx.MessageBox('Non standard space group '+SpGrp+' not permitted in Dysnomia','Dysnomia Error', style=wx.ICON_ERROR) return - cite = ''' For use of Dysnomia, please cite: - Dysnomia, a computer program for maximum-entropy method (MEM) - analysis and its performance in the MEM-based pattern fitting, - K. Moma, T. Ikeda, A.A. Belik & F. Izumi, Powder Diffr. 2013, 28, 184-193. - doi: https://doi.org/10.1017/S088571561300002X''' - wx.MessageBox(cite,caption='Dysnomia (MEM)',style=wx.ICON_INFORMATION) - - print('Run '+DYSNOMIA) + wx.MessageBox(' For use of Dysnomia, please cite:\n\n'+ + G2G.GetCite('Dysnomia'), + caption='Dysnomia (MEM)',style=wx.ICON_INFORMATION) + + print('Run '+DYSNOMIA) subp.call([DYSNOMIA,prfName]) - + DysData['DenStart'] = 'uniform' DysData['prior'] = 'uniform' wx.CallAfter(UpdateDysnomia) - + goon,reflData = G2pwd.MEMupdateReflData(prfName,data,reflData) if goon: reflSets[generalData['Name']]['RefList'] = reflData @@ -5906,9 +5973,9 @@ def UpdateRMC(event=None): def OnRMCselect(event): G2frame.RMCchoice = RMCsel.GetStringSelection() wx.CallLater(100,UpdateRMC) - + def GetAtmChoice(pnl,RMCPdict): - + Indx = {} def OnAtSel(event): Obj = event.GetEventObject() @@ -5930,14 +5997,14 @@ def OnAtSel(event): for pair in [[' %s-%s'%(atSeq[i],atSeq[j]) for j in range(i+1,lenA)] for i in range(lenA)]: BVSpairs += pair RMCPdict['BVS'] = {pairs:[0.0,0.0,0.0,0.0] for pairs in BVSpairs} - wx.CallAfter(UpdateRMC) - + wx.CallAfter(UpdateRMC) + def OnValSel(event): Obj = event.GetEventObject() itype = Indx[Obj.GetId()] - RMCPdict['Oxid'][itype][0] = Obj.GetStringSelection() + RMCPdict['Oxid'][itype][0] = Obj.GetStringSelection() wx.CallAfter(UpdateRMC) - + nTypes = len(RMCPdict['aTypes']) atmChoice = wx.FlexGridSizer(nTypes+1,5,5) atmChoice.Add(wx.StaticText(pnl,label='atom ordering: '),0,WACV) @@ -5948,7 +6015,7 @@ def OnValSel(event): atmSel.Bind(wx.EVT_COMBOBOX,OnAtSel) Indx[atmSel.GetId()] = iType atmChoice.Add(atmSel,0,WACV) - if RMCPdict['useBVS']: + if RMCPdict['useBVS']: atmChoice.Add(wx.StaticText(pnl,label='Valence: '),0,WACV) for itype in range(nTypes): valChoice = atmdata.BVSoxid[RMCPdict['atSeq'][itype]] @@ -5977,7 +6044,7 @@ def OnValSel(event): lbl = '?' atmChoice.Add(wx.StaticText(pnl,label=lbl),0,WACV) return atmChoice - + def GetSwapSizer(RMCPdict): def OnDelSwap(event): @@ -5985,9 +6052,9 @@ def OnDelSwap(event): swap = Indx[Obj.GetId()] del RMCPdict['Swaps'][swap] wx.CallAfter(UpdateRMC) - + Indx = {} - atChoice = RMCPdict['atSeq'] + atChoice = RMCPdict['atSeq'] # if G2frame.RMCchoice == 'fullrmc': # atChoice = atNames swapSizer = wx.FlexGridSizer(6,5,5) @@ -6007,7 +6074,7 @@ def OnDelSwap(event): Indx[delBtn.GetId()] = ifx swapSizer.Add(delBtn,0,WACV) return swapSizer - + def GetPairSizer(pnl,RMCPdict): pairSizer = wx.FlexGridSizer(len(RMCPdict['Pairs'])+1,5,5) pairSizer.Add((5,5),0) @@ -6027,17 +6094,17 @@ def GetPairSizer(pnl,RMCPdict): for pair in RMCPdict['Pairs']: pairSizer.Add(G2G.ValidatedTxtCtrl(pnl,RMCPdict['Pairs'][pair],2,xmin=0.,xmax=10.,size=(50,25)),0,WACV) return pairSizer - + def GetMetaSizer(RMCPdict,metalist): metaSizer = wx.FlexGridSizer(0,2,5,5) for item in metalist: metaSizer.Add(wx.StaticText(G2frame.FRMC,label=' Metadata item: '+item+' '),0,WACV) metaSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict['metadata'],item),0,WACV) return metaSizer - + def SetRestart(invalid,value,tc): RMCPdict['ReStart'] = [True,True] - + def GetSuperSizer(RMCPdict,Xmax): superSizer = wx.BoxSizer(wx.HORIZONTAL) axes = ['X','Y','Z'] @@ -6046,9 +6113,9 @@ def GetSuperSizer(RMCPdict,Xmax): superSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict['SuperCell'], i,xmin=1,xmax=Xmax,size=(50,25),OnLeave=SetRestart),0,WACV) return superSizer - + def FileSizer(RMCPdict): - + def OnFileSel(event): Obj = event.GetEventObject() fil = Indx[Obj.GetId()] @@ -6060,7 +6127,9 @@ def OnFileSel(event): if os.path.exists(fName): # is there a file by this name in the current directory? RMCPdict['files'][fil][0] = fName else: # nope, copy it - disfile.copy_file(dlg.GetPath(),os.path.join(G2frame.LastGPXdir,fName)) + # TODO: is G2frame.LastGPXdir the right choice here or + # do I want the current working directory (same?) + shutil.copy(dlg.GetPath(), os.path.join(G2frame.LastGPXdir,fName)) if not os.path.exists(fName): # sanity check print(f'Error: file {fName} not found in .gpx directory ({G2frame.LastGPXdir})') return @@ -6083,14 +6152,14 @@ def OnFileSel(event): RMCPdict[name]['Fitrange'][1] = np.max(XY.T[0]) else: dlg.Destroy() - + wx.CallAfter(UpdateRMC) - + def OnFileFormat(event): Obj = event.GetEventObject() fil = Indx[Obj.GetId()] RMCPdict['files'][fil][3] = Obj.GetStringSelection() - + def OnPlotBtn(event): Obj = event.GetEventObject() fil = Indx[Obj.GetId()] @@ -6112,37 +6181,37 @@ def OnPlotBtn(event): G2plt.PlotXY(G2frame,[XY.T[:2],],labelX=Xlab, labelY=fileItem[2],newPlot=True,Title=fileItem[0], lines=True) - + def OnCorrChk(event): Obj = event.GetEventObject() fil = Indx[Obj.GetId()] RMCPdict['files'][fil][3] = not RMCPdict['files'][fil][3] - + def OnDelBtn(event): Obj = event.GetEventObject() fil = Indx[Obj.GetId()] RMCPdict['files'][fil][0] = 'Select' RMCPdict['ReStart'][0] = True wx.CallAfter(UpdateRMC) - + def OnRef(event): Obj = event.GetEventObject() name,item = Indx[Obj.GetId()] RMCPdict[name][item][1] = not RMCPdict[name][item][1] - + def OnRefSel(event): RMCPdict['refinement'] = reftype.GetStringSelection() wx.CallLater(100,UpdateRMC) - + def OnDataSel(event): RMCPdict['SeqDataType'] = dataType.GetStringSelection() - + def OnSeqCopy(event): RMCPdict['SeqCopy'] = not RMCPdict['SeqCopy'] def OnSeqReverse(event): RMCPdict['SeqReverse'] = not RMCPdict['SeqReverse'] - + # --- FileSizer starts here Indx = {} mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -6236,9 +6305,9 @@ def OnSeqReverse(event): fileSizer.Add((-1,-1),0) mainSizer.Add(fileSizer,0) return mainSizer - + if G2frame.RMCchoice == 'PDFfit' and RMCPdict['refinement'] == 'sequential': - + def OnAddPDF(event): ''' Add PDF G(r)s while maintanining original sequence ''' @@ -6260,7 +6329,7 @@ def OnAddPDF(event): RMCPdict['seqfiles'].append([PDFnames[item],data]) dlg.Destroy() wx.CallAfter(UpdateRMC) - + def OnDelPDF(event): usedList = [item[0] for item in RMCPdict['seqfiles']] dlg = G2G.G2MultiChoiceDialog(G2frame.FRMC,'Delete PDF dataset', @@ -6272,7 +6341,7 @@ def OnDelPDF(event): del RMCPdict['seqfiles'][item] dlg.Destroy() wx.CallAfter(UpdateRMC) - + def OnSetColVal(event): parms = {'Rmin':[0.01,5.0],'Rmax':[5.,30.],'dscale':[0.5,2.0], 'qdamp':[0.0,0.5],'qbroad':[0.0,0.1],'Temp':300} @@ -6307,7 +6376,7 @@ def OnSetColVal(event): else: for row in range(seqGrid.GetNumberRows()): RMCPdict['seqfiles'][row][1]['Fitrange'][c] = value wx.CallAfter(UpdateRMC) - + def OnSetVal(event): r,c= event.GetRow(),event.GetCol() if c >= 0: @@ -6322,10 +6391,10 @@ def OnSetVal(event): RMCPdict['seqfiles'][r][1][parm] = float(seqGrid.GetCellValue(r,c)) else: RMCPdict['seqfiles'][r][1]['Fitrange'][c] = float(seqGrid.GetCellValue(r,c)) - + topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(wx.StaticText(G2frame.FRMC,label=' Select data for processing: ')) - mainSizer.Add(topSizer) + mainSizer.Add(topSizer) G2frame.GetStatusBar().SetStatusText('NB: All PDFs used in sequential PDFfit must be the same type ("X" or "N") - there is no check',1) if 'seqfiles' not in RMCPdict: RMCPdict['seqfiles'] = [] @@ -6355,7 +6424,7 @@ def OnSetVal(event): seqGrid.Bind(wg.EVT_GRID_CELL_CHANGED, OnSetVal) mainSizer.Add(seqGrid) return mainSizer - + # begin FileSizer topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(wx.StaticText(G2frame.FRMC,label=' Select data for processing: ')) @@ -6377,7 +6446,7 @@ def OnSetVal(event): fileSizer.Add(filSel,0,WACV) nform = 3 Name = 'Ndata' - if 'Xray' in fil: + if 'Xray' in fil: nform = 1 Name = 'Xdata' if Rfile and os.path.exists(Rfile): #incase .gpx file is moved away from G(R), F(Q), etc. files @@ -6426,18 +6495,18 @@ def OnSetVal(event): Indx[qbroadref.GetId()] = [Name,'qbroad'] qbroadref.Bind(wx.EVT_CHECKBOX,OnRef) fileSizer.Add(qbroadref,0,WACV) - + mainSizer.Add(fileSizer,0) - + return mainSizer - + def fullrmcSizer(RMCPdict): mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(wx.StaticText(G2frame.FRMC,label= -'''* "Atomic Stochastic Modeling & Optimization with fullrmc", B. Aoun, J. Appl. Cryst. 2022, 55(6) 1664-1676, +'''* "Atomic Stochastic Modeling & Optimization with fullrmc", B. Aoun, J. Appl. Cryst. 2022, 55(6) 1664-1676, DOI: 10.1107/S1600576722008536; -* "Fullrmc, a Rigid Body Reverse Monte Carlo Modeling Package Enabled with Machine Learning and Artificial - Intelligence", B. Aoun, Jour. Comp. Chem. (2016), 37, 1102-1111. DOI: 10.1002/jcc.24304; +* "Fullrmc, a Rigid Body Reverse Monte Carlo Modeling Package Enabled with Machine Learning and Artificial + Intelligence", B. Aoun, Jour. Comp. Chem. (2016), 37, 1102-1111. DOI: 10.1002/jcc.24304; * www.fullrmc.com ''')) # if G2pwd.findfullrmc() is None: @@ -6478,7 +6547,7 @@ def fullrmcSizer(RMCPdict): for key,val in {'SuperCell':[1,1,1],'Box':[10.,10.,10.],'ReStart':[False,False],'Cycles':1, 'Swaps':[],'useBVS':False,'FitScale':False,'AveCN':[],'FxCN':[], 'min Contact':1.5,'periodicBound':True}.items(): - RMCPdict[key] = RMCPdict.get(key,val) + RMCPdict[key] = RMCPdict.get(key,val) def GetSuperSizer(): def ShowRmax(*args,**kwargs): @@ -6506,14 +6575,14 @@ def GetBoxSizer(): boxSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict['Box'], i,xmin=10.,xmax=50.,size=(50,25)),0,WACV) return boxSizer - + def OnReStart(event): RMCPdict['ReStart'][0] = not RMCPdict['ReStart'][0] - + def OnAddSwap(event): RMCPdict['Swaps'].append(['','',0.0,]) wx.CallAfter(UpdateRMC) - + def OnPdbButton(event): dlg = wx.FileDialog(G2frame.FRMC, 'Choose molecule pdb file',G2frame.LastGPXdir, style=wx.FD_OPEN ,wildcard='PDB file(*.pdb)|*.pdb') @@ -6521,31 +6590,31 @@ def OnPdbButton(event): fpath,fName = os.path.split(dlg.GetPath()) RMCPdict['moleculePdb'] = fName pdbButton.SetLabel(fName) - + def OnAddAngle(event): RMCPdict['Angles'].append(['','','',0.,0.,0.,0.]) wx.CallAfter(UpdateRMC) - + # def OnAddTorsion(event): # RMCPdict['Torsions'].append(['','','','',0.,0.,0.,0.,0.,0.]) # wx.CallAfter(UpdateRMC) - + def GetAngleSizer(): - + def OnDelAngle(event): Obj = event.GetEventObject() angle = Indx[Obj.GetId()] del RMCPdict['Angles'][angle] wx.CallAfter(UpdateRMC) - + # def OnAngleAtSel(event): # Obj = event.GetEventObject() # angle,i = Indx[Obj.GetId()] # RMCPdict['Angles'][angle][i] = Obj.GetStringSelection() - + def SetRestart1(invalid,value,tc): RMCPdict['ReStart'][1] = True - + Indx = {} atChoice = [atm for atm in RMCPdict['atSeq'] if 'Va' not in atm] angleSizer = wx.GridBagSizer(0,5) @@ -6584,23 +6653,23 @@ def SetRestart1(invalid,value,tc): Indx[delBtn.GetId()] = ifx angleSizer.Add(delBtn,(row,9)) return angleSizer - + # def GetTorsionSizer(): - + # def OnDelTorsion(event): # Obj = event.GetEventObject() # angle = Indx[Obj.GetId()] # del RMCPdict['Torsions'][angle] # wx.CallAfter(UpdateRMC) - + # def OnTorsionAtSel(event): # Obj = event.GetEventObject() # torsion,i = Indx[Obj.GetId()] # RMCPdict['Torsions'][torsion][i] = Obj.GetStringSelection() - + # def SetRestart1(invalid,value,tc): # RMCPdict['ReStart'][1] = True - + # Indx = {} # atChoice = [atm for atm in RMCPdict['atSeq'] if 'Va' not in atm] # torsionSizer = wx.FlexGridSizer(11,5,5) @@ -6618,7 +6687,7 @@ def SetRestart1(invalid,value,tc): # atmSel.Bind(wx.EVT_COMBOBOX,OnTorsionAtSel) # Indx[atmSel.GetId()] = [ifx,i] # torsionSizer.Add(atmSel,0,WACV) - # for i in [4,5,6,7,8,9]: + # for i in [4,5,6,7,8,9]: # torsionSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,torsion,i,xmin=0.,xmax=360.,OnLeave=SetRestart1,size=(50,25)),0,WACV) # return torsionSizer @@ -6628,13 +6697,13 @@ def SetRestart1(invalid,value,tc): # atNames = [atom[ct-1] for atom in atomData] # ifP1 = False # if generalData['SGData']['SpGrp'] == 'P 1': - # ifP1 = True + # ifP1 = True ifBox = False if 'macromolecular' in generalData['Type']: ifBox = True lineSizer = wx.BoxSizer(wx.HORIZONTAL) if ifBox: - lineSizer.Add(wx.StaticText(G2frame.FRMC,label=' Big box dimensions, %s:'%Angstr),0,WACV) + lineSizer.Add(wx.StaticText(G2frame.FRMC,label=' Big box dimensions, %s:'%Angstr),0,WACV) lineSizer.Add(GetBoxSizer(),0,WACV) # elif ifP1: lineSizer.Add(wx.StaticText(G2frame.FRMC,label=' Lattice multipliers:'),0,WACV) @@ -6677,43 +6746,43 @@ def SetRestart1(invalid,value,tc): restart.SetValue(RMCPdict['ReStart'][0]) restart.Bind(wx.EVT_CHECKBOX,OnReStart) mainSizer.Add(resLine,0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(GetAtmChoice(G2frame.FRMC,RMCPdict),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) swapBox = wx.BoxSizer(wx.HORIZONTAL) swapBox.Add(wx.StaticText(G2frame.FRMC,label='Atom swap probabiities: '),0,WACV) swapAdd = wx.Button(G2frame.FRMC,label='Add',style=wx.BU_EXACTFIT) swapAdd.Bind(wx.EVT_BUTTON,OnAddSwap) swapBox.Add(swapAdd,0,WACV) - mainSizer.Add(swapBox,0) + mainSizer.Add(swapBox,0) if len(RMCPdict['Swaps']): - mainSizer.Add(GetSwapSizer(RMCPdict),0) + mainSizer.Add(GetSwapSizer(RMCPdict),0) G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(wx.StaticText(G2frame.FRMC,label='Geometry constraints && restraints'),0) distBox = wx.BoxSizer(wx.HORIZONTAL) distBox.Add(wx.StaticText(G2frame.FRMC,label='Distance constraints'),0,WACV) # weights removed for now - #distBox.Add(wx.StaticText(G2frame.FRMC,label=', distance weight:'),0,WACV) + #distBox.Add(wx.StaticText(G2frame.FRMC,label=', distance weight:'),0,WACV) #distBox.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict,'Bond Weight',xmin=0.,xmax=100.,size=(50,25)),0,WACV) distBox.Add(wx.StaticText(G2frame.FRMC,label=' min contact dist: '),0,WACV) distBox.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict,'min Contact',xmin=0.,xmax=4.,size=(50,25)),0,WACV) - + RMCPdict['useBondConstraints'] = RMCPdict.get('useBondConstraints',True) distBox.Add(wx.StaticText(G2frame.FRMC,label=' Use bond constraints? '),0,WACV) distBox.Add(G2G.G2CheckBox(G2frame.FRMC,'',RMCPdict,'useBondConstraints',OnChange=UpdateRMC), 0,WACV) mainSizer.Add(distBox,0) - + if RMCPdict['useBondConstraints']: mainSizer.Add(GetPairSizer(G2frame.FRMC,RMCPdict),0) mainSizer.Add((-1,10)) angBox = wx.BoxSizer(wx.HORIZONTAL) angBox.Add(wx.StaticText(G2frame.FRMC,label='A-B-C angle restraints'),0,WACV) # weights removed for now - #angBox.Add(wx.StaticText(G2frame.FRMC,label=', angle weight:'),0,WACV) + #angBox.Add(wx.StaticText(G2frame.FRMC,label=', angle weight:'),0,WACV) #angBox.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict,'Angle Weight',xmin=0.,xmax=100.,size=(50,25)),0,WACV) angBox.Add((20,-1)) angAdd = wx.Button(G2frame.FRMC,label='Add',style=wx.BU_EXACTFIT) @@ -6751,13 +6820,13 @@ def GroupEditor(index): mainSizer.Add(grpAdd,0) else: grpBox = wx.BoxSizer(wx.HORIZONTAL) - grpBox.Add(wx.StaticText(G2frame.FRMC,label='Atom Groups: '),0,WACV) + grpBox.Add(wx.StaticText(G2frame.FRMC,label='Atom Groups: '),0,WACV) grpAdd = wx.Button(G2frame.FRMC,label='Add group',style=wx.BU_EXACTFIT) grpAdd.Bind(wx.EVT_BUTTON,OnAddGroup) RMCPdict['GroupMode'] = RMCPdict.get('GroupMode',0) grpBox.Add(grpAdd,0,WACV) grpBox.Add(wx.StaticText(G2frame.FRMC, - label=' Group refinement mode: '),0,WACV) + label=' Group refinement mode: '),0,WACV) grpBox.Add(G2G.EnumSelector(G2frame.FRMC,RMCPdict,'GroupMode', ('Rotate & Translate','Rotate only','Translate only'), [0,1,2]),0,WACV) @@ -6765,7 +6834,7 @@ def GroupEditor(index): for i,g in enumerate(RMCPdict['Groups']): grpBox = wx.BoxSizer(wx.HORIZONTAL) grpBox.Add((20,-1)) - grpBox.Add(wx.StaticText(G2frame.FRMC,label='Group #'+str(i+1)),0,WACV) + grpBox.Add(wx.StaticText(G2frame.FRMC,label='Group #'+str(i+1)),0,WACV) grpBox.Add((4,-1)) grpdel = wx.Button(G2frame.FRMC,label='Del',style=wx.BU_EXACTFIT) grpdel.Bind(wx.EVT_BUTTON,OnDelGroup) @@ -6782,7 +6851,7 @@ def GroupEditor(index): elif i > 0: msg += ', ' msg += str(i) - grpBox.Add(wx.StaticText(G2frame.FRMC,label=msg),0,WACV) + grpBox.Add(wx.StaticText(G2frame.FRMC,label=msg),0,WACV) mainSizer.Add(grpBox,0) RMCPdict['addThermalBroadening'] = RMCPdict.get('addThermalBroadening',False) @@ -6802,9 +6871,9 @@ def GroupEditor(index): xmin=0.0001,xmax=0.25,size=(50,25)),0,WACV) mainSizer.Add(distBox,0) if RMCPdict['addThermalBroadening']: mainSizer.Add((-1,5)) - + # Torsions are difficult to implement. Need to be internal to a unit cell & named with fullrmc - # atom labels. Leave this out, at least for now. + # atom labels. Leave this out, at least for now. # torBox = wx.BoxSizer(wx.HORIZONTAL) # torAdd = wx.Button(G2frame.FRMC,label='Add') # torAdd.Bind(wx.EVT_BUTTON,OnAddTorsion) @@ -6814,13 +6883,13 @@ def GroupEditor(index): # mainSizer.Add(torBox,0) # if len(RMCPdict['Torsions']): # mainSizer.Add(GetTorsionSizer(),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(FileSizer(RMCPdict)) return mainSizer - + def RMCProfileSizer(RMCPdict): - + def CheckAtms(Atypes): newAtm = False for atm in Atypes: @@ -6832,7 +6901,7 @@ def CheckAtms(Atypes): newAtm = True break return newAtm - + mainSizer = wx.BoxSizer(wx.VERTICAL) subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) @@ -6840,10 +6909,10 @@ def CheckAtms(Atypes): subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer) mainSizer.Add((5,5)) - mainSizer.Add(wx.StaticText(G2frame.FRMC,label= -'''"RMCProfile: Reverse Monte Carlo for polycrystalline materials", M.G. Tucker, -D.A. Keen, M.T. Dove, A.L. Goodwin and Q. Hui, Jour. Phys.: Cond. Matter (2007), -19, 335218. doi: https://doi.org/10.1088/0953-8984/19/33/335218''')) + txt = wx.StaticText(G2frame.FRMC,label= + f"Please cite: {G2G.GetCite('RMCProfile')}") + txt.Wrap(500) + mainSizer.Add(txt) mainSizer.Add((5,5)) Atypes = [atype.split('+')[0].split('-')[0] for atype in data['General']['AtomTypes']] aTypes = dict(zip(Atypes,len(Atypes)*[0.10,])) @@ -6861,7 +6930,7 @@ def CheckAtms(Atypes): else: Pairs[pair] = [0.0,0.0,0.0] data['RMC']['RMCProfile'].update({'aTypes':aTypes,'atSeq':atSeq,'Pairs':Pairs,'Oxid':atOxid,}) - + if not data['RMC']['RMCProfile'] or 'metadata' not in RMCPdict: Pairs = {} # for pairs in [[' %s-%s'%(atSeq[i],atSeq[j]) for j in range(i,lenA) if 'Va' not in atSeq[j]] for i in range(lenA) if 'Va' not in atSeq[i]]: @@ -6887,7 +6956,7 @@ def CheckAtms(Atypes): 'runTimes':runTimes,'ReStart':[False,False],'BVS':BVS,'Oxid':atOxid,'useBVS':False,'Swaps':[], 'AveCN':[],'FxCN':[],'Potentials':{'Angles':[],'Angle search':10.,'Stretch':[],'Pairs':Pairs, 'Stretch search':10.,'Pot. Temp.':300.,'useGPU':False,}}) - + # data['RMC']['RMCProfile']['aTypes'] = {aTypes[atype] for atype in aTypes if atype in Atypes} data['RMC']['RMCProfile']['Isotope'] = copy.copy(data['General']['Isotope']) data['RMC']['RMCProfile']['Isotopes'] = copy.deepcopy(data['General']['Isotopes']) @@ -6898,53 +6967,53 @@ def CheckAtms(Atypes): RMCPdict['FitScale'] = False if 'useGPU' not in RMCPdict: RMCPdict['useGPU'] = False - + #end patches - + def OnHisto(event): RMCPdict['histogram'][0] = histo.GetStringSelection() - + def OnSize(event): RMCPdict['UseSampBrd'][0] = samSize.GetValue() - + def OnStrain(event): RMCPdict['UseSampBrd'][1] = strain.GetValue() - + def OnFitScale(event): RMCPdict['FitScale'] = not RMCPdict['FitScale'] def SetRestart(invalid,value,tc): RMCPdict['ReStart'] = [True,True] - + def OnUseBVS(event): RMCPdict['useBVS'] = not RMCPdict['useBVS'] wx.CallAfter(UpdateRMC) - + def OnAddSwap(event): RMCPdict['Swaps'].append(['','',0.0,]) wx.CallAfter(UpdateRMC) - + def OnAddFxCN(event): RMCPdict['FxCN'].append(['','',0.5,2.0,6,1.0,0.00001]) wx.CallAfter(UpdateRMC) - + def OnAddAveCN(event): RMCPdict['AveCN'].append(['','',0.5,2.0,6.,0.00001]) wx.CallAfter(UpdateRMC) - + def OnAddAnglePot(event): RMCPdict['Potentials']['Angles'].append(['','','',0.,0.,0.,0.]) wx.CallAfter(UpdateRMC) - + def OnAddBondPot(event): RMCPdict['Potentials']['Stretch'].append(['','',0.,0.]) wx.CallAfter(UpdateRMC) - + def GetTimeSizer(): - + def OnUseGPU(event): RMCPdict['useGPU'] = not RMCPdict['useGPU'] - + timeSizer = wx.BoxSizer(wx.HORIZONTAL) timeSizer.Add(wx.StaticText(G2frame.FRMC,label=' Total running time (min): '),0,WACV) timeSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict['runTimes'],0,xmin=0.,size=(70,25)),0,WACV) @@ -6955,7 +7024,7 @@ def OnUseGPU(event): usegpu.Bind(wx.EVT_CHECKBOX,OnUseGPU) timeSizer.Add(usegpu,0,WACV) return timeSizer - + # def GetSuperSizer(Xmax): # superSizer = wx.BoxSizer(wx.HORIZONTAL) # axes = ['X','Y','Z'] @@ -6964,9 +7033,9 @@ def OnUseGPU(event): # superSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict['SuperCell'], # i,xmin=1,xmax=xamx,size=(50,25),OnLeave=SetRestart),0,WACV) # return superSizer - + def GetBvsSizer(pnl): - + def OnResetBVS(event): Obj = event.GetEventObject() pair = Indx[Obj.GetId()] @@ -6980,7 +7049,7 @@ def OnResetBVS(event): bvsCh[addr].Window.SetValue('%6.3f'%dist) bvsCh[addr+nId].Window.SetValue('0.37') bvsCh[addr+2*nId].Window.SetValue('3.00') - + bvsSizer = wx.FlexGridSizer(len(RMCPdict['BVS'])+1,5,5) bvsSizer.Add((5,5),0) for pair in RMCPdict['BVS']: @@ -7001,20 +7070,20 @@ def OnResetBVS(event): for pair in RMCPdict['BVS']: bvsSizer.Add(G2G.ValidatedTxtCtrl(pnl,RMCPdict['BVS'][pair],2,xmin=0.,xmax=10.,size=(50,25)),0,WACV) return bvsSizer - + def GetFxcnSizer(): - + def OnDelFxCN(event): Obj = event.GetEventObject() fxCN = Indx[Obj.GetId()] del RMCPdict['FxCN'][fxCN] wx.CallAfter(UpdateRMC) - + def OnFxcnAtSel(event): Obj = event.GetEventObject() ifxCN,i = Indx[Obj.GetId()] RMCPdict['FxCN'][ifxCN][i] = Obj.GetStringSelection() - + fxcnSizer = wx.FlexGridSizer(8,5,5) atChoice = [atm for atm in RMCPdict['atSeq'] if 'Va' not in atm] fxcnLabels = [' ','Atom-1','Atom-2','min dist','max dist','CN','fraction','weight'] @@ -7037,20 +7106,20 @@ def OnFxcnAtSel(event): fxcnSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,fxCN,5,xmin=0.,xmax=1.,size=(50,25)),0,WACV) fxcnSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,fxCN,6,xmin=0.,size=(50,25)),0,WACV) return fxcnSizer - + def GetAvcnSizer(): - + def OnDelAvCN(event): Obj = event.GetEventObject() fxCN = Indx[Obj.GetId()] del RMCPdict['AveCN'][fxCN] wx.CallAfter(UpdateRMC) - + def OnAvcnAtSel(event): Obj = event.GetEventObject() ifxCN,i = Indx[Obj.GetId()] RMCPdict['AveCN'][ifxCN][i] = Obj.GetStringSelection() - + avcnSizer = wx.FlexGridSizer(7,5,5) atChoice = [atm for atm in RMCPdict['atSeq'] if 'Va' not in atm] fxcnLabels = [' ','Atom-1','Atom-2','min dist','max dist','CN','weight'] @@ -7072,23 +7141,23 @@ def OnAvcnAtSel(event): avcnSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,fxCN,4,xmin=1.,xmax=12.,size=(50,25)),0,WACV) avcnSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,fxCN,5,xmin=0.,size=(50,25)),0,WACV) return avcnSizer - + def GetAngleSizer(): - + def OnDelAngle(event): Obj = event.GetEventObject() angle = Indx[Obj.GetId()] del RMCPdict['Potentials']['Angles'][angle] wx.CallAfter(UpdateRMC) - + def OnAngleAtSel(event): Obj = event.GetEventObject() angle,i = Indx[Obj.GetId()] RMCPdict['Potentials']['Angles'][angle][i] = Obj.GetStringSelection() - + def SetRestart1(invalid,value,tc): RMCPdict['ReStart'][1] = True - + atChoice = [atm for atm in RMCPdict['atSeq'] if 'Va' not in atm] angleSizer = wx.FlexGridSizer(8,5,5) fxcnLabels = [' ','Atom-A','Atom-B','Atom-C',' ABC angle','AB dist','BC dist','potential'] @@ -7110,23 +7179,23 @@ def SetRestart1(invalid,value,tc): angleSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,angle,5,xmin=0.5,xmax=5.,OnLeave=SetRestart1,size=(50,25)),0,WACV) angleSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,angle,6,xmin=0.,OnLeave=SetRestart1,size=(50,25)),0,WACV) return angleSizer - + def GetBondSizer(): - + def OnDelBond(event): Obj = event.GetEventObject() bond = Indx[Obj.GetId()] del RMCPdict['Potentials']['Stretch'][bond] wx.CallAfter(UpdateRMC) - + def OnBondAtSel(event): Obj = event.GetEventObject() bond,i = Indx[Obj.GetId()] RMCPdict['Potentials']['Stretch'][bond][i] = Obj.GetStringSelection() - + def SetRestart1(invalid,value,tc): RMCPdict['ReStart'][1] = True - + atChoice = [atm for atm in RMCPdict['atSeq'] if 'Va' not in atm] bondSizer = wx.FlexGridSizer(5,5,5) fxcnLabels = [' ','Atom-A','Atom-B',' AB dist','potential'] @@ -7151,38 +7220,38 @@ def SetRestart1(invalid,value,tc): mainSizer.Add(wx.StaticText(G2frame.FRMC,label=' Enter metadata items:'),0) mainSizer.Add(GetMetaSizer(RMCPdict,['title','owner','material','phase','comment','source',]),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(GetTimeSizer(),0) - + mainSizer.Add(wx.StaticText(G2frame.FRMC,label=' Lattice multipliers; if changed will force reset of atom positions:'),0) mainSizer.Add(GetSuperSizer(RMCPdict,20),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) - + mSizer = wx.BoxSizer(wx.VERTICAL) mSizer.Add(wx.StaticText(G2frame.FRMC,label='Enter atom settings'),0) mSizer.Add(GetAtmChoice(G2frame.FRMC,RMCPdict),0) mSizer.Add(wx.StaticText(G2frame.FRMC,label=' N.B.: be sure to set cations first && anions last in atom ordering')) mainSizer.Add(mSizer) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) swapBox = wx.BoxSizer(wx.HORIZONTAL) swapAdd = wx.Button(G2frame.FRMC,label='Add') swapAdd.Bind(wx.EVT_BUTTON,OnAddSwap) swapBox.Add(swapAdd,0,WACV) swapBox.Add(wx.StaticText(G2frame.FRMC,label=' Atom swap probabilities: '),0,WACV) - mainSizer.Add(swapBox,0) + mainSizer.Add(swapBox,0) if len(RMCPdict['Swaps']): - mainSizer.Add(GetSwapSizer(RMCPdict),0) - + mainSizer.Add(GetSwapSizer(RMCPdict),0) + G2G.HorizontalLine(mainSizer,G2frame.FRMC) - + mSizer = wx.BoxSizer(wx.VERTICAL) mSizer.Add(wx.StaticText(G2frame.FRMC,label='Enter constraints && restraints via minimum && maximum distances for atom pairs:'),0) mSizer.Add(GetPairSizer(G2frame.FRMC,RMCPdict),0) mainSizer.Add(mSizer) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) useBVS = wx.CheckBox(G2frame.FRMC,label=' Use bond valence sum restraints for (set to 0 for non-bonded ones):') useBVS.SetValue(RMCPdict.get('useBVS',False)) @@ -7192,17 +7261,17 @@ def SetRestart1(invalid,value,tc): mSizer = wx.BoxSizer(wx.VERTICAL) mSizer.Add(GetBvsSizer(G2frame.FRMC),0) mainSizer.Add(mSizer) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) fxcnBox = wx.BoxSizer(wx.HORIZONTAL) fxcnAdd = wx.Button(G2frame.FRMC,label='Add') fxcnAdd.Bind(wx.EVT_BUTTON,OnAddFxCN) fxcnBox.Add(fxcnAdd,0,WACV) fxcnBox.Add(wx.StaticText(G2frame.FRMC,label=' Fixed coordination number restraint: '),0,WACV) - mainSizer.Add(fxcnBox,0) + mainSizer.Add(fxcnBox,0) if len(RMCPdict['FxCN']): - mainSizer.Add(GetFxcnSizer(),0) - + mainSizer.Add(GetFxcnSizer(),0) + G2G.HorizontalLine(mainSizer,G2frame.FRMC) avcnBox = wx.BoxSizer(wx.HORIZONTAL) avcnAdd = wx.Button(G2frame.FRMC,label='Add') @@ -7212,7 +7281,7 @@ def SetRestart1(invalid,value,tc): mainSizer.Add(avcnBox,0) if len(RMCPdict['AveCN']): mainSizer.Add(GetAvcnSizer(),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) pottempBox = wx.BoxSizer(wx.HORIZONTAL) pottempBox.Add(wx.StaticText(G2frame.FRMC,label=' Potential temperature (K): '),0,WACV) @@ -7237,7 +7306,7 @@ def SetRestart1(invalid,value,tc): mainSizer.Add(angpotBox,0) if len(RMCPdict['Potentials']['Angles']): mainSizer.Add(GetAngleSizer(),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(wx.StaticText(G2frame.FRMC,label=' Select data:'),0) histograms = data['Histograms'] @@ -7253,7 +7322,7 @@ def SetRestart1(invalid,value,tc): histoSizer.Add(wx.StaticText(G2frame.FRMC,label=' Weight '),0,WACV) histoSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict['histogram'],1,xmin=0.,xmax=10000.,size=(50,25)),0,WACV) mainSizer.Add(histoSizer,0) - + samSizer = wx.BoxSizer(wx.HORIZONTAL) samSize = wx.CheckBox(G2frame.FRMC,label=' Use size broadening?') samSize.SetValue(RMCPdict['UseSampBrd'][0]) @@ -7271,27 +7340,27 @@ def SetRestart1(invalid,value,tc): mainSizer.Add(FileSizer(RMCPdict)) return mainSizer - + def PDFfitSizer(data): - + mainSizer = wx.BoxSizer(wx.VERTICAL) Indx = {} def PDFParmSizer(): - + def OnShape(event): RMCPdict['shape'] = shape.GetValue() wx.CallAfter(UpdateRMC) - + parmSizer = wx.FlexGridSizer(3,6,5,5) Names = ['delta1','delta2','sratio','rcut','spdiameter'] Names2 = ['stepcut',] for name in Names: - + def OnRefine(event): Obj = event.GetEventObject() name = Indx[Obj.GetId()] RMCPdict[name][1] = not RMCPdict[name][1] - + if name == 'spdiameter' and RMCPdict.get('shape','sphere') != 'sphere': pass else: @@ -7314,10 +7383,10 @@ def OnRefine(event): if RMCPdict.get('shape','sphere') == 'stepcut': for name in Names2: parmSizer.Add(wx.StaticText(G2frame.FRMC,label=name),0,WACV) - parmSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict,name,xmin=0.,size=(70,25)),0,WACV) - + parmSizer.Add(G2G.ValidatedTxtCtrl(G2frame.FRMC,RMCPdict,name,xmin=0.,size=(70,25)),0,WACV) + return parmSizer - + def OnSpaceGroup(event): # try a lookup on the user-supplied name SpcGp = GetSpGrpfromUser(G2frame.FRMC,SpGrp) @@ -7335,12 +7404,12 @@ def OnSpaceGroup(event): msg = 'Target Space Group Information' G2G.SGMessageBox(G2frame.FRMC,msg,text,table).Show() G2spc.UpdateSytSym(data) - + def OnCellRef(event): RMCPdict['cellref'] = not RMCPdict['cellref'] - + def AtomSizer(): - + def OnSetVal(event): r,c = event.GetRow(),event.GetCol() if c > 0: @@ -7356,7 +7425,7 @@ def OnSetVal(event): wx.MessageBox('ERROR - atom constraints must be blank or have "@n" with n >= 20', style=wx.ICON_ERROR) wx.CallAfter(UpdateRMC) - + def OnUisoRefine(event): RMCPdict['UisoRefine'] = uiso.GetValue() nextP = 80 @@ -7381,7 +7450,7 @@ def OnUisoRefine(event): atom[6] = '@%d'%nextP RMCPdict['AtomVar']['@%d'%nextP] = 0.005 wx.CallAfter(UpdateRMC) - + atmSizer = wx.BoxSizer(wx.VERTICAL) atmSizer.Add(wx.StaticText(G2frame.FRMC,label=' Atom Constraints; enter as e.g. "@n" or "0.5-@n"; n>=20 && "@n" should be at end')) uisoSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -7401,7 +7470,7 @@ def OnUisoRefine(event): Types = 6*[wg.GRID_VALUE_STRING,] if addCol: colLabels += ['sym opr',] - Types = 7*[wg.GRID_VALUE_STRING,] + Types = 7*[wg.GRID_VALUE_STRING,] atmTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types) atmGrid = G2G.GSGrid(G2frame.FRMC) atmGrid.SetTable(atmTable, True,useFracEdit=False) @@ -7409,7 +7478,7 @@ def OnUisoRefine(event): atmGrid.Bind(wg.EVT_GRID_CELL_CHANGED, OnSetVal) atmSizer.Add(atmGrid) return atmSizer - + def AtomVarSizer(): atomVarSizer = wx.FlexGridSizer(0,8,5,5) for item in RMCPdict['AtomVar']: @@ -7418,17 +7487,11 @@ def AtomVarSizer(): item,xmin=-3.,xmax=3.,size=(70,25)),0,WACV) return atomVarSizer - subSizer = wx.BoxSizer(wx.HORIZONTAL) - subSizer.Add((-1,-1),1,wx.EXPAND) - subSizer.Add(wx.StaticText(G2frame.FRMC,label='For use of PDFfit, please cite:'),0,WACV) - subSizer.Add((-1,-1),1,wx.EXPAND) - mainSizer.Add(subSizer) - mainSizer.Add((5,5)) - mainSizer.Add(wx.StaticText(G2frame.FRMC,label= -'''"PDFfit2 and PDFgui: computer programs for studying nanostructures in crystals", -C.L. Farrow, P.Juhas, J.W. Liu, D. Bryndin, E.S. Bozin, J. Bloch, Th. Proffen && -S.J.L. Billinge, J. Phys, Condens. Matter 19, 335219 (2007)., Jour. Phys.: Cond. Matter -(2007), 19, 335218. doi: https://doi.org/10.1088/0953-8984/19/33/335219''')) + txt = wx.StaticText(G2frame.FRMC,label= + 'For use of PDFfit, please cite: '+ + G2G.GetCite('PDFfit2')) + txt.Wrap(500) + mainSizer.Add(txt) mainSizer.Add((5,5)) if 'PDFfit' not in data['RMC'] or not data['RMC']['PDFfit'] or 'delta1' not in data['RMC']['PDFfit']: if 'PDFfit' not in data['RMC']: @@ -7442,7 +7505,7 @@ def AtomVarSizer(): 'SeqDataType':'X','SeqCopy':True,'SeqReverse':False, 'Xdata':{'dscale':[1.0,False],'Datarange':[0.,30.],'Fitrange':[0.,30.],'qdamp':[0.03,False],'qbroad':[0.,False]}, 'Ndata':{'dscale':[1.0,False],'Datarange':[0.,30.],'Fitrange':[0.,30.],'qdamp':[0.03,False],'qbroad':[0.,False]},}) - + RMCPdict = data['RMC']['PDFfit'] #patch if 'AtomConstr' not in RMCPdict: #keep this one @@ -7466,52 +7529,52 @@ def AtomVarSizer(): RMCPdict['UisoRefine'] = 'No' #end patch Atoms = data['Atoms'] - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) if not RMCPdict['AtomConstr']: for atom in Atoms: RMCPdict['AtomConstr'].append([atom[ct-1],atom[ct],'','','','','']) else: #update name/type changes for iatm,atom in enumerate(Atoms): RMCPdict['AtomConstr'][iatm][:2] = atom[ct-1:ct+1] - + mainSizer.Add(wx.StaticText(G2frame.FRMC,label=' Enter metadata items:'),0) mainSizer.Add(GetMetaSizer(RMCPdict,['title','date','temperature','doping']),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) SgSizer = wx.BoxSizer(wx.HORIZONTAL) SgSizer.Add(wx.StaticText(G2frame.FRMC,label=' Target space group: '),0,WACV) - + mainSizer.Add(wx.StaticText(G2frame.FRMC,label='PDFfit phase structure parameters:')) - + SpGrp = RMCPdict['SGData']['SpGrp'] SGTxt = wx.Button(G2frame.FRMC,wx.ID_ANY,SpGrp,size=(100,-1)) SGTxt.Bind(wx.EVT_BUTTON,OnSpaceGroup) SgSizer.Add(SGTxt,0,WACV) mainSizer.Add(SgSizer) - + cellref = wx.CheckBox(G2frame.FRMC,label=' Refine unit cell?') cellref.SetValue(RMCPdict['cellref']) cellref.Bind(wx.EVT_CHECKBOX,OnCellRef) mainSizer.Add(cellref) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(wx.StaticText(G2frame.FRMC,label='PDFfit atom parameters:')) mainSizer.Add(AtomSizer()) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(wx.StaticText(G2frame.FRMC,label='PDFfit starting atom variables:')) G2pwd.GetPDFfitAtomVar(data,RMCPdict) mainSizer.Add(AtomVarSizer()) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(wx.StaticText(G2frame.FRMC,label=' PDFfit phase profile coefficients:')) mainSizer.Add(PDFParmSizer(),0) - + G2G.HorizontalLine(mainSizer,G2frame.FRMC) mainSizer.Add(FileSizer(RMCPdict)) return mainSizer - -####start of UpdateRMC + +####start of UpdateRMC G2frame.GetStatusBar().SetStatusText('',1) G2frame.dataWindow.FRMCDataEdit.Enable(G2G.wxID_ATOMSRMC,False) G2frame.dataWindow.FRMCDataEdit.Enable(G2G.wxID_SUPERRMC,False) @@ -7545,8 +7608,8 @@ def AtomVarSizer(): topSizer.Add(RMCsel,0) topSizer.Add((20,0)) txt = wx.StaticText(G2frame.FRMC, - label=' NB: if you change any of the entries below, you must redo the Operations/Setup RMC step above to apply them before doing Operations/Execute') - txt.Wrap(400) + label='NB: if you change any of the entries below, you must redo the Operations/Setup RMC step above to apply them before doing Operations/Execute') + txt.Wrap(250) topSizer.Add(txt,0) mainSizer.Add(topSizer,0) RMCmisc['RMCnote'] = wx.StaticText(G2frame.FRMC) @@ -7592,12 +7655,12 @@ def AtomVarSizer(): wx.EndBusyCursor() else: RMCmisc['RMCnote'].SetLabel('Note that fullrmc is not installed or was not located') - + def OnSetupRMC(event): generalData = data['General'] if not G2frame.GSASprojectfile: #force a project save G2frame.OnFileSaveas(event) - dName = G2frame.LastGPXdir + dName = G2frame.LastGPXdir os.chdir(dName) if G2frame.RMCchoice == 'fullrmc': RMCPdict = data['RMC']['fullrmc'] @@ -7606,15 +7669,15 @@ def OnSetupRMC(event): G2frame.dataWindow.FRMCDataEdit.Enable(G2G.wxID_RUNRMC,True) if RMCPdict['Swaps']: wx.MessageDialog(G2frame, G2G.StripIndents( - '''GSAS-II does not yet fully support use of swapping in fullrmc. + '''GSAS-II does not yet fully support use of swapping in fullrmc. Edit the script by hand before using.''',True), 'No swaps yet',wx.OK).ShowModal() - #--------- debug stuff + #--------- debug stuff # if GSASIIpath.GetConfigValue('debug'): # print('reloading',G2pwd) # import imp # imp.reload(G2pwd) - #--------- end debug stuff + #--------- end debug stuff rname = G2pwd.MakefullrmcRun(pName,data,RMCPdict) print('build of fullrmc file {} completed'.format(rname)) elif G2frame.RMCchoice == 'RMCProfile': @@ -7622,7 +7685,7 @@ def OnSetupRMC(event): wx.MessageDialog(G2frame,'ERROR: Phase name has space; change phase name','Bad phase name',wx.ICON_ERROR).ShowModal() G2frame.dataWindow.FRMCDataEdit.Enable(G2G.wxID_RUNRMC,False) return - dName = G2frame.LastGPXdir + dName = G2frame.LastGPXdir pName = generalData['Name'] G2frame.dataWindow.FRMCDataEdit.Enable(G2G.wxID_RUNRMC,True) RMCPdict = data['RMC']['RMCProfile'] @@ -7673,22 +7736,22 @@ def OnSetupRMC(event): G2frame.dataWindow.FRMCDataEdit.Enable(G2G.wxID_RUNRMC,True) RMCPdict = data['RMC']['PDFfit'] msg = G2pwd.MakePDFfitAtomsFile(data,RMCPdict) - if msg: + if msg: G2G.G2MessageBox(G2frame,'ERROR: '+msg,'PDFfit setup failure') return fname = G2pwd.MakePDFfitRunFile(data,RMCPdict) if fname is None: wx.MessageDialog(G2frame,'ERROR: failure to setup PDFfit; check console','PDFfit setup failure',wx.ICON_ERROR).ShowModal() - else: + else: print(fname+ ' written') print('PDFfit file build completed') - + def RunPDFfit(event): generalData = data['General'] ISOdict = data['ISODISTORT'] PDFfit_exec = G2pwd.findPDFfit() #returns location of python with PDFfit installed if not PDFfit_exec: - wx.MessageBox(''' PDFfit2 is not currently installed for this platform. + wx.MessageBox(''' PDFfit2 is not currently installed for this platform. Please contact us for assistance''',caption='No PDFfit2',style=wx.ICON_INFORMATION) return RMCPdict = data['RMC']['PDFfit'] @@ -7701,12 +7764,9 @@ def RunPDFfit(event): wx.MessageBox(f'File {rname} does not exist. Has the Operations/"Setup RMC" menu command been run?', caption='Run setup',style=wx.ICON_WARNING) return - wx.MessageBox(''' For use of PDFfit2, please cite: - PDFfit2 and PDFgui: computer progerama for studying nanostructures in crystals, -C.L. Farrow, P.Juhas, J.W. Liu, D. Bryndin, E.S. Bozin, J. Bloch, Th. Proffen & -S.J.L. Billinge, J. Phys, Condens. Matter 19, 335219 (2007)., Jour. Phys.: Cond. Matter -(2007), 19, 335218. doi: https://doi.org/10.1088/0953-8984/19/33/335219''', - caption='PDFfit2',style=wx.ICON_INFORMATION) + wx.MessageBox(' For use of PDFfit2, please cite:\n\n'+ + G2G.GetCite('PDFfit2'), + caption='PDFfit2',style=wx.ICON_INFORMATION) G2frame.OnFileSave(event) print (' GSAS-II project saved') if sys.platform.lower().startswith('win'): @@ -7762,7 +7822,7 @@ def RunPDFfit(event): if RMCPdict['SeqReverse']: SeqNames.reverse() nPDF = len(SeqNames) - pgbar = wx.ProgressDialog('Sequential PDFfit','PDF G(R) done = 0',nPDF+1, + pgbar = wx.ProgressDialog('Sequential PDFfit','PDF G(R) done = 0',nPDF+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) newParms = {} for itm,Item in enumerate(SeqNames): @@ -7865,7 +7925,7 @@ def RunPDFfit(event): result = np.array(list(newParms.values())).T SeqResult[PDFfile[0]] = {'variables':result[0],'varyList':varyList,'sig':result[1],'Rvals':{'Rwp':Rwp,}, 'covMatrix':[],'title':PDFfile[0],'parmDict':parmDict} - + pfile = open('Sequential_PDFfit.fgr') XYcalc = np.loadtxt(pfile).T[:2] pfile.close() @@ -7884,7 +7944,7 @@ def RunPDFfit(event): if not GoOn[0]: print(' Sequential PDFfit aborted') break - + pgbar.Destroy() G2frame.GPXtree.SetItemPyData(Id,SeqResult) G2frame.G2plotNB.Delete('Sequential refinement') #clear away probably invalid plot @@ -7924,7 +7984,7 @@ def RunPDFfit(event): if Error: wx.MessageBox('PDFfit failed',caption='%s not found'%Error[0],style=wx.ICON_EXCLAMATION) UpdateRMC() - + def Runfullrmc(event): fullrmc_exec = G2pwd.findfullrmc() if fullrmc_exec is None: @@ -7941,10 +8001,10 @@ def Runfullrmc(event): RMCPdict = data['RMC']['fullrmc'] rmcname = pName+'-fullrmc.rmc' if os.path.isdir(rmcname) and RMCPdict['ReStart'][0]: - msg = '''You have asked to start a new fullrmc run rather than - continue the existing {} run. - %%Press "Yes" to continue, deleting this - previous run or "No" to change the restart checkbox to + msg = '''You have asked to start a new fullrmc run rather than + continue the existing {} run. + %%Press "Yes" to continue, deleting this + previous run or "No" to change the restart checkbox to continue from the previous results.'''.format(rmcname) dlg = wx.MessageDialog(G2frame,G2G.StripIndents(msg,True), @@ -7955,24 +8015,12 @@ def Runfullrmc(event): finally: dlg.Destroy() if result == wx.ID_YES: - import shutil shutil.rmtree(rmcname) else: return - G2G.G2MessageBox(G2frame, -'''For use of fullrmc, please cite: - - "Atomic Stochastic Modeling & Optimization - with fullrmc", B. Aoun, J. Appl. Cryst. 2022, - 55(6) 1664-1676, DOI: 10.1107/S1600576722008536. - - "Fullrmc, a Rigid Body Reverse Monte Carlo - Modeling Package Enabled with Machine Learning - and Artificial Intelligence", - B. Aoun, Jour. Comp. Chem. 2016, 37, 1102-1111. - DOI: 10.1002/jcc.24304 - - Note: A more advanced version of fullrmc can be found at www.fullrmc.com''', + G2G.G2MessageBox(G2frame,'For use of fullrmc, please cite:\n\n'+ + G2G.GetCite('fullrmc')+ + '\n\nNote: A more advanced version of fullrmc can be found at www.fullrmc.com', 'Please cite fullrmc') ilog = 0 while True: @@ -8011,7 +8059,7 @@ def Runfullrmc(event): Proc = subp.Popen(['/bin/bash','fullrmc.sh']) # Proc.wait() #for it to finish before continuing on UpdateRMC() - + def RunRMCProfile(event): generalData = data['General'] pName = generalData['Name'].replace(' ','_') @@ -8025,18 +8073,16 @@ def RunRMCProfile(event): else: if rmcfile is None: wx.MessageBox(''' RMCProfile is not correctly installed for use in GSAS-II - Obtain the zip file distribution from www.rmcprofile.org, + Obtain the zip file distribution from www.rmcprofile.org, unzip it and place the RMCProfile main directory in the main GSAS-II directory ''', caption='RMCProfile',style=wx.ICON_INFORMATION) return rmcexe = os.path.split(rmcfile)[0] - print(rmcexe) - wx.MessageBox(''' For use of RMCProfile, please cite: - RMCProfile: Reverse Monte Carlo for polycrystalline materials, - M.G. Tucker, D.A. Keen, M.T. Dove, A.L. Goodwin and Q. Hui, - Jour. Phys.: Cond. Matter 2007, 19, 335218. - doi: https://doi.org/10.1088/0953-8984/19/33/335218''', - caption='RMCProfile',style=wx.ICON_INFORMATION) + #print(rmcexe) + wx.MessageBox( + ' For use of RMCProfile, please cite:\n\n'+ + G2G.GetCite("RMCProfile"), + caption='RMCProfile',style=wx.ICON_INFORMATION) if os.path.isfile(pName+'.his6f'): os.remove(pName+'.his6f') if os.path.isfile(pName+'.xray'): @@ -8062,7 +8108,7 @@ def RunRMCProfile(event): i += 1 else: break - + G2frame.OnFileSave(event) print (' GSAS-II project saved') pName = generalData['Name'].replace(' ','_') @@ -8092,17 +8138,17 @@ def RunRMCProfile(event): subp.Popen('runrmc.bat',creationflags=subp.CREATE_NEW_CONSOLE) # Proc.wait() #for it to finish before continuing on UpdateRMC() - + def OnRunRMC(event): '''Run a previously created RMCProfile/fullrmc/PDFfit2 script ''' if G2frame.RMCchoice == 'fullrmc': - Runfullrmc(event) + Runfullrmc(event) elif G2frame.RMCchoice == 'RMCProfile': RunRMCProfile(event) elif G2frame.RMCchoice == 'PDFfit': RunPDFfit(event) - + # def OnStopRMC(event): # if G2frame.RMCchoice == 'fullrmc': # generalData = data['General'] @@ -8119,24 +8165,24 @@ def OnRunRMC(event): # print('hook.stop_engine() sent to {}'.format(engineFilePath)) # except Exception as msg: # print('failed, msg=',msg) - + def OnLoadRMC(event): - '''Used to load the output from fullrmc with all atoms placed in the + '''Used to load the output from fullrmc with all atoms placed in the original cell ''' fullrmcLoadPhase(super=False) def OnLoadRMCsuper(event): - '''Used to load the output from fullrmc with atoms in the simulation + '''Used to load the output from fullrmc with atoms in the simulation supercell cell ''' fullrmcLoadPhase(super=True) def fullrmcLoadPhase(super): - '''Used to load the output from fullrmc. Creates a new phase, - reads all atoms & converts coordinates to fractional. - If super is False all atoms placed in the original cell. + '''Used to load the output from fullrmc. Creates a new phase, + reads all atoms & converts coordinates to fractional. + If super is False all atoms placed in the original cell. - Called from :func:`OnLoadRMC` or :func:`OnLoadRMCsuper` from - the RMC tab Operations menu commands 'Superimpose into cell' + Called from :func:`OnLoadRMC` or :func:`OnLoadRMCsuper` from + the RMC tab Operations menu commands 'Superimpose into cell' and 'Load Supercell'. ''' if G2frame.RMCchoice != 'fullrmc': @@ -8187,9 +8233,9 @@ def fullrmcLoadPhase(super): subr = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints') G2frame.GPXtree.GetItemPyData(subr).update({PhaseName:{}}) SetupGeneral() # index elements - + #wx.CallAfter(G2frame.GPXtree.SelectItem,psub) # should call SelectDataT - + def OnViewRMC(event): if G2frame.RMCchoice == 'fullrmc': RMCPdict = data['RMC']['fullrmc'] @@ -8243,7 +8289,7 @@ def OnViewRMC(event): fp.close() if not choices: G2G.G2MessageBox(G2frame, - 'Nothing to plot. '+ + 'Nothing to plot. '+ 'No results in '+statFilePath+' or '+plotFilePath, 'Nothing to plot') return @@ -8296,7 +8342,7 @@ def OnViewRMC(event): else: dlg.Destroy() return - + ifXray = False ifNeut = False try: @@ -8351,7 +8397,7 @@ def OnViewRMC(event): if '(R)' in Labels[label][1]: Ysave.append(Ycalc) Ymin = Ysave[0][1][0] - if 'bragg' in label: + if 'bragg' in label: Ydiff = np.array([X,(Yobs-Ycalc)[1]]) Yoff = np.max(Ydiff[1])-np.min(Yobs[1]) Ydiff[1] -= Yoff @@ -8391,7 +8437,7 @@ def OnViewRMC(event): if ifNeut: XY = [[X.T,(DX*Y.T)] for iy,Y in enumerate(Partials) if 'Va' not in Names[iy+1]] else: - XY = [[X.T,(DX*Y.T)*X.T] for iy,Y in enumerate(Partials) if 'Va' not in Names[iy+1]] + XY = [[X.T,(DX*Y.T)*X.T] for iy,Y in enumerate(Partials) if 'Va' not in Names[iy+1]] Names = [name for name in Names if 'Va' not in name] ylabel = Labels[label][1] if 'G(R)' in Labels[label][1]: @@ -8435,7 +8481,7 @@ def OnViewRMC(event): G2plt.PlotXY(G2frame,XY2=XY,XY=[Ysave[0][:,0:Xmax],],labelX=Labels[label][0], labelY=ylabel,newPlot=True,Title=title, lines=False,names=[r' $G(R)_{calc}$',]+Names[1:]) - else: + else: G2plt.PlotXY(G2frame,XY,labelX=Labels[label][0], labelY=ylabel,newPlot=True,Title=Labels[label][2]+pName, lines=True,names=Names[1:]) @@ -8455,7 +8501,7 @@ def OnViewRMC(event): labelY=r'$log_{10}$ (reduced $\mathsf{\chi^2})$',newPlot=True,Title='RMCP Chi^2 for '+pName, lines=True,names=Names[3:]) -#get atoms from rmc6f file +#get atoms from rmc6f file rmc6fName = pName+'.rmc6f' rmc6f = open(rmc6fName,'r') rmc6fAtoms = [] @@ -8470,9 +8516,9 @@ def OnViewRMC(event): rmc6fAtoms.append([line[1],float(line[3]),float(line[4]),float(line[5])]) rmc6f.close() #alt bond histograms - from rmc6 & bond files - + bondName = pName+'.bonds' - if os.path.exists(os.path.join(path,bondName)): + if os.path.exists(os.path.join(path,bondName)): nBond = len(RMCPdict['Potentials']['Stretch']) bondList = [] bonds = open(bondName,'r') @@ -8498,7 +8544,7 @@ def OnViewRMC(event): G2plt.PlotBarGraph(G2frame,bondDist,Xname=r'$Bond, \AA$',Title=title+' from Potential Energy Restraint', PlotName='%s Bond for %s'%(title,pName)) print(' %d %s bonds found'%(len(bondDist),title)) - + #alt angle histograms - from rmc6 & triplets files tripName = pName+'.triplets' if os.path.exists(os.path.join(path,tripName)): @@ -8528,8 +8574,8 @@ def OnViewRMC(event): G2plt.PlotBarGraph(G2frame,angles,Xname=r'$Angle, \AA$',Title=title+' from Potential Energy Restraint', PlotName='%s Angle for %s'%(title,pName)) print(' %d %s angles found'%(len(angles),title)) - -#bond odf plots + +#bond odf plots nPot = len(RMCPdict['Potentials']['Stretch']) for iPot in range(nPot): fname = pName+'.bondodf_%d'%(iPot+1) @@ -8542,7 +8588,7 @@ def OnViewRMC(event): odfData = np.fromfile(OutFile,sep=' ') numx,numy = odfData[:2] G2plt.Plot3dXYZ(G2frame,int(numx),int(numy),odfData[2:], - newPlot=False,Title='Number of %s-%s Bonds'%(bond[0],bond[1]),Centro=True) + newPlot=False,Title='Number of %s-%s Bonds'%(bond[0],bond[1]),Centro=True) OutFile.close() elif G2frame.RMCchoice == 'PDFfit': generalData = data['General'] @@ -8582,18 +8628,18 @@ def OnViewRMC(event): G2plt.PlotXY(G2frame,[XYobs,],XY2=[XYcalc,XYdiff],labelX=Labels[0], labelY=Labels[1],newPlot=True,Title=Labels[2]+files[file][0], lines=False,names=['G(R) obs','G(R) calc','diff',]) - - + + #### ISODISTORT tab ############################################################################### def UpdateISODISTORT(Scroll=0): ''' Setup ISODISTORT and present the results. Allow selection of a distortion model for PDFfit or - GSAS-II structure refinement as a cif file produced by ISODISTORT. Allows manipulation of distortion + GSAS-II structure refinement as a cif file produced by ISODISTORT. Allows manipulation of distortion mode displacements selection their refinement for this new phase. ''' - + def displaySetup(): - + def OnParentCif(event): dlg = wx.FileDialog(ISODIST, 'Select parent cif file',G2frame.LastGPXdir, style=wx.FD_OPEN ,wildcard='cif file(*.cif)|*.cif') @@ -8605,17 +8651,17 @@ def OnParentCif(event): else: dlg.Destroy() UpdateISODISTORT() - + def OnUsePhase(event): - ISOdata['ParentCIF'] = 'Use this phase' + ISOdata['ParentCIF'] = 'Use this phase' UpdateISODISTORT() - + def OnMethodSel(event): method = methodSel.GetSelection()+1 if method in [1,4]: ISOdata['ISOmethod'] = method UpdateISODISTORT() - + def OnChildCif(event): dlg = wx.FileDialog(ISODIST, 'Select child cif file',G2frame.LastGPXdir, style=wx.FD_OPEN ,wildcard='cif file(*.cif)|*.cif') @@ -8629,7 +8675,7 @@ def OnChildCif(event): UpdateISODISTORT() def OnUsePhase2(event): - ISOdata['ChildCIF'] = 'Use this phase' + ISOdata['ChildCIF'] = 'Use this phase' UpdateISODISTORT() topSizer = wx.BoxSizer(wx.VERTICAL) @@ -8668,25 +8714,25 @@ def OnUsePhase2(event): usePhase2.Bind(wx.EVT_BUTTON,OnUsePhase2) childSizer.Add(usePhase2,0,WACV) topSizer.Add(childSizer) - + return topSizer - + def displaySubset(): - + def OnLaue(event): Obj = event.GetEventObject() name = Indx[Obj.GetId()] ISOdata['SGselect'][name[:4]] = not ISOdata['SGselect'][name[:4]] ISOdata['selection'] = None UpdateISODISTORT() - + def OnAllBtn(event): for item in ISOdata['SGselect']: ISOdata['SGselect'][item] = not ISOdata['SGselect'][item] ISOdata['selection'] = None UpdateISODISTORT() - - topSizer = wx.BoxSizer(wx.VERTICAL) + + topSizer = wx.BoxSizer(wx.VERTICAL) G2G.HorizontalLine(topSizer,ISODIST) topSizer.Add(wx.StaticText(ISODIST,label='ISODISTORT Method 1 distortion search results:')) topSizer.Add(wx.StaticText(ISODIST,label=' Subset selection if desired:')) @@ -8704,16 +8750,16 @@ def OnAllBtn(event): littleSizer.Add(allBtn) topSizer.Add(littleSizer) return topSizer - + def displayRadio(): - + def CheckItem(item): SGnum = int(item.split()[1].split('*')[0]) for SGtype in ISOdata['SGselect']: if ISOdata['SGselect'][SGtype] and SGnum in SGrange[SGtype]: return True return False - + def OnSelect(event): r,c = event.GetRow(),event.GetCol() if c == 0: @@ -8722,13 +8768,13 @@ def OnSelect(event): isoTable.SetValue(row,c,False) isoTable.SetValue(r,c,True) isoGrid.ForceRefresh() - + SGrange = {'Cubi':np.arange(195,231),'Hexa':np.arange(168,195),'Trig':np.arange(143,168),'Tetr':np.arange(75,143), - 'Orth':np.arange(16,75),'Mono':np.arange(3,16),'Tric':np.arange(1,3)} + 'Orth':np.arange(16,75),'Mono':np.arange(3,16),'Tric':np.arange(1,3)} bottomSizer = wx.BoxSizer(wx.VERTICAL) colLabels = ['select',' ISODISTORT order parameter direction description'] colTypes = [wg.GRID_VALUE_BOOL,wg.GRID_VALUE_STRING,] - + Radio = ISOdata['radio'] rowLabels = [] table = [] @@ -8751,9 +8797,9 @@ def OnSelect(event): isoGrid.SetColAttr(1,attr) isoGrid.Bind(wg.EVT_GRID_CELL_LEFT_CLICK, OnSelect) return bottomSizer - + def displayModes(): - + def OnDispl(event): '''Respond to movement of distortion mode slider''' Obj = event.GetEventObject() @@ -8762,32 +8808,32 @@ def OnDispl(event): dispVal.SetValue(modeDisp[idsp]) err = G2mth.ApplyModeDisp(data) if err: - G2G.G2MessageBox(G2frame,'Do Draw atoms first') - FindBondsDraw(data) + G2G.G2MessageBox(G2frame,'Do Draw atoms first') + FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) - + def OnDispVal(invalid,value,tc): '''Respond to entry of a value into a distortion mode entry widget''' idsp,displ = Indx[tc.GetId()] displ.SetValue(int(value*1000)) err = G2mth.ApplyModeDisp(data) if err: - G2G.G2MessageBox(G2frame,'Do Draw atoms first') - FindBondsDraw(data) + G2G.G2MessageBox(G2frame,'Do Draw atoms first') + FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) - + def OnRefDispl(event): Obj = event.GetEventObject() idsp,item = Indx[Obj.GetId()] item[-2] = not item[-2] - + def OnReset(event): '''Reset all distortion mode values to initial values''' ISOdata['modeDispl'] = copy.deepcopy(ISOdata['ISOmodeDispl']) err = G2mth.ApplyModeDisp(data) if err: - G2G.G2MessageBox(G2frame,'Do Draw atoms first') - FindBondsDraw(data) + G2G.G2MessageBox(G2frame,'Do Draw atoms first') + FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) UpdateISODISTORT() @@ -8796,8 +8842,8 @@ def OnSetZero(event): ISOdata['modeDispl'] = [0.0 for i in ISOdata['ISOmodeDispl']] err = G2mth.ApplyModeDisp(data) if err: - G2G.G2MessageBox(G2frame,'Do Draw atoms first') - FindBondsDraw(data) + G2G.G2MessageBox(G2frame,'Do Draw atoms first') + FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) UpdateISODISTORT() @@ -8814,19 +8860,22 @@ def OnSaveModes(event): ISOdata['ISOmodeDispl'] = copy.deepcopy(ISOdata['modeDispl']) G2plt.PlotStructure(G2frame,data) UpdateISODISTORT() - - #### displayModes code starts here + + #### displayModes code starts here ConstrData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Constraints')) pId = data['ranId'] mainSizer = wx.BoxSizer(wx.VERTICAL) if SGLaue not in ['mmm','2/m','-1']: mainSizer.Add(wx.StaticText(ISODIST,label=' NB: ISODISTORT distortion mode symmetry is too high to be used in PDFfit')) - mainSizer.Add(wx.StaticText(ISODIST,label=ISOcite)) - + txt = wx.StaticText(ISODIST,label= + ' For use of ISODISTORT, please cite: '+ + G2G.GetCite('ISOTROPY, ISODISTORT, ISOCIF...')) + txt.Wrap(500) + mainSizer.Add(txt) mainSizer.Add(wx.StaticText(ISODIST,label= u''' The 2nd column below shows the last saved mode values. The 3rd && 4th columns will set the - display mode values. The positions in the Atoms and Draw Atoms tabs, as well as the atom - positions shown in the Plot Window are changed to reflect the display mode values. The + display mode values. The positions in the Atoms and Draw Atoms tabs, as well as the atom + positions shown in the Plot Window are changed to reflect the display mode values. The range of the slider corresponds to making a maximum atomic displacement between -2 && +2 \u212B.''')) mainSizer.Add((-1,10)) slideSizer = wx.FlexGridSizer(0,5,0,0) @@ -8850,7 +8899,7 @@ def OnSaveModes(event): slideSizer.Add(wx.StaticText(ISODIST,label=isoName),0,WACV) slideSizer.Add(wx.StaticText(ISODIST,label=' %.5g '%ISOdata['ISOmodeDispl'][idsp], style=wx.ALIGN_CENTER_HORIZONTAL),0,WACV|wx.EXPAND) - lineSizer = wx.BoxSizer(wx.HORIZONTAL) + lineSizer = wx.BoxSizer(wx.HORIZONTAL) dispVal = G2G.ValidatedTxtCtrl(ISODIST,modeDisp,idsp,xmin=-2.,xmax=2.,size=(75,20),OnLeave=OnDispVal) lineSizer.Add(dispVal,0,WACV) displ = G2G.G2Slider(ISODIST,style=wx.SL_HORIZONTAL,minValue=-2000,maxValue=2000, @@ -8864,12 +8913,12 @@ def OnSaveModes(event): refDispl.SetValue(item[-2]) refDispl.Bind(wx.EVT_CHECKBOX,OnRefDispl) Indx[refDispl.GetId()] = [idsp,item] - slideSizer.Add(refDispl,0,WACV|wx.EXPAND|wx.LEFT,15) + slideSizer.Add(refDispl,0,WACV|wx.EXPAND|wx.LEFT,15) slideSizer.Add(wx.StaticText(ISODIST,label=', '.join(ModeDispList[idsp])),0,wx.EXPAND|wx.LEFT,15) idsp += 1 slideSizer.SetMinSize(wx.Size(650,10)) mainSizer.Add(slideSizer) - lineSizer = wx.BoxSizer(wx.HORIZONTAL) + lineSizer = wx.BoxSizer(wx.HORIZONTAL) reset = wx.Button(ISODIST,label='Reset modes to save values') reset.Bind(wx.EVT_BUTTON,OnReset) lineSizer.Add(reset,0,WACV) @@ -8881,8 +8930,8 @@ def OnSaveModes(event): lineSizer.Add(reset,0,WACV) mainSizer.Add(lineSizer,0,wx.TOP,5) mainSizer.Layout() - SetPhaseWindow(ISODIST,mainSizer,Scroll=Scroll) - + SetPhaseWindow(ISODIST,mainSizer,Scroll=Scroll) + #### UpdateISODISTORT code starts here topSizer = G2frame.dataWindow.topBox topSizer.Clear(True) @@ -8897,20 +8946,15 @@ def OnSaveModes(event): SGLaue = data['General']['SGData']['SGLaue'] G2frame.dataWindow.ISODDataEdit.Enable(G2G.wxID_ISODNEWPHASE,'rundata' in ISOdata) G2frame.dataWindow.ISODDataEdit.Enable(G2G.wxID_ISOPDFFIT,(('G2VarList' in ISOdata) and (SGLaue in ['mmm','2/m','-1']))) - G2frame.dataWindow.ISODDataEdit.Enable(G2G.wxID_SHOWISO1,('G2VarList' in ISOdata) + G2frame.dataWindow.ISODDataEdit.Enable(G2G.wxID_SHOWISO1,('G2VarList' in ISOdata) or ('G2OccVarList' in ISOdata)) G2frame.dataWindow.ISODDataEdit.Enable(G2G.wxID_SHOWISOMODES,('G2VarList' in ISOdata)) - ISOcite = ''' For use of ISODISTORT, please cite: - H. T. Stokes, D. M. Hatch, and B. J. Campbell, ISODISTORT, ISOTROPY Software Suite, iso.byu.edu. - B. J. Campbell, H. T. Stokes, D. E. Tanner, and D. M. Hatch, "ISODISPLACE: An Internet Tool for - Exploring Structural Distortions." J. Appl. Cryst. 39, 607-614 (2006). - ''' if ISODIST.GetSizer(): ISODIST.GetSizer().Clear(True) - + if 'G2ModeList' in ISOdata: #invoked only if phase is from a ISODISTORT cif file & thus contains distortion mode constraints - + # #patch # if 'modeDispl' not in ISOdata: # ISOdata['modeDispl'] = np.zeros(len(ISOdata['G2ModeList'])) @@ -8918,33 +8962,39 @@ def OnSaveModes(event): ModeDispList = G2pwd.GetAtmDispList(ISOdata) displayModes() return - + #initialization if 'ParentCIF' not in ISOdata: ISOdata.update({'ParentCIF':'Select','ChildCIF':'Select','ISOmethod':4, 'ChildMatrix':np.eye(3),'ChildSprGp':'P 1','ChildCell':'abc',}) #these last 3 currently unused #end initialization - + mainSizer = wx.BoxSizer(wx.VERTICAL) - mainSizer.Add(wx.StaticText(ISODIST,label=ISOcite)) - + txt = wx.StaticText(ISODIST,label= + ' For use of ISODISTORT, please cite: '+ + G2G.GetCite('ISOTROPY, ISODISTORT, ISOCIF...')) + txt.Wrap(500) + mainSizer.Add(txt) + mainSizer.Add((-1,5)) + G2G.HorizontalLine(mainSizer,ISODIST) + mainSizer.Add((-1,5)) mainSizer.Add(displaySetup()) - + if 'radio' in ISOdata: - mainSizer.Add(displaySubset()) + mainSizer.Add(displaySubset()) mainSizer.Add(displayRadio()) SetPhaseWindow(ISODIST,mainSizer,Scroll=Scroll) - + def OnRunISODISTORT(event): ''' this needs to setup for method #3 or #4 in ISODISTORT after providing parent cif: #3 asks for transformation matrix & space group of child structure #4 asks for cif file of child structure ''' - + if not G2frame.GSASprojectfile: #force a project save to establish location of output cif file G2frame.OnFileSaveas(event) - + radio,rundata = ISO.GetISODISTORT(data) if radio: data['ISODISTORT']['radio'] = radio @@ -8958,7 +9008,7 @@ def OnRunISODISTORT(event): else: G2G.G2MessageBox(G2frame,'ISODISTORT run complete; new cif file %s created.\n To use, import it as a new phase.'%rundata) print(' ISODISTORT run complete; new cif file %s created. To use, import it as a new phase.'%rundata) - + def OnNewISOPhase(event): ''' Make CIF file with ISODISTORT ''' @@ -8968,22 +9018,22 @@ def OnNewISOPhase(event): elif 'rundata' in data['ISODISTORT']: G2G.G2MessageBox(G2frame,'Need to select an ISODISTORTdistortion model first before creating a CIF') else: - G2G.G2MessageBox(G2frame,'ERROR - need to run ISODISTORT first - see General/Compute menu') - + G2G.G2MessageBox(G2frame,'ERROR - need to run ISODISTORT first - see General/Compute menu') + def OnNewPDFfitPhase(event): ''' Make new phase for PDFfit using ISODISTORT mode definitions as constraints ''' newPhase = G2pwd.ISO2PDFfit(data) - phaseName = newPhase['General']['Name'] + phaseName = newPhase['General']['Name'] sub = G2frame.GPXtree.AppendItem(G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases'),text=phaseName) G2frame.GPXtree.SetItemPyData(sub,newPhase) G2frame.GPXtree.SelectItem(sub) - + #### DIFFax Layer Data page ################################################################################ def UpdateLayerData(Scroll=0): '''Present the contents of the Phase/Layers tab for stacking fault simulation ''' - + laueChoice = ['-1','2/m(ab)','2/m(c)','mmm','-3','-3m','4/m','4/mmm', '6/m','6/mmm','unknown'] colLabels = ['Name','Type','x','y','z','frac','Uiso'] @@ -8995,12 +9045,12 @@ def UpdateLayerData(Scroll=0): plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1], 'viewPoint':[[0.,0.,0.],[]],} Indx = {} - + def OnLaue(event): Obj = event.GetEventObject() data['Layers']['Laue'] = Obj.GetValue() wx.CallAfter(UpdateLayerData) - + def OnSadpPlot(event): sadpPlot.SetValue(False) labels = Layers['Sadp']['Plane'] @@ -9009,7 +9059,7 @@ def OnSadpPlot(event): G2frame.Cmax = 1.0 G2plt.PlotXYZ(G2frame,XY,Layers['Sadp']['Img'].T,labelX=labels[:-1], labelY=labels[-1],newPlot=False,Title=Layers['Sadp']['Plane']) - + def OnSeqPlot(event): seqPlot.SetValue(False) resultXY,resultXY2,seqNames = Layers['seqResults'] @@ -9017,18 +9067,18 @@ def OnSeqPlot(event): G2plt.PlotXY(G2frame,resultXY,XY2=resultXY2,labelX=r'$\mathsf{2\theta}$', labelY='Intensity',newPlot=True,Title='Sequential simulations on '+pName, lines=True,names=seqNames) - + def CellSizer(): - + cellGUIlist = [ [['-3','-3m','6/m','6/mmm','4/m','4/mmm'],6,zip([" a = "," c = "],["%.5f","%.5f",],[True,True],[0,2])], [['mmm'],8,zip([" a = "," b = "," c = "],["%.5f","%.5f","%.5f"],[True,True,True],[0,1,2,])], [['2/m(ab)','2/m(c)','-1','axial','unknown'],10,zip([" a = "," b = "," c = "," gamma = "], ["%.5f","%.5f","%.5f","%.3f"],[True,True,True,True],[0,1,2,5])]] - + def OnCellRef(event): data['Layers']['Cell'][0] = cellRef.GetValue() - + def OnCellChange(event): event.Skip() laue = data['Layers']['Laue'] @@ -9042,7 +9092,7 @@ def OnCellChange(event): value = cell[ObjId+1] else: #bad angle value = 90. - if laue in ['-3','-3m','6/m','6/mmm','4/m','4/mmm']: + if laue in ['-3','-3m','6/m','6/mmm','4/m','4/mmm']: cell[4] = cell[5] = 90. cell[6] = 120. if laue in ['4/m','4/mmm']: @@ -9067,7 +9117,7 @@ def OnCellChange(event): Obj.SetValue("%.3f"%(cell[6])) cell[7] = G2lat.calc_V(G2lat.cell2A(cell[1:7])) volVal.SetLabel(' Vol = %.3f'%(cell[7])) - + cell = data['Layers']['Cell'] laue = data['Layers']['Laue'] for cellGUI in cellGUIlist: @@ -9084,20 +9134,20 @@ def OnCellChange(event): # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) cellVal = wx.TextCtrl(layerData,value=(fmt%(cell[Id+1])), style=wx.TE_PROCESS_ENTER) - cellVal.Bind(wx.EVT_TEXT_ENTER,OnCellChange) + cellVal.Bind(wx.EVT_TEXT_ENTER,OnCellChange) cellVal.Bind(wx.EVT_KILL_FOCUS,OnCellChange) cellSizer.Add(cellVal,0,WACV) cellList.append(cellVal.GetId()) volVal = wx.StaticText(layerData,label=' Vol = %.3f'%(cell[7])) cellSizer.Add(volVal,0,WACV) return cellSizer - + def WidthSizer(): - + def OnRefWidth(event): Id = Indx[event.GetEventObject()] Layers['Width'][1][Id] = not Layers['Width'][1][Id] - + Labels = ['a','b'] flags = Layers['Width'][1] widthSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -9111,7 +9161,7 @@ def OnRefWidth(event): widthRef.Bind(wx.EVT_CHECKBOX, OnRefWidth) widthSizer.Add(widthRef,0,WACV) return widthSizer - + def OnNewLayer(event): data['Layers']['Layers'].append({'Name':'Unk','SameAs':'','Symm':'None','Atoms':[]}) Trans = data['Layers']['Transitions'] @@ -9123,14 +9173,14 @@ def OnNewLayer(event): Trans = [[[1.,0.,0.,0.,'',False],],] data['Layers']['Transitions'] = Trans wx.CallLater(100,UpdateLayerData) - + def OnDeleteLast(event): del(data['Layers']['Layers'][-1]) del(data['Layers']['Transitions'][-1]) for trans in data['Layers']['Transitions']: del trans[-1] wx.CallAfter(UpdateLayerData) - + def OnImportLayer(event): dlg = wx.FileDialog(G2frame, 'Choose GSAS-II project file', G2G.GetImportPath(G2frame), wildcard='GSAS-II project file (*.gpx)|*.gpx',style=wx.FD_OPEN| wx.FD_CHANGE_DIR) @@ -9172,28 +9222,28 @@ def OnImportLayer(event): Trans = [[[1.,0.,0.,0.,'',False],],] data['Layers']['Transitions'] = Trans wx.CallAfter(UpdateLayerData) - + def LayerSizer(il,Layer): - + def OnNameChange(event): event.Skip() - Layer['Name'] = layerName.GetValue() + Layer['Name'] = layerName.GetValue() wx.CallLater(100,UpdateLayerData) - + def OnAddAtom(event): Layer['Atoms'].append(['Unk','Unk',0.,0.,0.,1.,0.01]) wx.CallAfter(UpdateLayerData) - + def OnSymm(event): Layer['Symm'] = symm.GetValue() - + def AtomTypeSelect(event): r,c = event.GetRow(),event.GetCol() if atomGrid.GetColLabelValue(c) == 'Type': PE = G2elemGUI.PickElement(G2frame) if PE.ShowModal() == wx.ID_OK: if PE.Elem != 'None': - atType = PE.Elem.strip() + atType = PE.Elem.strip() Layer['Atoms'][r][c] = atType name = Layer['Atoms'][r][c] if len(name) in [2,4]: @@ -9206,21 +9256,21 @@ def AtomTypeSelect(event): wx.CallAfter(UpdateLayerData) else: event.Skip() - + def OnDrawLayer(event): drawLayer.SetValue(False) - G2plt.PlotLayers(G2frame,Layers,[il,],plotDefaults) - + G2plt.PlotLayers(G2frame,Layers,[il,],plotDefaults,firstCall=True) + def OnSameAs(event): Layer['SameAs'] = sameas.GetValue() wx.CallLater(100,UpdateLayerData) - + layerSizer = wx.BoxSizer(wx.VERTICAL) - nameSizer = wx.BoxSizer(wx.HORIZONTAL) + nameSizer = wx.BoxSizer(wx.HORIZONTAL) nameSizer.Add(wx.StaticText(layerData,label=' Layer name: '),0,WACV) # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) layerName = wx.TextCtrl(layerData,value=Layer['Name'],style=wx.TE_PROCESS_ENTER) - layerName.Bind(wx.EVT_TEXT_ENTER,OnNameChange) + layerName.Bind(wx.EVT_TEXT_ENTER,OnNameChange) layerName.Bind(wx.EVT_KILL_FOCUS,OnNameChange) layerName.Bind(wx.EVT_LEAVE_WINDOW,OnNameChange) nameSizer.Add(layerName,0,WACV) @@ -9270,15 +9320,15 @@ def OnSameAs(event): atomGrid.AutoSizeColumns(True) layerSizer.Add(atomGrid) return layerSizer - + def TransSizer(): - + def PlotSelect(event): Obj = event.GetEventObject() - Yi = Indx[Obj.GetId()] + Yi = Indx[Obj.GetId()] Xi,c = event.GetRow(),event.GetCol() if Xi >= 0 and c == 5: #plot column - G2plt.PlotLayers(G2frame,Layers,[Yi,Xi,],plotDefaults) + G2plt.PlotLayers(G2frame,Layers,[Yi,Xi,],plotDefaults,firstCall=True) else: Psum = 0. for Xi in range(len(transArray)): @@ -9286,7 +9336,7 @@ def PlotSelect(event): Psum /= len(transArray) totalFault.SetLabel(' Total fault density = %.3f'%(1.-Psum)) event.Skip() - + def OnNormProb(event): for Yi,Yname in enumerate(Names): Psum = 0. @@ -9298,7 +9348,7 @@ def OnNormProb(event): for Xi,Xname in enumerate(Names): transArray[Yi][Xi][0] /= Psum wx.CallAfter(UpdateLayerData) - + def OnSymProb(event): if symprob.GetValue(): Nx = len(Names)-1 @@ -9313,7 +9363,7 @@ def OnSymProb(event): break else: Layers['SymTrans'] = False - + transSizer = wx.BoxSizer(wx.VERTICAL) transSizer.Add(wx.StaticText(layerData,label=' Layer-Layer transition probabilities: '),0) topSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -9362,9 +9412,9 @@ def OnSymProb(event): label=' Total fault density = %.3f'%(1.-diagSum)) transSizer.Add(totalFault,0) return transSizer - + def PlotSizer(): - + def OnPlotSeq(event): event.Skip() vals = plotSeq.GetValue().split() @@ -9375,8 +9425,8 @@ def OnPlotSeq(event): except ValueError: plotSeq.SetValue('Error in string '+plotSeq.GetValue()) return - G2plt.PlotLayers(G2frame,Layers,vals,plotDefaults) - + G2plt.PlotLayers(G2frame,Layers,vals,plotDefaults,firstCall=True) + Names = [' %s: %d,'%(layer['Name'],iL+1) for iL,layer in enumerate(Layers['Layers'])] plotSizer = wx.BoxSizer(wx.VERTICAL) Str = ' Using sequence nos. from:' @@ -9387,21 +9437,21 @@ def OnPlotSeq(event): lineSizer.Add(wx.StaticText(layerData,label=' Enter sequence of layers to plot:'),0,WACV) # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) plotSeq = wx.TextCtrl(layerData,value = '',style=wx.TE_PROCESS_ENTER) - plotSeq.Bind(wx.EVT_TEXT_ENTER,OnPlotSeq) + plotSeq.Bind(wx.EVT_TEXT_ENTER,OnPlotSeq) plotSeq.Bind(wx.EVT_KILL_FOCUS,OnPlotSeq) lineSizer.Add(plotSeq,0,WACV) plotSizer.Add(lineSizer,0) return plotSizer - + def StackSizer(): - + stackChoice = ['recursive','explicit',] seqChoice = ['random','list',] - + def OnStackType(event): newType = stackType.GetValue() if newType == data['Layers']['Stacking'][0]: - return + return data['Layers']['Stacking'][0] = newType if newType == 'recursive': data['Layers']['Stacking'][1] = 'infinite' @@ -9409,7 +9459,7 @@ def OnStackType(event): data['Layers']['Stacking'][1] = 'random' data['Layers']['Stacking'][2] = '250' wx.CallAfter(UpdateLayerData) - + def OnSeqType(event): newType = seqType.GetValue() if newType == data['Layers']['Stacking'][1]: @@ -9420,7 +9470,7 @@ def OnSeqType(event): else: #List data['Layers']['Stacking'][2] = '' wx.CallAfter(UpdateLayerData) - + def OnNumLayers(event): event.Skip() val = numLayers.GetValue() @@ -9435,7 +9485,7 @@ def OnNumLayers(event): except ValueError: pass numLayers.SetValue(data['Layers']['Stacking'][1]) - + def OnNumRan(event): event.Skip() val = numRan.GetValue() @@ -9447,7 +9497,7 @@ def OnNumRan(event): except ValueError: val = data['Layers']['Stacking'][2] numRan.SetValue(val) - + def OnStackList(event): event.Skip() stack = stackList.GetValue() @@ -9478,7 +9528,7 @@ def OnStackList(event): else: stack = 'Improbable sequence or bad string' stackList.SetValue(stack) - + stackSizer = wx.BoxSizer(wx.VERTICAL) stackSizer.Add(wx.StaticText(layerData,label=' Layer stacking parameters:'),0) if not Layers['Stacking']: @@ -9493,7 +9543,7 @@ def OnStackList(event): topLine.Add(wx.StaticText(layerData,label=' number of layers (<1022 or "infinite"): '),0,WACV) # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) numLayers = wx.TextCtrl(layerData,value=data['Layers']['Stacking'][1],style=wx.TE_PROCESS_ENTER) - numLayers.Bind(wx.EVT_TEXT_ENTER,OnNumLayers) + numLayers.Bind(wx.EVT_TEXT_ENTER,OnNumLayers) numLayers.Bind(wx.EVT_KILL_FOCUS,OnNumLayers) topLine.Add(numLayers,0,WACV) stackSizer.Add(topLine) @@ -9511,22 +9561,22 @@ def OnStackList(event): for name in Names: Str += name stackSizer.Add(wx.StaticText(layerData,label=Str[:-1]+' Repeat sequences can be used: e.g. 6*(1 2) '),0) - stackSizer.Add(wx.StaticText(layerData,label=' Zero probability sequences not allowed'),0) + stackSizer.Add(wx.StaticText(layerData,label=' Zero probability sequences not allowed'),0) stackList = wx.TextCtrl(layerData,value=Layers['Stacking'][2],size=(600,-1), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) - stackList.Bind(wx.EVT_TEXT_ENTER,OnStackList) + stackList.Bind(wx.EVT_TEXT_ENTER,OnStackList) stackList.Bind(wx.EVT_KILL_FOCUS,OnStackList) stackSizer.Add(stackList,0,wx.ALL|wx.EXPAND,8) else: #random topLine.Add(wx.StaticText(layerData,label=' Length of random sequence: '),0,WACV) # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) numRan = wx.TextCtrl(layerData,value=Layers['Stacking'][2],style=wx.TE_PROCESS_ENTER) - numRan.Bind(wx.EVT_TEXT_ENTER,OnNumRan) + numRan.Bind(wx.EVT_TEXT_ENTER,OnNumRan) numRan.Bind(wx.EVT_KILL_FOCUS,OnNumRan) topLine.Add(numRan,0,WACV) stackSizer.Add(topLine,0) return stackSizer - + Layers = data['Layers'] layerNames = [] Layers['allowedTrans'] = [] @@ -9540,9 +9590,9 @@ def OnStackList(event): except: pass mainSizer = wx.BoxSizer(wx.VERTICAL) - topSizer = wx.BoxSizer(wx.VERTICAL) + topSizer = wx.BoxSizer(wx.VERTICAL) bottomSizer = wx.BoxSizer(wx.VERTICAL) - headSizer = wx.BoxSizer(wx.HORIZONTAL) + headSizer = wx.BoxSizer(wx.HORIZONTAL) headSizer.Add(wx.StaticText(layerData,label=' Global layer description: '),0,WACV) if 'Sadp' in Layers: sadpPlot = wx.CheckBox(layerData,label=' Plot selected area diffraction?') @@ -9592,7 +9642,7 @@ def OnStackList(event): bottomSizer.Add(StackSizer()) mainSizer.Add(bottomSizer) SetPhaseWindow(G2frame.layerData,mainSizer,Scroll=Scroll) - + def OnCopyPhase(event): dlg = wx.FileDialog(G2frame, 'Choose GSAS-II project file', G2G.GetImportPath(G2frame), wildcard='GSAS-II project file (*.gpx)|*.gpx',style=wx.FD_OPEN| wx.FD_CHANGE_DIR) @@ -9616,7 +9666,7 @@ def OnCopyPhase(event): def OnLoadDIFFaX(event): if len(data['Layers']['Layers']): - dlg = wx.MessageDialog(G2frame,'Do you really want to replace the Layer data?','Load DIFFaX file', + dlg = wx.MessageDialog(G2frame,'Do you really want to replace the Layer data?','Load DIFFaX file', wx.YES_NO | wx.ICON_QUESTION) try: result = dlg.ShowModal() @@ -9633,53 +9683,50 @@ def OnLoadDIFFaX(event): finally: dlg.Destroy() wx.CallAfter(UpdateLayerData) - + def OnSimulate(event): debug = False #set True to run DIFFax to compare/debug (must be in bin) idebug = 0 if debug: idebug = 1 - wx.MessageBox(''' For use of DIFFaX, please cite: - A general recursion method for calculating diffracted intensities from crystals containing - planar faults, - M.M.J. Treacy, J.M. Newsam & M.W. Deem, Proc. Roy. Soc. Lond. A 433, 499-520 (1991) - doi: https://doi.org/10.1098/rspa.1991.0062 - ''',caption='DIFFaX',style=wx.ICON_INFORMATION) + wx.MessageBox(' For use of DIFFaX, please cite:\n\n'+ + G2G.GetCite('DIFFaX'), + caption='DIFFaX',style=wx.ICON_INFORMATION) ctrls = '' dlg = DIFFaXcontrols(G2frame,ctrls) if dlg.ShowModal() == wx.ID_OK: simCodes = dlg.GetSelection() else: return - + if 'PWDR' in simCodes[0]: #powder pattern data['Layers']['selInst'] = simCodes[1] - UseList = [] - for item in data['Histograms']: - if 'PWDR' in item: - UseList.append(item) + UseList = [item for item in data['Histograms'] if 'PWDR' in item] if not UseList: wx.MessageBox('No PWDR data for this phase to simulate',caption='Data error',style=wx.ICON_EXCLAMATION) return - dlg = wx.SingleChoiceDialog(G2frame,'Data to simulate','Select',UseList) - if dlg.ShowModal() == wx.ID_OK: - sel = dlg.GetSelection() - HistName = UseList[sel] + elif len(UseList) == 1: # don't ask questions when we know the answer! + HistName = UseList[0] else: - return - dlg.Destroy() + dlg = wx.SingleChoiceDialog(G2frame,'Data to simulate','Select',UseList) + if dlg.ShowModal() == wx.ID_OK: + sel = dlg.GetSelection() + HistName = UseList[sel] + else: + return + dlg.Destroy() G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,HistName) sample = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( G2frame,G2frame.PatternId, 'Sample Parameters')) scale = sample['Scale'][0] background = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( - G2frame,G2frame.PatternId, 'Background')) + G2frame,G2frame.PatternId, 'Background')) limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( G2frame,G2frame.PatternId, 'Limits'))[1] inst = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Instrument Parameters'))[0] if 'T' in inst['Type'][0]: wx.MessageBox("Can't simulate neutron TOF patterns yet",caption='Data error',style=wx.ICON_EXCLAMATION) - return + return profile = G2frame.GPXtree.GetItemPyData(G2frame.PatternId)[1] G2pwd.CalcStackingPWDR(data['Layers'],scale,background,limits,inst,profile,debug) if debug: @@ -9707,7 +9754,7 @@ def OnSimulate(event): G2pwd.CalcStackingSADP(data['Layers'],debug) wx.MessageBox('Simulation finished',caption='Stacking fault simulation',style=wx.ICON_EXCLAMATION) wx.CallAfter(UpdateLayerData) - + def OnFitLayers(event): print (' fit stacking fault model TBD') # import scipy.optimize as opt @@ -9716,9 +9763,9 @@ def OnFitLayers(event): wx.EndBusyCursor() wx.CallAfter(UpdateLayerData) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnSeqSimulate(event): - + cellSel = ['cellA','cellB','cellC','cellG'] transSel = ['TransP','TransX','TransY','TransZ'] ctrls = '' @@ -9750,14 +9797,14 @@ def OnSeqSimulate(event): G2frame,G2frame.PatternId, 'Sample Parameters')) scale = sample['Scale'][0] background = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( - G2frame,G2frame.PatternId, 'Background')) + G2frame,G2frame.PatternId, 'Background')) limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( G2frame,G2frame.PatternId, 'Limits'))[1] inst = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Instrument Parameters'))[0] if 'T' in inst['Type'][0]: wx.MessageBox("Can't simulate neutron TOF patterns yet",caption='Data error',style=wx.ICON_EXCLAMATION) - return + return profile = np.copy(G2frame.GPXtree.GetItemPyData(G2frame.PatternId)[1]) resultXY2 = [] resultXY = [np.vstack((profile[0],profile[1])),] #observed data @@ -9777,7 +9824,7 @@ def OnSeqSimulate(event): cellId = cellSel.index(pName) cell = Layers['Cell'] cell[cellId+1] = val - if laue in ['-3','-3m','6/m','6/mmm','4/m','4/mmm']: + if laue in ['-3','-3m','6/m','6/mmm','4/m','4/mmm']: cell[2] = cell[1] cell[7] = G2lat.calc_V(G2lat.cell2A(cell[1:7])) Layers['Cell'] = cell @@ -9810,10 +9857,10 @@ def OnSeqSimulate(event): data['Layers']['seqResults'] = [resultXY,resultXY2,simNames] wx.MessageBox('Sequential simulation finished',caption='Stacking fault simulation',style=wx.ICON_EXCLAMATION) wx.CallAfter(UpdateLayerData) - + #### Wave Data page ################################################################################ def UpdateWavesData(Scroll=0): - + generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] typeNames = {'Sfrac':' Site fraction','Spos':' Position','Sadp':' Thermal motion','Smag':' Magnetic moment'} @@ -9837,7 +9884,7 @@ def UpdateWavesData(Scroll=0): if waveData.GetSizer(): waveData.GetSizer().Clear(True) mainSizer = wx.BoxSizer(wx.VERTICAL) - topSizer = wx.BoxSizer(wx.HORIZONTAL) + topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(wx.StaticText(waveData,label=' Incommensurate propagation wave data: Select atom to edit: '),0,WACV) atNames = [] for atm in atomData: @@ -9846,12 +9893,12 @@ def UpdateWavesData(Scroll=0): return if G2frame.atmSel not in atNames: G2frame.atmSel = atNames[0] - + def OnAtmSel(event): Obj = event.GetEventObject() G2frame.atmSel = Obj.GetValue() RepaintAtomInfo() - + def RepaintAtomInfo(Scroll=0): G2frame.bottomSizer.Clear(True) G2frame.bottomSizer = ShowAtomInfo() @@ -9861,22 +9908,22 @@ def RepaintAtomInfo(Scroll=0): waveData.SetVirtualSize(mainSizer.GetMinSize()) waveData.Scroll(0,Scroll) G2frame.dataWindow.SendSizeEvent() - + def ShowAtomInfo(): - + global mapSel #so it can be seen below in OnWavePlot def AtomSizer(atom): global mapSel - + def OnShowWave(event): Obj = event.GetEventObject() - atom = Indx[Obj.GetId()] + atom = Indx[Obj.GetId()] Ax = Obj.GetValue() G2plt.ModulationPlot(G2frame,data,atom,Ax) - + atomSizer = wx.BoxSizer(wx.HORIZONTAL) atomSizer.Add(wx.StaticText(waveData,label= - ' Modulation data for atom: %s Site sym: %s'%(atom[0],atom[cs].strip())),0,WACV) + ' Modulation data for atom: %s Site sym: %s'%(atom[0],atom[cs].strip())),0,WACV) axchoice = ['x','y','z'] if len(D4Map['rho']): atomSizer.Add(wx.StaticText(waveData,label=' Show contour map for axis: '),0,WACV) @@ -9886,9 +9933,9 @@ def OnShowWave(event): Indx[mapSel.GetId()] = atom atomSizer.Add(mapSel,0,WACV) return atomSizer - + def WaveSizer(iatm,wavedata,Stype,typeName,Names): - + def OnWaveType(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] @@ -9900,24 +9947,24 @@ def OnWaveType(event): if len(waveTypes[Stype]) > 1: waveType.SetValue(atm[-1]['SS1'][Stype][0]) G2G.G2MessageBox(G2frame,'Warning: can only change wave type if no waves','Not changed') - + def OnAddWave(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] nt = numVals[Stype] if not len(atm[-1]['SS1'][item]): if waveTyp in ['ZigZag','Block','Crenel']: - nt = numVals[waveTyp] + nt = numVals[waveTyp] atm[-1]['SS1'][item] = [0,] atm[-1]['SS1'][item][0] = waveType.GetValue() atm[-1]['SS1'][item].append([[0.0 for i in range(nt)],False]) wx.CallAfter(RepaintAtomInfo,G2frame.waveData.GetScrollPos(wx.VERTICAL)) - + def OnRefWave(event): Obj = event.GetEventObject() item,iwave = Indx[Obj.GetId()] atm[-1]['SS1'][item][iwave+1][1] = not atm[-1]['SS1'][item][iwave+1][1] - + def OnDelWave(event): Obj = event.GetEventObject() item,iwave = Indx[Obj.GetId()] @@ -9925,13 +9972,13 @@ def OnDelWave(event): if len(atm[-1]['SS1'][item]) == 1: atm[-1]['SS1'][item][0] = 'Fourier' wx.CallAfter(RepaintAtomInfo,G2frame.waveData.GetScrollPos(wx.VERTICAL)) - + def OnWavePlot(invalid,value,tc): if len(D4Map['rho']): Ax = mapSel.GetValue() if Ax: G2plt.ModulationPlot(G2frame,data,atm,Ax) - + waveTyp,waveBlk = 'Fourier',[] if len(wavedata): waveTyp = wavedata[0] @@ -10014,13 +10061,13 @@ def OnWavePlot(invalid,value,tc): waveRef.Bind(wx.EVT_CHECKBOX, OnRefWave) Waves.Add(waveRef,0,WACV) if iwave < len(waveBlk)-1: - Waves.Add((5,5),0) + Waves.Add((5,5),0) else: waveDel = wx.Button(waveData,wx.ID_ANY,'Delete',style=wx.BU_EXACTFIT) Indx[waveDel.GetId()] = [Stype,iwave] waveDel.Bind(wx.EVT_BUTTON,OnDelWave) Waves.Add(waveDel,0,WACV) - waveSizer.Add(Waves) + waveSizer.Add(Waves) return waveSizer iatm = atNames.index(G2frame.atmSel) @@ -10034,8 +10081,8 @@ def OnWavePlot(invalid,value,tc): continue if generalData['Type'] != 'magnetic' and Stype == 'Smag': break - - atomSizer.Add(WaveSizer(iatm,atm[-1]['SS1'][Stype],Stype,typeNames[Stype],Labels[Stype])) + + atomSizer.Add(WaveSizer(iatm,atm[-1]['SS1'][Stype],Stype,typeNames[Stype],Labels[Stype])) return atomSizer atms = wx.ComboBox(waveData,value=G2frame.atmSel,choices=atNames, @@ -10046,7 +10093,7 @@ def OnWavePlot(invalid,value,tc): G2frame.bottomSizer = ShowAtomInfo() mainSizer.Add(G2frame.bottomSizer) SetPhaseWindow(G2frame.waveData,mainSizer,Scroll=Scroll) - + def OnWaveVary(event): generalData = data['General'] cx,ct,cs,cia = generalData['AtomPtrs'] @@ -10142,20 +10189,20 @@ def SetupDrawingData(): data['Drawing'] = drawingData if len(drawingData['Plane']) < 5: drawingData['Plane'].append([255,255,0]) - + def DrawAtomAdd(drawingData,atom): drawingData['Atoms'].append(G2mth.MakeDrawAtom(data,atom)) - + def OnRestraint(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return #indx = drawAtoms.GetSelectedRows() - restData = G2frame.GPXtree.GetItemPyData( + restData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints')) drawingData = data['Drawing'] generalData = data['General'] - Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) + Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) atomData = drawingData['Atoms'] atXYZ = [] atSymOp = [] @@ -10179,7 +10226,7 @@ def OnRestraint(event): angleData = {'wtFactor':1.0,'Angles':[],'Use':True} restData[PhaseName] = {} restData[PhaseName]['Angle'] = angleData - angleData['Angles'].append([atIndx,atSymOp,109.5,1.0]) + angleData['Angles'].append([atIndx,atSymOp,109.5,1.0]) elif event.GetId() == G2G.wxID_DRAWRESTRPLANE and len(indx) > 3: try: planeData = restData[PhaseName]['Plane'] @@ -10187,7 +10234,7 @@ def OnRestraint(event): planeData = {'wtFactor':1.0,'Planes':[],'Use':True} restData[PhaseName] = {} restData[PhaseName]['Plane'] = planeData - planeData['Planes'].append([atIndx,atSymOp,0.0,0.01]) + planeData['Planes'].append([atIndx,atSymOp,0.0,0.01]) elif event.GetId() == G2G.wxID_DRAWRESTRCHIRAL and len(indx) == 4: try: chiralData = restData[PhaseName]['Chiral'] @@ -10195,23 +10242,23 @@ def OnRestraint(event): chiralData = {'wtFactor':1.0,'Volumes':[],'Use':True} restData[PhaseName] = {} restData[PhaseName]['Chiral'] = chiralData - chiralData['Volumes'].append([atIndx,atSymOp,2.5,0.1]) + chiralData['Volumes'].append([atIndx,atSymOp,2.5,0.1]) else: print ('**** ERROR wrong number of atoms selected for this restraint') return - G2frame.GPXtree.SetItemPyData( + G2frame.GPXtree.SetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints'),restData) def OnDefineRB(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return indx.sort() - RBData = G2frame.GPXtree.GetItemPyData( + RBData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) drawingData = data['Drawing'] generalData = data['General'] - Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) + Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) atomData = drawingData['Atoms'] rbXYZ = [] rbType = [] @@ -10271,14 +10318,14 @@ def SetChoice(name,c,n=0): if test in parms: drawAtoms.SelectRow(row,True) drawingData['selectedAtoms'].append(row) - G2plt.PlotStructure(G2frame,data) + G2plt.PlotStructure(G2frame,data) dlg.Destroy() - + r,c = event.GetRow(),event.GetCol() if r < 0 and c < 0: for row in range(drawAtoms.GetNumberRows()): drawingData['selectedAtoms'].append(row) - drawAtoms.SelectRow(row,True) + drawAtoms.SelectRow(row,True) elif r < 0: #dclick on col label sel = -1 if drawAtoms.GetColLabelValue(c) == 'Style': @@ -10300,7 +10347,7 @@ def SetChoice(name,c,n=0): for r in range(len(atomData)): atomData[r][c] = parms drawAtoms.SetCellValue(r,c,parms) - dlg.Destroy() + dlg.Destroy() elif drawAtoms.GetColLabelValue(c) == 'Color': colors = wx.ColourData() colors.SetChooseFull(True) @@ -10349,7 +10396,7 @@ def SetChoice(name,c,n=0): dlg.Destroy() UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + def NextAtom(event): 'respond to a tab by cycling through the atoms' next = 0 @@ -10364,14 +10411,14 @@ def NextAtom(event): drawingData['selectedAtoms'] = drawAtoms.GetSelectedRows() G2plt.PlotStructure(G2frame,data) G2frame.Raise() - + def RowSelect(event): r,c = event.GetRow(),event.GetCol() if r < 0 and c < 0: if drawAtoms.IsSelection(): drawAtoms.ClearSelection() elif c < 0: #only row clicks - if event.ControlDown(): + if event.ControlDown(): if r in drawAtoms.GetSelectedRows(): drawAtoms.DeselectRow(r) else: @@ -10385,9 +10432,9 @@ def RowSelect(event): for row in range(ibeg,r+1): drawAtoms.SelectRow(row,True) else: - G2frame.GetStatusBar().SetStatusText('Use right mouse click to brng up Draw Atom editing options',1) + G2frame.GetStatusBar().SetStatusText('Use right mouse click to brng up Draw Atom editing options',1) drawAtoms.ClearSelection() - drawAtoms.SelectRow(r,True) + drawAtoms.SelectRow(r,True) drawingData['selectedAtoms'] = [] drawingData['selectedAtoms'] = drawAtoms.GetSelectedRows() G2plt.PlotStructure(G2frame,data) @@ -10411,7 +10458,7 @@ def RowSelect(event): generalData = data['General'] SetupDrawingData() drawingData = data['Drawing'] - SetDrawingDefaults(drawingData) + SetDrawingDefaults(drawingData) cx,ct,cs,ci = drawingData['atomPtrs'] atomData = drawingData['Atoms'] if atomStyle: @@ -10471,7 +10518,7 @@ def RowSelect(event): onRightClick = drawAtoms.setupPopup(lblList,callList) drawAtoms.Bind(wg.EVT_GRID_CELL_RIGHT_CLICK, onRightClick) drawAtoms.Bind(wg.EVT_GRID_LABEL_RIGHT_CLICK, onRightClick) - + try: drawAtoms.Bind(wg.EVT_GRID_TABBING, NextAtom) except: # patch: for pre-2.9.5 wx @@ -10493,7 +10540,7 @@ def RowSelect(event): attr.SetBackgroundColour(VERY_LIGHT_GREY) if colLabels[c] not in ['Style','Label','Color']: drawAtoms.SetColAttr(c,attr) - + mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(drawAtoms,1,wx.EXPAND) drawAtoms.SetScrollRate(10,10) # allow grid to scroll @@ -10505,7 +10552,7 @@ def RowSelect(event): def DrawAtomStyle(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return generalData = data['General'] @@ -10527,7 +10574,7 @@ def DrawAtomStyle(event): G2plt.PlotStructure(G2frame,data) def DrawAtomLabel(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return generalData = data['General'] @@ -10545,9 +10592,9 @@ def DrawAtomLabel(event): dlg.Destroy() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def DrawAtomColor(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return if len(indx) > 1: @@ -10567,7 +10614,7 @@ def DrawAtomColor(event): colors.SetChooseFull(True) dlg = wx.ColourDialog(None,colors) if dlg.ShowModal() == wx.ID_OK: - for i in range(len(atmColors)): + for i in range(len(atmColors)): atmColors[i] = dlg.GetColourData().GetColour()[:3] colorDict = dict(zip(atmTypes,atmColors)) for r in indx: @@ -10580,18 +10627,18 @@ def DrawAtomColor(event): dlg.Destroy() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def ResetAtomColors(event): generalData = data['General'] atomData = data['Drawing']['Atoms'] cx,ct,cs,ci = data['Drawing']['atomPtrs'] - for atom in atomData: + for atom in atomData: atNum = generalData['AtomTypes'].index(atom[ct]) atom[cs+2] = list(generalData['Color'][atNum]) UpdateDrawAtoms() drawAtoms.ClearSelection() - G2plt.PlotStructure(G2frame,data) - + G2plt.PlotStructure(G2frame,data) + def OnEditAtomRadii(event): DisAglCtls = {} generalData = data['General'] @@ -10603,10 +10650,10 @@ def OnEditAtomRadii(event): dlg.Destroy() generalData['DisAglCtls'] = DisAglCtls FindBondsDraw(data) - G2plt.PlotStructure(G2frame,data) - + G2plt.PlotStructure(G2frame,data) + def SetViewPoint(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return atomData = data['Drawing']['Atoms'] @@ -10615,16 +10662,16 @@ def SetViewPoint(event): pos += atomData[i][cx:cx+3] data['Drawing']['viewPoint'] = [list(pos/len(indx)),[indx[0],0]] G2plt.PlotStructure(G2frame,data) - + def noDuplicate(xyz,atomData): #be careful where this is used - it's slow cx = data['Drawing']['atomPtrs'][0] if True in [np.allclose(np.array(xyz),np.array(atom[cx:cx+3]),atol=0.0002) for atom in atomData]: return False else: return True - + def AddSymEquiv(event): - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) indx = getAtomSelections(drawAtoms,ct-1) if not indx: return indx.sort() @@ -10658,14 +10705,14 @@ def AddSymEquiv(event): atomOp = atom[cs-1] OprNum = ((Opr+1)+100*Cent)*(1-2*Inv) newOp = str(OprNum)+'+'+ \ - str(int(Cell[0]))+','+str(int(Cell[1]))+','+str(int(Cell[2])) + str(int(Cell[0]))+','+str(int(Cell[1]))+','+str(int(Cell[2])) atom[cs-1] = G2spc.StringOpsProd(atomOp,newOp,SGData) if cmx: #magnetic moment opNum = G2spc.GetOpNum(OprNum,SGData) mom = np.array(atom[cmx:cmx+3]) if SGData['SGGray']: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M) - else: + else: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M)*SpnFlp[opNum-1] if atom[cuij] == 'A': Uij = atom[cuij:cuij+6] @@ -10677,7 +10724,7 @@ def AddSymEquiv(event): UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def AddSphere(event=None,selection=None,radius=None,targets=None): cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) if selection: @@ -10714,9 +10761,9 @@ def AddSphere(event=None,selection=None,radius=None,targets=None): if Id < len(data['Drawing']['Atoms']): atom = data['Drawing']['Atoms'][Id] centers.append(atom[cx:cx+3]) - + ncent = len(centers) - pgbar = wx.ProgressDialog('Sphere of enclosure for %d atoms'%ncent,'Centers done=',ncent+1, + pgbar = wx.ProgressDialog('Sphere of enclosure for %d atoms'%ncent,'Centers done=',ncent+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -10741,7 +10788,7 @@ def AddSphere(event=None,selection=None,radius=None,targets=None): mom = np.array(atom[cmx:cmx+3]) if SGData['SGGray']: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M) - else: + else: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M)*SpnFlp[opNum-1] atom[cs-1] = str(item[2])+'+' atom[cuij:cuij+6] = item[1] @@ -10761,7 +10808,7 @@ def AddSphere(event=None,selection=None,radius=None,targets=None): UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def TransformSymEquiv(event): indx = getAtomSelections(drawAtoms) if not indx: return @@ -10816,13 +10863,13 @@ def TransformSymEquiv(event): UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def FillMolecule(event): - '''This is called by the Complete Molecule command. It adds a layer - of bonded atoms of the selected types for all selected atoms in - the Draw Atoms table. If the number of repetitions is greater than - one, the added atoms (other than H atoms, which are assumed to only - have one bond) are then searched for the next surrounding layer of + '''This is called by the Complete Molecule command. It adds a layer + of bonded atoms of the selected types for all selected atoms in + the Draw Atoms table. If the number of repetitions is greater than + one, the added atoms (other than H atoms, which are assumed to only + have one bond) are then searched for the next surrounding layer of bonded atoms. ''' indx = getAtomSelections(drawAtoms) @@ -10830,7 +10877,7 @@ def FillMolecule(event): generalData = data['General'] Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) atomTypes,radii = getAtomRadii(data) - + dlg = wx.Dialog(G2frame,wx.ID_ANY,'Addition criteria', pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) dlg.CenterOnParent() @@ -10859,7 +10906,7 @@ def FillMolecule(event): atm = G2G.G2CheckBox(dlg,item,params,item) atSizer.Add(atm,0,WACV) mainSizer.Add(atSizer,0) - + OkBtn = wx.Button(dlg,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, lambda x: dlg.EndModal(wx.ID_OK)) cancelBtn = wx.Button(dlg,-1,"Cancel") @@ -10870,7 +10917,7 @@ def FillMolecule(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) dlg.SetSizer(mainSizer) dlg.Fit() @@ -10878,7 +10925,7 @@ def FillMolecule(event): dlg.Destroy() return dlg.Destroy() - + try: indH = atomTypes.index('H') radii[indH] = 0.5 @@ -10921,7 +10968,7 @@ def FillMolecule(event): if not GoOn[0]: break print('pass {} processed {} atoms adding {}; Search time: {:.2f}s'.format( allrep+1,len(indx),len(addedAtoms),time.time()-time1)) - time1 = time.time() + time1 = time.time() rep += 1 allrep += 1 if len(addedAtoms) == 0: break @@ -10939,7 +10986,7 @@ def FillMolecule(event): dlg.Destroy() rep = 0 added = 1 - pgbar.Destroy() + pgbar.Destroy() if rep >= params['maxrep']: msg = "Exceeded number of repetitions. Continue?" dlg = wx.MessageDialog(G2frame,msg,caption='Continue?',style=wx.YES_NO) @@ -10956,7 +11003,7 @@ def FillMolecule(event): UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def FillCoordSphere(event=None,selection=None): if selection: indx = selection @@ -10972,7 +11019,7 @@ def FillCoordSphere(event=None,selection=None): indH = atomTypes.index('H') radii[indH] = 0.5 except: - pass + pass if indx: indx.sort() atomData = data['Drawing']['Atoms'] @@ -10982,7 +11029,7 @@ def FillCoordSphere(event=None,selection=None): SGData = generalData['SGData'] cellArray = G2lat.CellBlock(1) nind = len(indx) - pgbar = wx.ProgressDialog('Fill CN sphere for %d atoms'%nind,'Atoms done=',nind+1, + pgbar = wx.ProgressDialog('Fill CN sphere for %d atoms'%nind,'Atoms done=',nind+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -11011,7 +11058,7 @@ def FillCoordSphere(event=None,selection=None): GoOn = pgbar.Update(Ind,newmsg='Atoms done=%d'%(Ind)) if not GoOn[0]: break - pgbar.Destroy() + pgbar.Destroy() data['Drawing']['Atoms'] = atomData print('search time: %.3f'%(time.time()-time0)) UpdateDrawAtoms() @@ -11019,7 +11066,7 @@ def FillCoordSphere(event=None,selection=None): G2plt.PlotStructure(G2frame,data) else: G2G.G2MessageBox(G2frame,'Select atoms first') - + def FillCoordSphereNew(event): time0 = time.time() indx = getAtomSelections(drawAtoms) @@ -11042,7 +11089,7 @@ def FillCoordSphereNew(event): if 'Mx' in colLabels: cmx = colLabels.index('Mx') nind = len(indx) - pgbar = wx.ProgressDialog('Fill CN sphere for %d atoms'%nind,'Atoms done=',nind+1, + pgbar = wx.ProgressDialog('Fill CN sphere for %d atoms'%nind,'Atoms done=',nind+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -11053,13 +11100,13 @@ def FillCoordSphereNew(event): atomData += FindCoordination(ind,data,neighborArray,coordsArray,cmx,atomTypes) GoOn = pgbar.Update(Ind,newmsg='Atoms done=%d'%(Ind)) if not GoOn[0]: break - pgbar.Destroy() + pgbar.Destroy() data['Drawing']['Atoms'] = atomData print('search time: %.3f'%(time.time()-time0)) UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def FillUnitCell(event,selectAll=None): if selectAll is not None: indx = list(range(drawAtoms.NumberRows)) @@ -11078,7 +11125,7 @@ def FillUnitCell(event,selectAll=None): SGData = generalData['SGData'] SpnFlp = SGData.get('SpnFlp',[]) nind = len(indx) - pgbar = wx.ProgressDialog('Fill unit cell for %d atoms'%nind,'Atoms done=',nind+1, + pgbar = wx.ProgressDialog('Fill unit cell for %d atoms'%nind,'Atoms done=',nind+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -11100,7 +11147,7 @@ def FillUnitCell(event,selectAll=None): mom = np.array(atom[cmx:cmx+3]) if SGData['SGGray']: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M) - else: + else: atom[cmx:cmx+3] = np.inner(mom,M)*nl.det(M)*SpnFlp[opNum-1] atom[cs-1] = str(item[2])+'+' \ +str(item[3][0])+','+str(item[3][1])+','+str(item[3][2]) @@ -11117,12 +11164,12 @@ def FillUnitCell(event,selectAll=None): GoOn = pgbar.Update(Ind,newmsg='Atoms done=%d'%(Ind)) if not GoOn[0]: break - pgbar.Destroy() + pgbar.Destroy() UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - - def DrawAtomsDelete(event): + + def DrawAtomsDelete(event): indx = getAtomSelections(drawAtoms) if not indx: return indx.sort() @@ -11150,14 +11197,14 @@ def SelDrawList(event): indx = dlg.GetSelections() dlg.Destroy() if len(indx) == 0: return - drawAtoms.ClearSelection() + drawAtoms.ClearSelection() for row in indx: drawAtoms.SelectRow(row,True) G2plt.PlotStructure(G2frame,data) event.StopPropagation() def DrawLoadSel(event): - '''Copy selected atoms from the atoms list into the draw atoms list, making + '''Copy selected atoms from the atoms list into the draw atoms list, making sure not to duplicate any. ''' choices = [atm[0] for atm in data['Atoms']] @@ -11175,19 +11222,19 @@ def DrawLoadSel(event): for i in indx: found = False for dA in drawingData['Atoms']: - if (dA[ctD] == data['Atoms'][i][ct] and - dA[ctD-1] == data['Atoms'][i][ct-1] and - dA[cxD+3] == '1' and + if (dA[ctD] == data['Atoms'][i][ct] and + dA[ctD-1] == data['Atoms'][i][ct-1] and + dA[cxD+3] == '1' and np.sum((atmsXYZ[i]-dA[cxD:cxD+3])**2) < 0.001): found = True break - if not found: + if not found: DrawAtomAdd(drawingData,data['Atoms'][i]) UpdateDrawAtoms() drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) event.StopPropagation() - + def OnReloadDrawAtoms(event=None): atomData = data['Atoms'] cx,ct,cs,ci = data['General']['AtomPtrs'] @@ -11199,7 +11246,7 @@ def OnReloadDrawAtoms(event=None): G2plt.PlotStructure(G2frame,data) if event: event.StopPropagation() - + def DrawAtomsDeleteByIDs(IDs): atomData = data['Drawing']['Atoms'] cx,ct,cs,ci = data['General']['AtomPtrs'] @@ -11208,7 +11255,7 @@ def DrawAtomsDeleteByIDs(IDs): indx.reverse() for ind in indx: del atomData[ind] - + def ChangeDrawAtomsByIDs(colName,IDs,value): atomData = data['Drawing']['Atoms'] cx,ct,cs,ci = data['Drawing']['atomPtrs'] @@ -11221,7 +11268,7 @@ def ChangeDrawAtomsByIDs(colName,IDs,value): indx = G2mth.FindAtomIndexByIDs(atomData,ci+8,IDs) for ind in indx: atomData[ind][col] = value - + def OnDrawPlane(event): indx = getAtomSelections(drawAtoms) if len(indx) < 4: @@ -11243,13 +11290,13 @@ def OnDrawPlane(event): PlaneData['Atoms'] = xyz PlaneData['Cell'] = generalData['Cell'][1:] #+ volume G2stMn.BestPlane(PlaneData) - + def OnDrawDistVP(event): # distance to view point indx = getAtomSelections(drawAtoms,action='distance calc') if not indx: return generalData = data['General'] - Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) + Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) drawingData = data['Drawing'] viewPt = np.array(drawingData['viewPoint'][0]) print (' Distance from view point at %.3f %.3f %.3f to:'%(viewPt[0],viewPt[1],viewPt[2])) @@ -11263,7 +11310,7 @@ def OnDrawDistVP(event): Dx = np.array(atom[cx:cx+3])-viewPt dist = np.sqrt(np.sum(np.inner(Amat,Dx)**2,axis=0)) print ('Atom: %8s (%12s) distance = %.3f'%(atom[cn],atom[cs],dist)) - + def OnDrawDAT(event): #compute distance, angle, or torsion depending on number of selections indx = getAtomSelections(drawAtoms) @@ -11298,7 +11345,7 @@ def OnDrawDAT(event): DATData['pId'] = data['pId'] DATData['covData'] = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Covariance')) G2stMn.DisAglTor(DATData) - + def MapVoid(event): generalData = data['General'] rMax = max(data['General']['vdWRadii']) @@ -11315,11 +11362,11 @@ def MapVoid(event): mainSizer.Add(G2G.G2SliderWidget(voidDlg,voidPar,i,'Max '+i+' value: ',0.,xmax,100)) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(voidDlg,wx.ID_ANY,'Grid spacing (A)')) - hSizer.Add(G2G.ValidatedTxtCtrl(voidDlg,voidPar,'grid',nDig=(5,2), xmin=0.1, xmax=2., typeHint=float)) + hSizer.Add(G2G.ValidatedTxtCtrl(voidDlg,voidPar,'grid',nDig=(5,2), xmin=0.1, xmax=2., typeHint=float)) mainSizer.Add(hSizer) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(wx.StaticText(voidDlg,wx.ID_ANY,'Probe radius (A)')) - hSizer.Add(G2G.ValidatedTxtCtrl(voidDlg,voidPar,'probe',nDig=(5,2), xmin=0.1, xmax=2., typeHint=float)) + hSizer.Add(G2G.ValidatedTxtCtrl(voidDlg,voidPar,'probe',nDig=(5,2), xmin=0.1, xmax=2., typeHint=float)) mainSizer.Add(hSizer) def OnOK(event): voidDlg.EndModal(wx.ID_OK) @@ -11329,7 +11376,7 @@ def OnOK(event): voidDlg.EndModal(wx.ID_OK) btn.Bind(wx.EVT_BUTTON, OnOK) btn.SetDefault() btnsizer.AddButton(btn) - btn = wx.Button(voidDlg, wx.ID_CANCEL) + btn = wx.Button(voidDlg, wx.ID_CANCEL) btnsizer.AddButton(btn) btnsizer.Realize() mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) @@ -11346,9 +11393,9 @@ def OnOK(event): voidDlg.EndModal(wx.ID_OK) G2plt.PlotStructure(G2frame,data) def RandomizedAction(event): - '''perform a selected action on a random sequence from a selected - list of atoms. After each selection, one chooses if the - action should be performed on the selected atom + '''perform a selected action on a random sequence from a selected + list of atoms. After each selection, one chooses if the + action should be performed on the selected atom ''' ranDrwDict['opt'] = 1 ranDrwDict['optList'] = ['Delete selection', @@ -11406,7 +11453,7 @@ def RandomizedAction(event): dlg.Destroy() elif ranDrwDict['opt'] == 4: ranDrwDict['2call'] = FillCoordSphere - + indx = getAtomSelections(drawAtoms) if not indx: return ranDrwDict['atomList'] = list(indx) @@ -11435,10 +11482,10 @@ def RandomizedAction(event): ranDrwDict['msgWin'].CentreOnParent() ranDrwDict['msgWin'].Show() drawAtoms.Update() - + #### Draw Options page ################################################################################ def UpdateDrawOptions(): - def SlopSizer(): + def SlopSizer(): def OnCameraPos(): #old code # drawingData['cameraPos'] = cameraPos.GetValue() @@ -11456,7 +11503,7 @@ def OnCameraPos(): Zval.Validator.xmax = xmax #end new code G2plt.PlotStructure(G2frame,data) - + def OnMoveZ(event): #old code # drawingData['Zclip'] = Zclip.GetValue() @@ -11475,11 +11522,11 @@ def OnMoveZ(event): panel[names.index('viewPoint')].SetValue('%.3f %.3f %.3f'%(VP[0],VP[1],VP[2])) #end new code G2plt.PlotStructure(G2frame,data) - + def OnRadFactor(invalid,value,tc): FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) - + slopSizer = wx.BoxSizer(wx.HORIZONTAL) slideSizer = wx.FlexGridSizer(0,3,0,0) slideSizer.AddGrowableCol(2,1) @@ -11505,7 +11552,7 @@ def OnRadFactor(invalid,value,tc): G2frame.phaseDisplay.Zval = Zval G2frame.phaseDisplay.Zclip = Zclip OnCameraPos() - + slideSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,' Z step, '+Angstr+': '),0,WACV) Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0,size=valSize) slideSizer.Add(Zstep,0,WACV) @@ -11517,7 +11564,7 @@ def OnRadFactor(invalid,value,tc): MoveZ.Bind(wx.EVT_SPIN, OnMoveZ) MoveSizer.Add(MoveZ) slideSizer.Add(MoveSizer,1,wx.EXPAND|wx.RIGHT) - + G2G.G2SliderWidget( drawOptions,drawingData,'vdwScale', sizer=slideSizer,size=valSize, @@ -11554,7 +11601,7 @@ def OnRadFactor(invalid,value,tc): nDig=(10,2),iscale=100., label=' Mag. mom. mult.: ', onChange=G2plt.PlotStructure,onChangeArgs=[G2frame,data]) - + slideSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,' Bond search factor: '),0,WACV) slideSizer.Add(G2G.ValidatedTxtCtrl(drawOptions,drawingData,'radiusFactor', nDig=(10,2),xmin=0.1,xmax=1.2,size=valSize,OnLeave=OnRadFactor),0,WACV) @@ -11564,36 +11611,36 @@ def OnRadFactor(invalid,value,tc): slopSizer.Add((10,5),0) slopSizer.SetMinSize(wx.Size(350,10)) return slopSizer - + def ShowSizer(): def OnShowABC(event): drawingData['showABC'] = showABC.GetValue() G2plt.PlotStructure(G2frame,data) - + def OnShowUnitCell(event): drawingData['unitCellBox'] = unitCellBox.GetValue() G2plt.PlotStructure(G2frame,data) G2frame.GPXtree.UpdateSelection() # wx.CallAfter(UpdateDrawOptions) - + def OnShowHyd(event): drawingData['showHydrogen'] = showHydrogen.GetValue() FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) - + def OnShowRB(event): drawingData['showRigidBodies'] = showRB.GetValue() FindBondsDraw(data) G2plt.PlotStructure(G2frame,data) - + def OnSymFade(event): drawingData['SymFade'] = symFade.GetValue() G2plt.PlotStructure(G2frame,data) - + def OnShowVoids(event): drawingData['showVoids'] = showVoids.GetValue() G2plt.PlotStructure(G2frame,data) - + def OnViewPoint(event): event.Skip() Obj = event.GetEventObject() @@ -11605,14 +11652,14 @@ def OnViewPoint(event): Obj.SetValue('%.3f %.3f %.3f'%(VP[0],VP[1],VP[2])) drawingData['viewPoint'][0] = VP G2plt.PlotStructure(G2frame,data) - + def OnViewDir(event): event.Skip() Obj = event.GetEventObject() viewDir = Obj.GetValue().split() try: Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) -#reset view to stndard +#reset view to stndard drawingData['viewDir'] = [0,0,1] drawingData['oldxy'] = [] V0 = np.array([0,0,1]) @@ -11659,8 +11706,8 @@ def OnViewDir(event): Obj.SetValue('%.3f %.3f %.3f'%(VD[0],VD[1],VD[2])) drawingData['viewDir'] = VD G2plt.PlotStructure(G2frame,data) - - showSizer = wx.BoxSizer(wx.VERTICAL) + + showSizer = wx.BoxSizer(wx.VERTICAL) lineSizer = wx.BoxSizer(wx.HORIZONTAL) lineSizer.Add(wx.StaticText(drawOptions,label=' Background color:'),0,WACV) backColor = G2G.setColorButton(drawOptions,drawingData, 'backColor', @@ -11677,7 +11724,7 @@ def OnViewDir(event): lineSizer.Add(viewDir,0,WACV) showSizer.Add(lineSizer) showSizer.Add((0,5),0) - + lineSizer = wx.BoxSizer(wx.HORIZONTAL) showABC = wx.CheckBox(drawOptions,-1,label=' Show view point?') showABC.Bind(wx.EVT_CHECKBOX, OnShowABC) @@ -11697,26 +11744,26 @@ def OnViewDir(event): 'VPPeakDistRad','VPatomsExpandRad','VPatomsDistRad') showSizer.Add(mapSizer,0,wx.LEFT,20) showSizer.Add((0,5),0) - + line2Sizer = wx.BoxSizer(wx.HORIZONTAL) - + unitCellBox = wx.CheckBox(drawOptions,-1,label=' Show unit cell?') unitCellBox.Bind(wx.EVT_CHECKBOX, OnShowUnitCell) unitCellBox.SetValue(drawingData['unitCellBox']) line2Sizer.Add(unitCellBox,0,WACV) - + showHydrogen = wx.CheckBox(drawOptions,-1,label=' Show hydrogens?') showHydrogen.Bind(wx.EVT_CHECKBOX, OnShowHyd) showHydrogen.SetValue(drawingData['showHydrogen']) line2Sizer.Add(showHydrogen,0,WACV) - + showRB = wx.CheckBox(drawOptions,-1,label=' Show Rigid Bodies?') showRB.Bind(wx.EVT_CHECKBOX, OnShowRB) showRB.SetValue(drawingData['showRigidBodies']) line2Sizer.Add(showRB,0,WACV) - + showSizer.Add(line2Sizer) - + line3Sizer = wx.BoxSizer(wx.HORIZONTAL) symFade = wx.CheckBox(drawOptions,-1,label=' Fade sym equivs?') symFade.Bind(wx.EVT_CHECKBOX, OnSymFade) @@ -11727,23 +11774,23 @@ def OnViewDir(event): showVoids.SetValue(drawingData['showVoids']) line3Sizer.Add(showVoids,0,WACV) showSizer.Add(line3Sizer) - + return showSizer - + def MapSizer(): - + def OnShowMap(event): drawingData['showMap'] = showMap.GetValue() G2plt.PlotStructure(G2frame,data) - + def OnShowSlice(event): drawingData['showSlice'] = G2frame.phaseDisplay.showCS.GetSelection() G2frame.phaseDisplay.showCS.SetValue(slices[drawingData['showSlice']]) G2plt.PlotStructure(G2frame,data) - + def OnSliceSize(invalid,value,tc): G2plt.PlotStructure(G2frame,data) - + def OnContourMax(event): drawingData['contourMax'] = contourMax.GetValue()/100. contourMaxTxt.SetLabel(' Max.: '+'%.2f'%(drawingData['contourMax']*generalData['Map']['rhoMax'])) @@ -11756,7 +11803,7 @@ def OnContourMax(event): G2frame.phaseDisplay.showCS = wx.ComboBox(drawOptions,value=slices[drawingData['showSlice']], choices=slices,style=wx.CB_READONLY|wx.CB_DROPDOWN) G2frame.phaseDisplay.showCS.Bind(wx.EVT_COMBOBOX, OnShowSlice) - G2frame.phaseDisplay.showCS.SetValue(slices[drawingData['showSlice']]) + G2frame.phaseDisplay.showCS.SetValue(slices[drawingData['showSlice']]) line3Sizer.Add(G2frame.phaseDisplay.showCS,0,WACV) line3Sizer.Add(wx.StaticText(drawOptions,label=' Slice size 2X(2-20)A: '),0,WACV) line3Sizer.Add(G2G.ValidatedTxtCtrl(drawOptions,drawingData,'sliceSize',nDig=(10,2),xmin=2.0,xmax=20.0,OnLeave=OnSliceSize),0,WACV) @@ -11789,9 +11836,9 @@ def OnContourMax(event): mapSizer.Add(DistanceSettingSizer(drawingData, 'PeakDistRadius','atomsExpandRadius','atomsDistRadius'),0,wx.LEFT,20) return mapSizer - + def PlaneSizer(): - + def OnPlane(event): event.Skip() vals = plane.GetValue().split() @@ -11804,18 +11851,18 @@ def OnPlane(event): drawingData['Plane'][0] = hkl plane.SetValue('%5.3f %5.3f %5.3f'%(hkl[0],hkl[1],hkl[2])) G2plt.PlotStructure(G2frame,data) - + def OnShowPlane(event): drawingData['Plane'][1] = showPlane.GetValue() G2plt.PlotStructure(G2frame,data) - + def OnShowStack(event): drawingData['Plane'][2] = showStack.GetValue() G2plt.PlotStructure(G2frame,data) - + def OnPhase(invalid,value,tc): G2plt.PlotStructure(G2frame,data) - + planeSizer = wx.BoxSizer(wx.VERTICAL) planeSizer1 = wx.BoxSizer(wx.HORIZONTAL) planeSizer1.Add(wx.StaticText(drawOptions,label=' Plane: '),0,WACV) @@ -11844,28 +11891,28 @@ def OnPhase(invalid,value,tc): planeSizer.Add(planeSizer1) planeSizer.Add(planeSizer2) return planeSizer - + def DistanceSettingSizer(var,key1,key2,key3): '''Sizer to get distances to show''' def onLeave(*args,**kwargs): G2plt.PlotStructure(G2frame,data) for key in key1,key2,key3: - if key not in var: var[key] = 0 + if key not in var: var[key] = 0. mapSizer = wx.FlexGridSizer(0,3,5,5) mapSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,'Show Map points within:'),0,WACV) mapSizer.Add(G2G.ValidatedTxtCtrl(drawOptions,var,key1, xmin=0.0,xmax=5.0,nDig=(10,1),size=(50,-1), - OnLeave=onLeave)) + typeHint=float,OnLeave=onLeave)) mapSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,u"\u212B"),0,WACV) mapSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,'Show atoms within:'),0,WACV) mapSizer.Add(G2G.ValidatedTxtCtrl(drawOptions,var,key2, xmin=0.0,xmax=15.0,nDig=(10,1),size=(50,-1), - OnLeave=onLeave)) + typeHint=float,OnLeave=onLeave)) mapSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,u"\u212B"),0,WACV) mapSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,'Label distance to atoms within:'),0,WACV) mapSizer.Add(G2G.ValidatedTxtCtrl(drawOptions,var,key3, xmin=0.0,xmax=15.0,nDig=(10,1),size=(50,-1), - OnLeave=onLeave)) + typeHint=float,OnLeave=onLeave)) mapSizer.Add(wx.StaticText(drawOptions,wx.ID_ANY,u"\u212B"),0,WACV) return mapSizer @@ -11882,7 +11929,7 @@ def onLeave(*args,**kwargs): Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) SetupDrawingData() drawingData = data['Drawing'] - SetDrawingDefaults(drawingData) + SetDrawingDefaults(drawingData) G2frame.GetStatusBar().SetStatusText('Add h or v to View Dir to set vector horizontal or vertical',1) if drawOptions.GetSizer(): @@ -11898,7 +11945,7 @@ def onLeave(*args,**kwargs): mainSizer.Add(PlaneSizer(),0,) SetPhaseWindow(drawOptions,mainSizer) - + #### Deformation form factor routines ################################################################ def SetDefDist(event): @@ -11911,7 +11958,7 @@ def SetDefDist(event): generalData['DisAglCtls'] = dlg.GetData() UpdateDeformation(None) event.StopPropagation() - + def SelDeformAtom(event): 'select deformation atom using a filtered listbox' generalData = data['General'] @@ -11972,27 +12019,28 @@ def SelDeformAtom(event): fxchoice.append('Slater') if not radial: radial = 'Slater' - if 's' in orb: - data['Deformations'][Ids[indx]].append([orb,{'Ne':[float(orbs[orb]['Ne']),False],'kappa':[1.0,False]}]) #no sp. harm for s terms - else: #p, d or f - orbDict = {} - Order = 'spdf'.index(orb[-1]) - cofNames,cofSgns = G2lat.GenRBCoeff(sytsyms[indx],'1',Order) #sytsym, RBsym = '1' - cofNames = [name.replace('C','D') for name in cofNames] - cofTerms = {name:[0.0,False] for name in cofNames if str(Order) in name} - for name in cofNames: - if str(Order) in name and '0' not in name: - negname = name.replace(',',',-') - cofTerms.update({negname:[0.0,False]}) - orbDict.update(cofTerms) - orbDict.update({'Ne':[float(orbs[orb]['Ne']),False]}) - data['Deformations'][Ids[indx]].append([orb,orbDict]) + if 'Sl val' in orb: #valence; no harmonics + data['Deformations'][Ids[indx]].append([orb,{'Ne':[float(orbs[orb]['Ne']),False],'kappa':[1.0,False]}]) + break + # else: #p, d or f + # orbDict = {} + # Order = 'spdf'.index(orb[-1]) + # cofNames,cofSgns = G2lat.GenRBCoeff(sytsyms[indx],'1',Order) #sytsym, RBsym = '1' + # cofNames = [name.replace('C','D') for name in cofNames] + # cofTerms = {name:[0.0,False] for name in cofNames if str(Order) in name} + # for name in cofNames: + # if str(Order) in name and '0' not in name: + # negname = name.replace(',',',-') + # cofTerms.update({negname:[0.0,False]}) + # orbDict.update(cofTerms) + # orbDict.update({'Ne':[float(orbs[orb]['Ne']),False]}) + # data['Deformations'][Ids[indx]].append([orb,orbDict]) data['Deformations'][-Ids[indx]] = {'U':'X','V':'Y','UVmat':np.eye(3), 'MUV':"A: X'=U, Y'=(UxV)xU & Z'=UxV",'Radial':radial,'fxchoice':fxchoice} dlg.Destroy() if not len(indxes): return - drawAtoms.ClearSelection() + drawAtoms.ClearSelection() drawAtoms.SelectRow(indx,True) G2plt.PlotStructure(G2frame,data) UpdateDeformation(None) @@ -12026,7 +12074,7 @@ def OnDeformRef(event): Obj = event.GetEventObject() dId,oId,dkey = Indx[Obj.GetId()] deformationData[dId][oId][1][dkey][1] = not deformationData[dId][oId][1][dkey][1] - + def OnPlotAtm(event): Obj = event.GetEventObject() dId = Indx[Obj.GetId()] @@ -12042,7 +12090,7 @@ def OnDelAtm(event): dId = Indx[Obj.GetId()] del deformationData[dId] wx.CallAfter(UpdateDeformation,None) - + def OnMatSel(event): "Cartesian axes: A: X'=U, Y'=(UxV)xU & Z'=UxV,B: X'=U, Y'=UxV & Z'=Ux(UxV)" Obj = event.GetEventObject() @@ -12053,7 +12101,7 @@ def OnMatSel(event): UVmat = MakeUVmat(deformationData[-dId],U,V) data['Deformations'][-dId]['UVmat'] = UVmat wx.CallAfter(UpdateDeformation,dId) - + def OnUvec(event): "Cartesian axes: A: X'=U, Y'=(UxV)xU & Z'=UxV,B: X'=U, Y'=UxV & Z'=Ux(UxV)" Obj = event.GetEventObject() @@ -12116,7 +12164,7 @@ def NeSizer(deformation,orbSizer,dId,orb,Indx): def Dsizer(deformation,orbSizer,dId,orb,Indx): orbSizer.Add(wx.StaticText(deformation,label=item+':')) - orbSizer.Add(G2G.ValidatedTxtCtrl(deformation,orb[1][item],0,nDig=(8,3),xmin=-2.,xmax=2.)) + orbSizer.Add(G2G.ValidatedTxtCtrl(deformation,orb[1][item],0,nDig=(8,5),xmin=-1.,xmax=1.)) Tcheck = wx.CheckBox(deformation,-1,'Refine?') Tcheck.SetValue(orb[1][item][1]) Tcheck.Bind(wx.EVT_CHECKBOX,OnDeformRef) @@ -12127,6 +12175,7 @@ def OnNewHarm(event): Obj = event.GetEventObject() dId = Indx[Obj.GetId()] atom = atomData[AtLookUp[dId]] + sytsym = atom[cs].strip() for harm in data['Deformations'][dId]: if 'Sl' in harm[0]: Harm = harm @@ -12135,16 +12184,45 @@ def OnNewHarm(event): orders = [int(item[2]) for item in Hkeys if 'D' in item] if len(orders): Order = max(orders)+1 - cofNames,cofSgns = G2lat.GenRBCoeff(atom[cs],'1',Order) #sytsym, RBsym = '1' - cofNames = [name.replace('C','D') for name in cofNames] - cofTerms = {name:[0.0,False] for name in cofNames if str(Order) in name} - for name in cofNames: - if str(Order) in name and '0' not in name: - negname = name.replace(',',',-') - cofTerms.update({negname:[0.0,False]}) - Harm[1].update(cofTerms) + cofNames = [] + notFound = True + while notFound and Order < 6: + cofNames,cofSgns = G2lat.GenRBCoeff(sytsym,'1',Order) #sytsym, RBsym = '1' + cofNames = [name.replace('C','D') for name in cofNames] + for name in cofNames: + if name not in Hkeys: #new names found + notFound = False + Harm[1].update({name:[0.0,False]}) + # if '0' not in name: + # negname = name.replace(',',',-') + # Harm[1].update({negname:[0.0,False]}) + Order += 1 + wx.CallAfter(UpdateDeformation,dId) + + def OnDelHarm(event): + Obj = event.GetEventObject() + dId = Indx[Obj.GetId()] + for harm in data['Deformations'][dId]: + if 'Sl' in harm[0]: + Harm = harm + Hkeys = list(Harm[1].keys()) + if len(Hkeys) > 1: #always an "Ne" + maxord = max([int(item[2]) for item in Hkeys if 'D' in item]) + for item in Hkeys: + if 'D' in item and int(item[2]) == maxord: + del Harm[1][item] wx.CallAfter(UpdateDeformation,dId) + + def OnShowDef(event): + dId = Indx[event.GetEventObject().GetId()] + deformationData[-dId]['showDef'] = not deformationData[-dId]['showDef'] + G2plt.PlotStructure(G2frame,data) + def OnAtCol(event): + dId = Indx[event.GetEventObject().GetId()] + deformationData[-dId]['atColor'] = not deformationData[-dId]['atColor'] + G2plt.PlotStructure(G2frame,data) + # UpdateDeformation executable code starts here alpha = ['A','B','C','D','E','F','G','H',] generalData = data['General'] @@ -12176,7 +12254,7 @@ def OnNewHarm(event): topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) wx.CallAfter(G2frame.dataWindow.SetDataSize) - + mainSizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.BoxSizer(wx.HORIZONTAL) if dId is None: @@ -12201,6 +12279,28 @@ def OnNewHarm(event): neigh = G2mth.sortArray(neigh,2) #sort by dist lineSizer = wx.BoxSizer(wx.HORIZONTAL) lineSizer.Add(wx.StaticText(deformation,label=' For atom %s, site sym %s:'%(atom[ct-1],atom[cs])),0,WACV) + plotAtm = wx.Button(deformation,label='Plot') + plotAtm.Bind(wx.EVT_BUTTON,OnPlotAtm) + Indx[plotAtm.GetId()] = dId + lineSizer.Add(plotAtm,0,WACV) + deformationData[-dId]['showDef'] = deformationData[-dId].get('showDef',False) + deformationData[-dId]['atColor'] = deformationData[-dId].get('atColor',True) + showDef = wx.CheckBox(deformation,label='show def.?') + showDef.SetValue(deformationData[-dId]['showDef']) + Indx[showDef.GetId()] = dId + showDef.Bind(wx.EVT_CHECKBOX,OnShowDef) + lineSizer.Add(showDef,0,WACV) + atCol = wx.CheckBox(deformation,label='use atom colors?') + atCol.SetValue(deformationData[-dId]['atColor']) + Indx[atCol.GetId()] = dId + atCol.Bind(wx.EVT_CHECKBOX,OnAtCol) + lineSizer.Add(atCol,0,WACV) + delAtm = wx.Button(deformation,label='Delete') + delAtm.Bind(wx.EVT_BUTTON,OnDelAtm) + Indx[delAtm.GetId()] = dId + lineSizer.Add(delAtm,0,WACV) + mainSizer.Add(lineSizer) + lineSizer = wx.BoxSizer(wx.HORIZONTAL) names = [] if not len(neigh): lineSizer.Add(wx.StaticText(deformation,label=' No neighbors found; Do Set bond parms to expand search'),0,WACV) @@ -12209,26 +12309,11 @@ def OnNewHarm(event): lineSizer.Add(wx.StaticText(deformation,label=' Neighbors: '+str(names)),0,WACV) else: names = 'Too many neighbors - change atom radii to fix' - Nneigh = len(neigh) - if Nneigh > 2: - Nneigh += 1 - Nneigh = min(4,Nneigh) - UVchoice[dId] = ['X','Y','Z','X+Y','X+Y+Z','A','B','C','A+B','A+B+C'] + UVchoice[dId] = ['X','Y','Z','X+Y','X+Y+Z',] UVvec[dId] = [[1.,0.,0.],[0.,1.,0.],[0.,0.,1.],[1.,1.,0.]/sqt2,[1.,1.,1.]/sqt3,] - if Nneigh >= 1: - UVvec[dId] += [neigh[0][3]/neigh[0][2],] #A - if Nneigh >= 2: - UVvec[dId] += [neigh[1][3]/neigh[1][2],(neigh[0][3]+neigh[1][3])/np.sqrt(neigh[0][2]**2+neigh[1][2]**2),] #B, A+B - if Nneigh == 4: - UVvec[dId] += [(neigh[0][3]+neigh[1][3]+neigh[2][3])/np.sqrt(neigh[0][2]**2+neigh[1][2]**2+neigh[2][2]**2),] #A+B+C - plotAtm = wx.Button(deformation,label='Plot') - plotAtm.Bind(wx.EVT_BUTTON,OnPlotAtm) - Indx[plotAtm.GetId()] = dId - lineSizer.Add(plotAtm,0,WACV) - delAtm = wx.Button(deformation,label='Delete') - delAtm.Bind(wx.EVT_BUTTON,OnDelAtm) - Indx[delAtm.GetId()] = dId - lineSizer.Add(delAtm,0,WACV) + NUVvec,NUVchoice = G2lat.SetUVvec(neigh) + UVchoice[dId] += NUVchoice + UVvec[dId] += NUVvec mainSizer.Add(lineSizer) matSizer = wx.BoxSizer(wx.HORIZONTAL) Mchoice = ["A: X'=U, Y'=(UxV)xU & Z'=UxV","B: X'=U, Y'=UxV & Z'=Ux(UxV)"] @@ -12248,12 +12333,12 @@ def OnNewHarm(event): mainSizer.Add(matSizer) oriSizer = wx.BoxSizer(wx.HORIZONTAL) oriSizer.Add(wx.StaticText(deformation,label=' Select orbital U vector: '),0,WACV) - Uvec = wx.ComboBox(deformation,value=deformationData[-dId]['U'],choices=UVchoice[dId][:Nneigh+5],style=wx.CB_READONLY|wx.CB_DROPDOWN) + Uvec = wx.ComboBox(deformation,value=deformationData[-dId]['U'],choices=UVchoice[dId],style=wx.CB_READONLY|wx.CB_DROPDOWN) Uvec.Bind(wx.EVT_COMBOBOX,OnUvec) Indx[Uvec.GetId()] = dId oriSizer.Add(Uvec,0,WACV) oriSizer.Add(wx.StaticText(deformation,label=' Select orbital V vector: '),0,WACV) - Vvec = wx.ComboBox(deformation,value=deformationData[-dId]['V'],choices=UVchoice[dId][:Nneigh+5],style=wx.CB_READONLY|wx.CB_DROPDOWN) + Vvec = wx.ComboBox(deformation,value=deformationData[-dId]['V'],choices=UVchoice[dId],style=wx.CB_READONLY|wx.CB_DROPDOWN) Vvec.Bind(wx.EVT_COMBOBOX,OnVvec) Indx[Vvec.GetId()] = dId oriSizer.Add(Vvec,0,WACV) @@ -12262,6 +12347,10 @@ def OnNewHarm(event): newHarm.Bind(wx.EVT_BUTTON,OnNewHarm) Indx[newHarm.GetId()] = dId oriSizer.Add(newHarm,0,WACV) + delHarm = wx.Button(deformation,label='Delete highest harmonic') + delHarm.Bind(wx.EVT_BUTTON,OnDelHarm) + Indx[delHarm.GetId()] = dId + oriSizer.Add(delHarm,0,WACV) mainSizer.Add(oriSizer) G2G.HorizontalLine(mainSizer,deformation) mainSizer.Add(wx.StaticText(deformation,label=' Deformation parameters:')) @@ -12294,25 +12383,29 @@ def OnNewHarm(event): elif deformationData[-dId]['Radial'] == 'Slater' and 'Sl ' in orb[0]: orbSizer.Add(wx.StaticText(deformation,label=orb[0]+' Ne:')) NeSizer(deformation,orbSizer,dId,orb,Indx) + Np = 2 if 'kappa' in orb[1]: orbSizer.Add(wx.StaticText(deformation,label=' kappa:')) Kappa(deformation,orbSizer,dId,orb,Indx) - nItem = 0 + Np = 1 + for i in range(3*Np): orbSizer.Add((5,5),0) + iD = 1 for item in orb[1]: if 'D' in item: - nItem += 1 + if iD < int(item[2]): + iD = int(item[2]) + nItems = orbSizer.GetItemCount()%9 + if nItems: + nB = 9-nItems + for i in range(nB): orbSizer.Add((5,5),0) Dsizer(deformation,orbSizer,dId,orb,Indx) - if nItem in [2,4,6,8,10,12,14]: - for i in range(3): orbSizer.Add((5,5),0) - for i in range(3): orbSizer.Add((5,5),0) - continue mainSizer.Add(orbSizer) SetPhaseWindow(deformation,mainSizer) -#### Texture routines ################################################################################ +#### Texture routines ################################################################################ def UpdateTexture(): - + def SetSHCoef(): cofNames = G2lat.GenSHCoeff(SGData['SGLaue'],SamSym[textureData['Model']],textureData['Order']) newSHCoef = dict(zip(cofNames,np.zeros(len(cofNames)))) @@ -12321,7 +12414,7 @@ def SetSHCoef(): if cofName in cofNames: newSHCoef[cofName] = SHCoeff[cofName] return newSHCoef - + def OnShOrder(event): Obj = event.GetEventObject() # the Kaduk test: is Texture appropriate? Look for a 2nd histogram @@ -12353,7 +12446,7 @@ def OnShOrder(event): G2frame.GPXtree.UpdateSelection() # wx.CallLater(100,UpdateTexture) wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnShModel(event): Obj = event.GetEventObject() textureData['Model'] = Obj.GetValue() @@ -12361,51 +12454,51 @@ def OnShModel(event): G2frame.GPXtree.UpdateSelection() # wx.CallLater(100,UpdateTexture) wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnSHRefine(event): Obj = event.GetEventObject() textureData['SH Coeff'][0] = Obj.GetValue() - + def OnSHShow(event): Obj = event.GetEventObject() textureData['SHShow'] = Obj.GetValue() G2frame.GPXtree.UpdateSelection() # wx.CallLater(100,UpdateTexture) - + def OnProjSel(event): Obj = event.GetEventObject() G2frame.Projection = Obj.GetValue() wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnShoDet(event): Obj = event.GetEventObject() textureData['ShoDet'] = Obj.GetValue() - wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + wx.CallAfter(G2plt.PlotTexture,G2frame,data) + def OnColorSel(event): Obj = event.GetEventObject() G2frame.ContourColor = Obj.GetValue() wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnAngRef(event): Obj = event.GetEventObject() textureData[angIndx[Obj.GetId()]][0] = Obj.GetValue() - - def OnODFValue(invalid,value,tc): + + def OnODFValue(invalid,value,tc): wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnPfType(event): Obj = event.GetEventObject() textureData['PlotType'] = Obj.GetValue() G2frame.GPXtree.UpdateSelection() # wx.CallLater(100,UpdateTexture) wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnPFValue(event): event.Skip() Obj = event.GetEventObject() Saxis = Obj.GetValue().split() - if textureData['PlotType'] in ['Pole figure','Axial pole distribution','3D pole distribution']: + if textureData['PlotType'] in ['Pole figure','Axial pole distribution','3D pole distribution']: try: hkl = [int(Saxis[i]) for i in range(3)] except (ValueError,IndexError): @@ -12424,7 +12517,7 @@ def OnPFValue(event): Obj.SetValue('%3.1f %3.1f %3.1f'%(xyz[0],xyz[1],xyz[2])) textureData['PFxyz'] = xyz wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + def OnpopLA(event): pfName = PhaseName cell = generalData['Cell'][1:7] @@ -12434,7 +12527,7 @@ def OnpopLA(event): ODFln = G2lat.Flnh(SHCoef,phi,beta,SGData) pfName = PhaseName+'%d%d%d.gpf'%(PH[0],PH[1],PH[2]) pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose popLA pole figure file name', pth, pfName, + dlg = wx.FileDialog(G2frame, 'Choose popLA pole figure file name', pth, pfName, 'popLA file (*.gpf)|*.gpf',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -12457,7 +12550,7 @@ def OnpopLA(event): iBeg = i*18 iFin = iBeg+18 np.savetxt(pf,Z[iBeg:iFin],fmt='%4d',newline='') - pf.write('\n') + pf.write('\n') pf.close() print (' popLA %d %d %d pole figure saved to %s'%(PH[0],PH[1],PH[2],pfFile)) @@ -12475,16 +12568,16 @@ def OnCSV(event): IODFln = G2lat.Glnh(SHCoef,psi,gam,SamSym[textureData['Model']]) pfName = PhaseName+'%d%d%dIPF.csv'%(int(PX[0]),int(PX[1]),int(PX[2])) pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose CSV inverse pole figure file name', pth, pfName, + dlg = wx.FileDialog(G2frame, 'Choose CSV inverse pole figure file name', pth, pfName, 'CSV file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) - else: + else: PH = np.array(textureData['PFhkl']) phi,beta = G2lat.CrsAng(PH,cell,SGData) SHCoef = textureData['SH Coeff'][1] ODFln = G2lat.Flnh(SHCoef,phi,beta,SGData) pfName = PhaseName+'%d%d%dPF.csv'%(PH[0],PH[1],PH[2]) pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose CSV pole figure file name', pth, pfName, + dlg = wx.FileDialog(G2frame, 'Choose CSV pole figure file name', pth, pfName, 'CSV file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -12508,7 +12601,7 @@ def OnCSV(event): for i,row in enumerate(Z): pf.write('%8d, '%(i*5)) np.savetxt(pf,row,fmt='%10.4f,',newline='') - pf.write('\n') + pf.write('\n') pf.close() print (' %s %d %d %d inverse pole figure saved to %s'%(PhaseName,int(PX[0]),int(PX[1]),int(PX[2]),pfFile)) else: @@ -12524,12 +12617,12 @@ def OnCSV(event): for i,row in enumerate(Z): pf.write('%8d, '%(i*5)) np.savetxt(pf,row,fmt='%10.4f,',newline='') - pf.write('\n') + pf.write('\n') pf.close() print (' %s %d %d %d pole figure saved to %s'%(PhaseName,PH[0],PH[1],PH[2],pfFile)) def SHPenalty(Penalty): - + def OnHKLList(event): event.Skip() dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select penalty hkls', @@ -12545,9 +12638,9 @@ def OnHKLList(event): dlg.Destroy() G2frame.GPXtree.UpdateSelection() # wx.CallLater(100,UpdateTexture) - + A = G2lat.cell2A(generalData['Cell'][1:7]) - hkls = G2lat.GenPfHKLs(10,SGData,A) + hkls = G2lat.GenPfHKLs(10,SGData,A) shPenalty = wx.BoxSizer(wx.HORIZONTAL) shPenalty.Add(wx.StaticText(Texture,wx.ID_ANY,' Negative MRD penalty list: '),0,WACV) shPenalty.Add(wx.ComboBox(Texture,value=Penalty[0][0],choices=Penalty[0], @@ -12559,14 +12652,14 @@ def OnHKLList(event): shToler = G2G.ValidatedTxtCtrl(Texture,Penalty,1,nDig=(10,2),xmin=0.001) shPenalty.Add(shToler,0,WACV) return shPenalty - + def OnProj(event): Obj = event.GetEventObject() generalData['3Dproj'] = Obj.GetValue() - wx.CallAfter(G2plt.PlotTexture,G2frame,data) - + wx.CallAfter(G2plt.PlotTexture,G2frame,data) + # UpdateTexture executable starts here - generalData = data['General'] + generalData = data['General'] SGData = generalData['SGData'] try: textureData = generalData['SH Texture'] @@ -12588,7 +12681,7 @@ def OnProj(event): keyList = G2frame.GetHistogramNames(['PWDR',]) UseList = data['Histograms'] HistsInPhase = [name for name in keyList if name in UseList] - + textureData['det Angles'] = [] for hist in HistsInPhase: pId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist) #only use 1st histogram @@ -12603,8 +12696,8 @@ def OnProj(event): phi = 180.-phi gam += 180. gam %= 360. - textureData['det Angles'].append([hist,phi,gam]) - + textureData['det Angles'].append([hist,phi,gam]) + G2frame.GetStatusBar().SetStatusText('',1) if Texture.GetSizer(): Texture.GetSizer().Clear(True) @@ -12670,7 +12763,7 @@ def OnProj(event): mainSizer.Add((0,5),0) PTSizer = wx.FlexGridSizer(0,5,5,5) PTSizer.Add(wx.StaticText(Texture,-1,' Texture plot type: '),0,WACV) - choices = ['Axial pole distribution','Pole figure','Inverse pole figure','3D pole distribution'] + choices = ['Axial pole distribution','Pole figure','Inverse pole figure','3D pole distribution'] pfType = wx.ComboBox(Texture,-1,value=str(textureData['PlotType']),choices=choices, style=wx.CB_READONLY|wx.CB_DROPDOWN) pfType.Bind(wx.EVT_COMBOBOX,OnPfType) @@ -12696,7 +12789,7 @@ def OnProj(event): projType.Bind(wx.EVT_COMBOBOX, OnProj) PTSizer.Add(projType,0,WACV) PTSizer.Add((0,5),0) - + if textureData['PlotType'] in ['Pole figure','Axial pole distribution','3D pole distribution']: PTSizer.Add(wx.StaticText(Texture,-1,' Pole figure HKL: '),0,WACV) PH = textureData['PFhkl'] @@ -12767,11 +12860,11 @@ def OnHklfAdd(event): result = CheckAddHKLF(G2frame,data) if result is None: return wx.CallAfter(G2ddG.UpdateDData,G2frame,DData,data) - + def OnDataUse(event): # hist = G2frame.hist if data['Histograms']: - dlg = G2G.G2MultiChoiceDialog(G2frame, 'Use histograms', + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Use histograms', 'Use which histograms?',G2frame.dataWindow.HistsInPhase) try: if dlg.ShowModal() == wx.ID_OK: @@ -12780,12 +12873,12 @@ def OnDataUse(event): if Id in sel: data['Histograms'][item]['Use'] = True else: - data['Histograms'][item]['Use'] = False + data['Histograms'][item]['Use'] = False finally: dlg.Destroy() wx.CallAfter(G2ddG.UpdateDData,G2frame,DData,data) - + def OnDataCopy(event): hist = G2frame.hist keyList = G2frame.dataWindow.HistsInPhase[:] @@ -12796,8 +12889,8 @@ def OnDataCopy(event): sourceDict = copy.deepcopy(data['Histograms'][hist]) if 'HKLF' in sourceDict['Histogram']: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR - copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','newLeBail','Layer Disp'] + else: #PWDR + copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','Layer Disp'] copyNames += ['Scale','Fix FXU','FixedSeqVars'] copyDict = {} for name in copyNames: @@ -12811,14 +12904,14 @@ def OnDataCopy(event): data['Histograms'][keyList[sel]].update(copy.deepcopy(copyDict)) finally: dlg.Destroy() - + def OnDataCopyFlags(event): hist = G2frame.hist sourceDict = copy.deepcopy(data['Histograms'][hist]) copyDict = {} if 'HKLF' in sourceDict['Histogram']: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR + else: #PWDR copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','Layer Disp'] copyNames += ['Scale','Fix FXU','FixedSeqVars'] babNames = ['BabA','BabU'] @@ -12848,7 +12941,7 @@ def OnDataCopyFlags(event): for bab in babNames: copyDict[name][bab] = sourceDict[name][bab][1] elif name == 'Fix FXU' or name == 'FixedSeqVars': - copyDict[name] = copy.deepcopy(sourceDict[name]) + copyDict[name] = copy.deepcopy(sourceDict[name]) keyList = G2frame.dataWindow.HistsInPhase[:] if hist in keyList: keyList.remove(hist) if not keyList: @@ -12890,10 +12983,10 @@ def OnDataCopyFlags(event): for bab in babNames: data['Histograms'][item][name][bab][1] = copy.deepcopy(copyDict[name][bab]) elif name == 'Fix FXU' or name == 'FixedSeqVars': - data['Histograms'][item][name] = copy.deepcopy(sourceDict[name]) + data['Histograms'][item][name] = copy.deepcopy(sourceDict[name]) finally: dlg.Destroy() - + def OnSelDataCopy(event): '''Select HAP items to copy from one Phase/Hist to other(s) ''' @@ -12906,9 +12999,9 @@ def OnSelDataCopy(event): return if 'HKLF' in sourceDict['Histogram']: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR - copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','newLeBail','Layer Disp'] - copyNames += ['Scale','Fix FXU','FixedSeqVars'] + else: #PWDR + copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','Layer Disp'] + copyNames += ['Scale','Fix FXU','FixedSeqVars'] dlg = G2G.G2MultiChoiceDialog(G2frame,'Select which parameters to copy', 'Select phase data parameters', copyNames) selectedItems = [] @@ -12929,10 +13022,10 @@ def OnSelDataCopy(event): for sel in dlg.GetSelections(): data['Histograms'][keyList[sel]].update(copy.deepcopy(copyDict)) finally: - dlg.Destroy() + dlg.Destroy() def OnSelDataRead(event): - '''Select HAP items to copy from another GPX file to current + '''Select HAP items to copy from another GPX file to current phase & hist ''' sourceDict = data['Histograms'][G2frame.hist] @@ -12987,8 +13080,8 @@ def OnSelDataRead(event): return if 'HKLF' in histNam: copyNames = ['Extinction','Babinet','Flack','Twins'] - else: #PWDR - copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','newLeBail','Layer Disp'] + else: #PWDR + copyNames = ['Pref.Ori.','Size','Mustrain','HStrain','Extinction','Babinet','LeBail','Layer Disp'] copyNames += ['Scale','Fix FXU','FixedSeqVars'] dlg = G2G.G2MultiChoiceDialog(G2frame,'Select which parameters to copy', 'Select phase data parameters', copyNames) @@ -13018,7 +13111,7 @@ def OnPwdrAdd(event): if name not in keyList and 'PWDR' in name: TextList.append(name) item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) - if not TextList: + if not TextList: G2G.G2MessageBox(G2frame,'No histograms') return dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select powder histograms to use', @@ -13034,24 +13127,24 @@ def OnPwdrAdd(event): Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Instrument Parameters'))[0] data['Histograms'][histoName] = { 'Histogram':histoName,'Show':False, - 'LeBail':False,'newLeBail':False, + 'LeBail':False, 'Scale':[1.0,False],'Pref.Ori.':['MD',1.0,False,[0,0,1],0,{},['',],0.1],'Type':Inst['Type'][0], 'Size':['isotropic',[1.,1.,1.],[False,False,False],[0,0,1], [1.,1.,1.,0.,0.,0.],6*[False,]], 'Mustrain':['isotropic',[1000.0,1000.0,1.0],[False,False,False],[0,0,1], NShkl*[0.01,],NShkl*[False,]], 'HStrain':[NDij*[0.0,],NDij*[False,]], - 'Layer Disp':[0.0,False], + 'Layer Disp':[0.0,False], 'Extinction':[0.0,False], 'Flack':[0.0,False], 'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]}, 'Fix FXU':' ','FixedSeqVars':[]} refList = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Reflection Lists')) - refList[generalData['Name']] = {} + refList[generalData['Name']] = {} wx.CallAfter(G2ddG.UpdateDData,G2frame,DData,data) finally: dlg.Destroy() - + def OnDataDelete(event): if G2frame.dataWindow.HistsInPhase: DelList = [] @@ -13061,7 +13154,7 @@ def OnDataDelete(event): opts = extraOpts else: opts = {} - dlg = G2G.G2MultiChoiceDialog(G2frame, + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select histogram(s) to remove \nfrom this phase:', 'Remove histograms', G2frame.dataWindow.HistsInPhase, extraOpts=opts) @@ -13071,7 +13164,7 @@ def OnDataDelete(event): finally: dlg.Destroy() if extraOpts['value_0']: - for p in pd: + for p in pd: for i in DelList: if i in pd[p]['Histograms']: del pd[p]['Histograms'][i] else: @@ -13083,9 +13176,9 @@ def OnDataDelete(event): # G2frame.GPXtree.SelectItem(G2frame.root) # G2frame.GPXtree.SelectItem(TId) # UpdatePhaseData(G2frame,Item,data) - + def OnDataApplyStrain(event): - SGData = data['General']['SGData'] + SGData = data['General']['SGData'] DijVals = data['Histograms'][G2frame.hist]['HStrain'][0][:] # apply the Dij values to the reciprocal cell newA = [] @@ -13144,9 +13237,9 @@ def OnThermSel(event): # spnId = spnSelect.GetSelection() wx.CallAfter(FillRigidBodyGrid,True,vecId=vecId,resId=resId) ##,spnId=spnId G2plt.PlotStructure(G2frame,data) - + def ThermDataSizer(RBObj,rbType): - + def OnThermval(invalid,value,tc): Cart = G2mth.UpdateRBXYZ(Bmat,RBObj,RBData,rbType)[1] Uout = G2mth.UpdateRBUIJ(Bmat,Cart,RBObj) @@ -13157,12 +13250,12 @@ def OnThermval(invalid,value,tc): else: data['Atoms'][AtLookUp[Id]][cia+2:cia+8] = Uout[i][2:8] G2plt.PlotStructure(G2frame,data) - + def OnTLSRef(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] RBObj['ThermalMotion'][2][item] = Obj.GetValue() - + thermSizer = wx.FlexGridSizer(0,9,5,5) model = RBObj['ThermalMotion'] if model[0] == 'Uiso': @@ -13183,17 +13276,17 @@ def OnTLSRef(event): Indx[Tcheck.GetId()] = i thermSizer.Add(Tcheck,0,WACV) return thermSizer - + def LocationSizer(RBObj,rbType): - + def OnOrigRef(event): RBObj['Orig'][1] = Ocheck.GetValue() - + def OnOrienRef(event): RBObj['Orient'][1] = Qcheck.GetValue() - + def OnOrigX(invalid,value,tc): - '''Called when the position info is changed (vector + '''Called when the position info is changed (vector or azimuth) ''' newXYZ = G2mth.UpdateRBXYZ(Bmat,RBObj,RBData,rbType)[0] @@ -13209,11 +13302,11 @@ def OnOrigX(invalid,value,tc): data['Drawing']['Atoms'] = [] UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + def OnOrien(*args, **kwargs): - '''Called when the orientation info is changed (vector - or azimuth). When called after a move of RB with alt key pressed, - an optional keyword arg is provided with the mode to draw atoms + '''Called when the orientation info is changed (vector + or azimuth). When called after a move of RB with alt key pressed, + an optional keyword arg is provided with the mode to draw atoms (lines, ball & sticks,...) ''' try: @@ -13241,7 +13334,7 @@ def OnOrien(*args, **kwargs): G2plt.PlotStructure(G2frame,data) except ValueError: pass - + SGData = data['General']['SGData'] rbSizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.FlexGridSizer(0,6,5,5) @@ -13302,10 +13395,10 @@ def OnOrien(*args, **kwargs): rbSizer.Add(topSizer) rbSizer.Add(sytsymtxt) return rbSizer - + def SpnrbSizer(RBObj,spnIndx): '''Displays details for selected spinning rigid body''' - + def OnDelSpnRB(event): Obj = event.GetEventObject() RBId = Indx[Obj.GetId()] @@ -13322,18 +13415,18 @@ def OnDelSpnRB(event): data['Drawing']['Atoms'] = [] G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True) - + def OnSymRadioSet(event): - '''Set the polar axis for the sp. harm. as - RBdata['Spin'][RBId]['symAxis']. This may never be - set, so use RBdata['Spin'][RBId].get('symAxis') to - access this so the default value is [0,0,1]. + '''Set the polar axis for the sp. harm. as + RBdata['Spin'][RBId]['symAxis']. This may never be + set, so use RBdata['Spin'][RBId].get('symAxis') to + access this so the default value is [0,0,1]. ''' Obj = event.GetEventObject() axis = ([1,0,0],[0,1,0],[0,0,1],[1,1,0],[1,1,1])[Obj.GetSelection()] RBObj['symAxis'] = axis G2plt.PlotStructure(G2frame,data) - + def OnAddShell(event): rbNames = [] rbIds = {} @@ -13356,17 +13449,18 @@ def OnAddShell(event): return finally: dlg.Destroy() - + rbData = RBData['Spin'][rbIds[selection]] RBObj['RBId'].append(rbIds[selection]) RBObj['SHC'].append({}) - for name in ['atColor','atType','Natoms','nSH','RBname','RBsym','Radius']: + for name in ['atColor','atType','Natoms','nSH','RBname','RBsym']: RBObj[name].append(rbData[name]) + RBObj['Radius'].append([1.0,False]) RBObj['hide'].append(False) RBData['Spin'][rbIds[selection]]['useCount'] += 1 G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True,spnId=rbId) - + def SHsizer(): def OnSHOrder(event): Obj = event.GetEventObject() @@ -13375,7 +13469,7 @@ def OnSHOrder(event): RBObj['SHC'][iSh] = SetSHCoef(iSh,RBObj['nSH'][iSh]) G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True,spnId=rbId) - + def SetSHCoef(iSh,Order): Sytsym = RBObj['SytSym'] cofNames,cofSgns = G2lat.GenRBCoeff(Sytsym,RBObj['RBsym'][iSh],Order) @@ -13386,20 +13480,20 @@ def SetSHCoef(iSh,Order): if cofName in newSHcoef: newSHcoef[cofName] = SHcoef[cofName] return newSHcoef - + def OnSchRef(event): Obj = event.GetEventObject() iSh,name = Indx[Obj.GetId()] RBObj['SHC'][iSh][name][2] = not RBObj['SHC'][iSh][name][2] - + def OnRadRef(event): Obj = event.GetEventObject() iSh = Indx[Obj.GetId()] RBObj['Radius'][iSh][1] = not RBObj['Radius'][iSh][1] - + def NewSHC(invalid,value,tc): G2plt.PlotStructure(G2frame,data) - + def OnDelShell(event): Obj = event.GetEventObject() iSh = Indx[Obj.GetId()] @@ -13410,8 +13504,8 @@ def OnDelShell(event): del RBObj[name][iSh] G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True,spnId=rbId) - - + + shSizer = wx.BoxSizer(wx.VERTICAL) for iSh,nSh in enumerate(RBObj['nSH']): #patch @@ -13419,7 +13513,7 @@ def OnDelShell(event): RBObj['Radius'] = [[1.0,False] for i in range(len(RBObj['nSH']))] #end patch rbId = RBObj['RBId'][iSh] - RBObj['atType'][iSh] = RBData['Spin'][rbId]['atType'] + RBObj['atType'][iSh] = RBData['Spin'][rbId]['atType'] RBObj['atColor'][iSh] = G2elem.GetAtomInfo(RBObj['atType'][iSh])['Color'] #correct atom color for shell if iSh: subLine = wx.BoxSizer(wx.HORIZONTAL) @@ -13459,9 +13553,9 @@ def OnDelShell(event): if not RBObj['nSH'][iSh]: shSizer.Add(wx.StaticText(RigidBodies, label=' Select harmonic order or try different equivalent position')) - elif len(RBObj['SHC'][iSh]) > 12: + elif len(RBObj['SHC'][iSh]) > 24: shSizer.Add(wx.StaticText(RigidBodies, - label=' WARNING: More than 12 terms found; use lower harmonic order')) + label=' WARNING: More than 24 terms found; use lower harmonic order')) else: shcSizer = wx.FlexGridSizer(0,9,5,5) for item in RBObj['SHC'][iSh]: @@ -13470,7 +13564,7 @@ def OnDelShell(event): typeHint=float,size=(70,-1),OnLeave=NewSHC)) schref = wx.CheckBox(RigidBodies,label=' refine? ') schref.SetValue(RBObj['SHC'][iSh][item][2]) - schref.Bind(wx.EVT_CHECKBOX,OnSchRef) + schref.Bind(wx.EVT_CHECKBOX,OnSchRef) Indx[schref.GetId()] = iSh,item shcSizer.Add(schref) shSizer.Add(shcSizer) @@ -13482,6 +13576,14 @@ def OnHideSh(event): RBObj['hide'][iSh] = not RBObj['hide'][iSh] G2plt.PlotStructure(G2frame,data) + def OnAtColor(event): + RBObj['useAtColor'] = not RBObj['useAtColor'] + G2plt.PlotStructure(G2frame,data) + + def OnFadeShell(event): + RBObj['fadeSh'] = not RBObj['fadeSh'] + G2plt.PlotStructure(G2frame,data) + RBObj['hide'] = RBObj.get('hide',[False for i in range(len(RBObj['atType']))]) rbId = RBObj['RBId'][0] atId = RBObj['Ids'][0] @@ -13507,7 +13609,7 @@ def OnHideSh(event): hidesh.Bind(wx.EVT_CHECKBOX,OnHideSh) Indx[hidesh.GetId()] = 0 topLine.Add(hidesh,0,WACV) - sprbSizer.Add(topLine) + sprbSizer.Add(wx.StaticText(RigidBodies,label='Spinning RB orientation parameters for %s:'%RBObj['RBname'][0])) sprbSizer.Add(LocationSizer(RBObj,'Spin')) choices = [' x ',' y ',' z ','x+y','x+y+z'] RBObj['symAxis'] = RBObj.get('symAxis',[0,0,1]) #set default as 'z' @@ -13517,16 +13619,30 @@ def OnHideSh(event): symRadioSet.Bind(wx.EVT_RADIOBOX, OnSymRadioSet) Indx[symRadioSet.GetId()] = rbId sprbSizer.Add(symRadioSet) + plotLine = wx.BoxSizer(wx.HORIZONTAL) + RBObj['useAtColor'] = RBObj.get('useAtColor',True) + atColor = wx.CheckBox(RigidBodies,label='Use atom color?') + atColor.SetValue(RBObj['useAtColor']) + atColor.Bind(wx.EVT_CHECKBOX,OnAtColor) + plotLine.Add(atColor,0,WACV) + RBObj['fadeSh'] = RBObj.get('fadeSh',True) + fadeShell = wx.CheckBox(RigidBodies,label='Fade shells?') + fadeShell.SetValue(RBObj['fadeSh']) + fadeShell.Bind(wx.EVT_CHECKBOX,OnFadeShell) + plotLine.Add(fadeShell,0,WACV) + sprbSizer.Add(plotLine) + G2G.HorizontalLine(sprbSizer,RigidBodies) + sprbSizer.Add(topLine) sprbSizer.Add(SHsizer()) return sprbSizer - + def ResrbSizer(RBObj,resIndx): '''Displays details for selected residue rigid body''' def OnTorsionRef(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] - RBObj['Torsions'][item][1] = Obj.GetValue() - + RBObj['Torsions'][item][1] = Obj.GetValue() + def OnTorsion(invalid,value,tc): newXYZ = G2mth.UpdateRBXYZ(Bmat,RBObj,RBData,'Residue')[0] for i,Id in enumerate(RBObj['Ids']): @@ -13535,15 +13651,15 @@ def OnTorsion(invalid,value,tc): UpdateDrawAtoms(atomStyle) drawAtoms.ClearSelection() G2plt.PlotStructure(G2frame,data) - + def OnFrac(invalid,value,tc): for i,Id in enumerate(RBObj['Ids']): if data['Atoms'][AtLookUp[Id]][cx+3]: data['Atoms'][AtLookUp[Id]][cx+3] = value - + def OnRefFrac(event): RBObj['AtomFrac'][1] = not RBObj['AtomFrac'][1] - + def OnDelResRB(event): Obj = event.GetEventObject() RBId = Indx[Obj.GetId()] @@ -13551,7 +13667,7 @@ def OnDelResRB(event): del data['RBModels']['Residue'][resIndx] G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True,resId=resIndx) - + resrbSizer = wx.BoxSizer(wx.VERTICAL) G2G.HorizontalLine(resrbSizer,RigidBodies) topLine = wx.BoxSizer(wx.HORIZONTAL) @@ -13572,7 +13688,7 @@ def OnDelResRB(event): elif symAxis[1]: lbl = 'y' else: - lbl = 'z' + lbl = 'z' topLine.Add(wx.StaticText(RigidBodies,-1, ' Rigid body {} axis is aligned along oriention vector'.format(lbl)),0,WACV) try: @@ -13635,25 +13751,25 @@ def OnDelResRB(event): resrbSizer.Add((-1,5)) resrbSizer.Add(dragSizer) return resrbSizer - + def VecrbSizer(RBObj,resIndx): '''Displays details for selected vector rigid body''' def OnFrac(invalid,value,tc): for Id in RBObj['Ids']: if data['Atoms'][AtLookUp[Id]][cx+3]: data['Atoms'][AtLookUp[Id]][cx+3] = value - + def OnRefFrac(event): RBObj['AtomFrac'][1] = not RBObj['AtomFrac'][1] - + def OnDelVecRB(event): Obj = event.GetEventObject() RBId = Indx[Obj.GetId()] - RBData['Vector'][RBId]['useCount'] -= 1 + RBData['Vector'][RBId]['useCount'] -= 1 del data['RBModels']['Vector'][resIndx] G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True,vecId=resIndx) - + vecrbSizer = wx.BoxSizer(wx.VERTICAL) G2G.HorizontalLine(vecrbSizer,RigidBodies) topLine = wx.BoxSizer(wx.HORIZONTAL) @@ -13691,8 +13807,8 @@ def OnDelVecRB(event): vecrbSizer.Add(thermSizer) if RBObj['ThermalMotion'][0] != 'None': vecrbSizer.Add(ThermDataSizer(RBObj,'Vector')) - return vecrbSizer - + return vecrbSizer + def OnVecSelect(event): global prevVecId prevVecId = vecSelect.GetSelection() @@ -13705,7 +13821,7 @@ def OnVecSelect(event): except: pass wx.CallLater(100,RepaintRBInfo,'Vector',prevVecId) - + def OnResSelect(event): global prevResId prevResId = resSelect.GetSelection() @@ -13722,7 +13838,7 @@ def OnResSelect(event): rbType = 'Residue' data['testRBObj']['rbObj'] = copy.deepcopy(data['RBModels'][rbType][prevResId]) rbId = data['RBModels'][rbType][prevResId]['RBId'] - RBdata = G2frame.GPXtree.GetItemPyData( + RBdata = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) data['testRBObj']['rbData'] = RBdata data['testRBObj']['rbType'] = rbType @@ -13753,7 +13869,7 @@ def OnResSelect(event): data['testRBObj']['rbObj']['Torsions'].append([item[2],False]) # Needed? data['testRBObj']['torAtms'].append([-1,-1,-1]) wx.CallLater(100,RepaintRBInfo,'Residue',prevResId) - + def OnSpnSelect(event): global prevSpnId prevSpnId = spnSelect.GetSelection() @@ -13766,7 +13882,7 @@ def OnSpnSelect(event): except: pass wx.CallLater(100,RepaintRBInfo,'Spin',prevSpnId) - + def RepaintRBInfo(rbType,rbIndx,Scroll=0): oldFocus = wx.Window.FindFocus() try: @@ -13778,14 +13894,16 @@ def RepaintRBInfo(rbType,rbIndx,Scroll=0): return Indx.clear() rbObj = data['RBModels'][rbType][rbIndx] - data['Drawing']['viewPoint'][0] = data['Atoms'][AtLookUp[rbObj['Ids'][0]]][cx:cx+3] Quad = rbObj['Orient'][0] data['Drawing']['Quaternion'] = G2mth.invQ(Quad) if rbType == 'Residue': + data['Drawing']['viewPoint'][0] = rbObj['Orig'][0] G2frame.bottomSizer = ResrbSizer(rbObj,rbIndx) elif rbType == 'Spin': + data['Drawing']['viewPoint'][0] = data['Atoms'][AtLookUp[rbObj['Ids'][0]]][cx:cx+3] G2frame.bottomSizer = SpnrbSizer(rbObj,rbIndx) else: #Vector + data['Drawing']['viewPoint'][0] = rbObj['Orig'][0] G2frame.bottomSizer = VecrbSizer(rbObj,rbIndx) mainSizer.Add(G2frame.bottomSizer) mainSizer.Layout() @@ -13856,7 +13974,7 @@ def RepaintRBInfo(rbType,rbIndx,Scroll=0): rbObj = data['RBModels']['Residue'][resId] data['Drawing']['viewPoint'][0] = rbObj['Orig'][0] data['Drawing']['Quaternion'] = rbObj['Orient'][0] - resSelect = wx.ListBox(RigidBodies,choices=RBnames,style=wx.LB_SINGLE,size=(-1,120)) + resSelect = wx.ListBox(RigidBodies,choices=RBnames,style=wx.LB_SINGLE,size=(-1,80)) if resId: resSelect.SetSelection(resId) OnResSelect(None) @@ -13885,12 +14003,12 @@ def RepaintRBInfo(rbType,rbIndx,Scroll=0): rbObj = data['RBModels']['Vector'][vecId] data['Drawing']['viewPoint'][0] = rbObj['Orig'][0] data['Drawing']['Quaternion'] = rbObj['Orient'][0] - vecSelect = wx.ListBox(RigidBodies,choices=RBnames,style=wx.LB_SINGLE,size=(-1,120)) + vecSelect = wx.ListBox(RigidBodies,choices=RBnames,style=wx.LB_SINGLE,size=(-1,80)) if vecId is not None: vecSelect.SetSelection(vecId) OnVecSelect(None) vecSelect.Bind(wx.EVT_LISTBOX,OnVecSelect) - vecSizer.Add(vecSelect) + vecSizer.Add(vecSelect) rbSizer.Add(vecSizer) if 'Spin' in data['RBModels'] and len(data['RBModels']['Spin']): spnSizer = wx.BoxSizer(wx.VERTICAL) @@ -13910,12 +14028,12 @@ def RepaintRBInfo(rbType,rbIndx,Scroll=0): rbObj = data['RBModels']['Spin'][spnId] data['Drawing']['viewPoint'][0] = data['Atoms'][AtLookUp[RBObj['Ids'][0]]][cx:cx+3] data['Drawing']['Quaternion'] = rbObj['Orient'][0] - spnSelect = wx.ListBox(RigidBodies,choices=RBnames,style=wx.LB_SINGLE,size=(-1,120)) + spnSelect = wx.ListBox(RigidBodies,choices=RBnames,style=wx.LB_SINGLE,size=(-1,80)) if spnId != -1: spnSelect.SetSelection(spnId) OnSpnSelect(None) spnSelect.Bind(wx.EVT_LISTBOX,OnSpnSelect) - spnSizer.Add(spnSelect,0) + spnSizer.Add(spnSelect,0) rbSizer.Add(spnSizer) mainSizer.Add(rbSizer,0,wx.EXPAND) G2frame.bottomSizer = wx.BoxSizer(wx.VERTICAL) @@ -13936,7 +14054,7 @@ def RepaintRBInfo(rbType,rbIndx,Scroll=0): def OnRBCopyParms(event): RBObjs = [] - for rbType in ['Vector','Residue']: + for rbType in ['Vector','Residue']: RBObjs += data['RBModels'].get(rbType,[]) if not len(RBObjs): print ('**** ERROR - no rigid bodies defined ****') @@ -13951,7 +14069,7 @@ def OnRBCopyParms(event): dlg = wx.SingleChoiceDialog(G2frame,'Select source','Copy rigid body parameters',Source) if dlg.ShowModal() == wx.ID_OK: sel = dlg.GetSelection() - for item in ['Orig','Orient','ThermalMotion','AtomFract']: + for item in ['Orig','Orient','ThermalMotion','AtomFract']: sourceRB.update({item:RBObjs[sel][item],}) dlg.Destroy() if not sourceRB: @@ -13963,10 +14081,10 @@ def OnRBCopyParms(event): RBObjs[x].update(copy.copy(sourceRB)) G2plt.PlotStructure(G2frame,data) wx.CallAfter(FillRigidBodyGrid,True) - + def assignAtoms(RBData,selDict={},unmatchedRBatoms=None): - '''Find the closest RB atoms to atoms in the structure - If selDict is specified, it overrides the assignments to specify + '''Find the closest RB atoms to atoms in the structure + If selDict is specified, it overrides the assignments to specify atoms that should be matched. ''' general = data['General'] @@ -13991,7 +14109,7 @@ def assignAtoms(RBData,selDict={},unmatchedRBatoms=None): newXYZ = G2mth.UpdateRBXYZ(Bmat,rbObj,RBData,rbType)[0] rbUsedIds = [] # Ids of atoms in current phase used inside RBs - for i in data['RBModels']: + for i in data['RBModels']: for j in data['RBModels'][i]: rbUsedIds += j['Ids'] # categorize atoms by type, omitting any that are already assigned @@ -14004,7 +14122,7 @@ def assignAtoms(RBData,selDict={},unmatchedRBatoms=None): atmXYZ = G2mth.getAtomXYZ(atomData,cx) # separate structure's atoms by type (w/o assigned atoms) oXYZbyT = {} - atmNumByT = {} + atmNumByT = {} for t in set(atmTypes): if t is None: continue oXYZbyT[t] = np.array([atmXYZ[i] for i in range(len(atmXYZ)) if atmTypes[i] == t]) @@ -14053,21 +14171,21 @@ def assignAtoms(RBData,selDict={},unmatchedRBatoms=None): if unmatched and unmatchedRBatoms is not None: unmatchedRBatoms[:] = unmatched return matchTable - + def OnRBAssign(event): '''Assign RB to atoms in a phase with tools to locate the RB in the structure ''' - + def Draw(): '''Create the window for assigning RB to atoms''' - + def OnAddRB(event): 'respond to RB Add button, sets RB info in phase' dataGeneral = data['General'] dataGeneral['SpnIds'] = dataGeneral.get('SpnIds',{}) cx,ct,cs,cia = dataGeneral['AtomPtrs'] rbType = data['testRBObj']['rbType'] - atomData = data['Atoms'] + atomData = data['Atoms'] if RigidBodies.atomsGrid: matchTable = UpdateTable() else: @@ -14110,7 +14228,7 @@ def OnAddRB(event): SetupGeneral() UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + rbNames = [[]] for item in data['RBModels'].get(rbType,[]): if rbType == 'Spin': @@ -14137,17 +14255,14 @@ def OnAddRB(event): if not rbType in data['RBModels']: data['RBModels'][rbType] = [] if rbType == 'Spin': #convert items to lists of shells - for name in ['atColor','atType','Natoms','nSH','Radius','RBId','RBname','RBsym']: - #patch - if name == 'Radius' and name not in rbObj: - item = rbObj['radius'] - else: - item = rbObj[name] - rbObj[name] = [item,] + for name in ['atColor','atType','Natoms','nSH','RBId','RBname','RBsym']: + item = rbObj[name] + rbObj[name] = [rbObj[name],] + rbObj['Radius'] = [[1.0,False],] data['RBModels'][rbType].append(copy.deepcopy(rbObj)) RBData[rbType][rbId]['useCount'] += 1 del data['testRBObj'] - + # Update the draw atoms array & recompute bonds for atom in atomData: ID = atom[cia+8] @@ -14155,20 +14270,20 @@ def OnAddRB(event): FindBondsDraw(data) G2plt.PlotStructure(G2frame,data,False) FillRigidBodyGrid(True) - + def OnCancel(event): del data['testRBObj'] FillRigidBodyGrid(True) - + def OnTorAngle(invalid,value,tc): - '''respond to a number entered into the torsion editor. + '''respond to a number entered into the torsion editor. Update the slider (number is already saved) recompute atom distances ''' [tor,torSlide] = Indx[tc.GetId()] - torSlide.SetValue(int(value*10)) + torSlide.ChangeValue(int(value*10)) UpdateTablePlot() - + def OnTorSlide(event): '''respond to the slider moving. Put value into editor & save recompute atom distances @@ -14178,11 +14293,11 @@ def OnTorSlide(event): Tors = data['testRBObj']['rbObj']['Torsions'][tor] val = float(Obj.GetValue())/10. Tors[0] = val - ang.SetValue(val) + ang.ChangeValue(val) UpdateTablePlot() def UpdateTable(event=None): - '''get fixed atom assignments, find closest mappings & + '''get fixed atom assignments, find closest mappings & update displayed table ''' if not RigidBodies.atomsGrid: return [] @@ -14200,13 +14315,13 @@ def UpdateTable(event=None): for i,l in enumerate(matchTable): if len(l) < 11: continue RigidBodies.atomsTable.data[i][1:4] = l[5],l[6],l[10] - if RigidBodies.atomsGrid: + if RigidBodies.atomsGrid: RigidBodies.atomsGrid.ForceRefresh() if added: wx.CallLater(100,Draw) return matchTable - + def OnAzSlide(event): - '''respond to the azimuth slider moving. + '''respond to the azimuth slider moving. Save & put value into azimuth edit widget; show Q & update the plot and table ''' @@ -14214,7 +14329,7 @@ def OnAzSlide(event): rbObj['OrientVec'][0] = float(Obj.GetValue())/10. for i in range(4): val = rbObj['OrientVec'][i] - G2frame.testRBObjSizers['OrientVecSiz'][i].SetValue(val) + G2frame.testRBObjSizers['OrientVecSiz'][i].ChangeValue(val) Q = G2mth.AVdeg2Q(rbObj['OrientVec'][0], np.inner(Amat,rbObj['OrientVec'][1:])) rbObj['Orient'][0] = Q @@ -14222,7 +14337,7 @@ def OnAzSlide(event): rbObj['OrientVec'][1:] = np.inner(Bmat,V) G2plt.PlotStructure(G2frame,data,False,UpdateTable) UpdateTable() - + def UpdateOrientation(*args,**kwargs): '''Respond to a change in the azimuth or vector via the edit widget; update Q display & azimuth slider @@ -14231,25 +14346,25 @@ def UpdateOrientation(*args,**kwargs): np.inner(Amat,rbObj['OrientVec'][1:])) rbObj['Orient'][0] = Q try: - G2frame.testRBObjSizers['OrientVecSiz'][4].SetValue( + G2frame.testRBObjSizers['OrientVecSiz'][4].ChangeValue( int(10*rbObj['OrientVec'][0])) except: pass G2plt.PlotStructure(G2frame,data,False,UpdateTable) UpdateTable() - + def UpdateTablePlot(*args,**kwargs): '''update displayed table and plot ''' G2plt.PlotStructure(G2frame,data,False,UpdateTable) UpdateTable() - + def UpdateSytSym(*args,**kwargs): - + Sytsym,Mult = G2spc.SytSym(rbObj['Orig'][0],data['General']['SGData'])[:2] sytsymtxt.SetLabel('Origin site symmetry: %s, multiplicity: %d '%(Sytsym,Mult)) UpdateTablePlot(args,kwargs) - + def getSelectedAtoms(): 'Find the FB atoms that have been assigned to specific atoms in structure' if not RigidBodies.atomsGrid: return @@ -14269,15 +14384,14 @@ def getSelectedAtoms(): if sel not in dups: dups.append(sel) else: - assigned.append(atmNum) + assigned.append(atmNum) selDict[r] = atmNum if dups: msg = 'Error: The following atom(s) are assigned multiple times: ' for i in dups: msg += i msg += ', ' - wx.MessageBox(msg[:-2],caption='Duplicated Fixed Atoms', - style=wx.ICON_EXCLAMATION) + wx.MessageBox(msg[:-2],caption='Duplicated Fixed Atoms',style=wx.ICON_EXCLAMATION) return return selDict @@ -14295,8 +14409,8 @@ def getDeltaXYZ(selDict,data,rbObj): return np.array(deltaList) def objectiveDeltaPos(vals,selDict,data,rbObj_in): - '''Objective function for minimization. - Returns a list of distances between atom positions and + '''Objective function for minimization. + Returns a list of distances between atom positions and located rigid body positions :param list vals: a 4 or 7 element array with 4 quaterian values @@ -14325,9 +14439,10 @@ def onSetOrigin(event): deltaList = getDeltaXYZ(selDict,data,rbObj) data['testRBObj']['rbObj']['Orig'][0] += deltaList.sum(axis=0)/len(deltaList) for i,item in enumerate(Xsizers): - item.SetValue(data['testRBObj']['rbObj']['Orig'][0][i]) +# item.SetValue(data['testRBObj']['rbObj']['Orig'][0][i]) + item.ChangeValue(data['testRBObj']['rbObj']['Orig'][0][i]) UpdateTablePlot() - + def onFitOrientation(event): 'Set Orientation to best fit selected atoms' selDict = getSelectedAtoms() @@ -14340,7 +14455,7 @@ def onFitOrientation(event): data['testRBObj']['rbObj']['Orient'][0][:] = G2mth.normQ(out[0]) updateAddRBorientText(G2frame,data['testRBObj'],Bmat) UpdateTablePlot() - + def onFitBoth(event): 'Set Orientation and origin to best fit selected atoms' if rbObj['fixOrig']: @@ -14357,10 +14472,11 @@ def onFitBoth(event): data['testRBObj']['rbObj']['Orig'][0][:] = out[0][4:] data['testRBObj']['rbObj']['Orient'][0][:] = G2mth.normQ(out[0][:4]) for i,item in enumerate(Xsizers): - item.SetValue(data['testRBObj']['rbObj']['Orig'][0][i]) +# item.SetValue(data['testRBObj']['rbObj']['Orig'][0][i]) + item.ChangeValue(data['testRBObj']['rbObj']['Orig'][0][i]) updateAddRBorientText(G2frame,data['testRBObj'],Bmat) UpdateTablePlot() - + def BallnSticks(event): '''Set all draw atoms in crystal structure to balls & stick ''' @@ -14368,7 +14484,7 @@ def BallnSticks(event): FindBondsDraw(data) G2plt.PlotStructure(G2frame,data,False,UpdateTable) RigidBodies.SetFocus() # make sure tab presses go to panel - + def Sticks(event): '''Set all draw atoms in crystal structure to stick ''' @@ -14376,15 +14492,15 @@ def Sticks(event): FindBondsDraw(data) G2plt.PlotStructure(G2frame,data,False,UpdateTable) RigidBodies.SetFocus() # make sure tab presses go to panel - + def OnRowSelect(event): '''Respond to the selection of a rigid body atom. Highlight the atom in the body and the paired atom in the crystal ''' event.Skip() - cryatom = event.GetEventObject().Table.GetValue(event.GetRow(),4) - if not cryatom: + cryatom = event.GetEventObject().Table.GetValue(event.GetRow(),4) + if not cryatom: cryatom = event.GetEventObject().Table.GetValue(event.GetRow(),2) data['testRBObj']['RBhighLight'] = event.GetRow() data['testRBObj']['CRYhighLight'] = [ @@ -14393,12 +14509,12 @@ def OnRowSelect(event): misc['showSelect'].setByString(cryatom) G2frame.Raise() #RigidBodies.atomsGrid.SetFocus() - + def OnSymRadioSet(event): - '''Set the symmetry axis for the body as - data['testRBObj']['rbObj']['symAxis']. This may never be - set, so use data['testRBObj']['rbObj'].get('symAxis') to - access this so the default value is None. + '''Set the symmetry axis for the body as + data['testRBObj']['rbObj']['symAxis']. This may never be + set, so use data['testRBObj']['rbObj'].get('symAxis') to + access this so the default value is None. ''' axis = (None,[1,0,0],[0,1,0],[0,0,1],[1,1,0],[1,1,1] )[event.GetEventObject().GetSelection()] @@ -14406,17 +14522,17 @@ def OnSymRadioSet(event): axis = np.array(axis)/nl.norm(axis) data['testRBObj']['rbObj']['symAxis'] = axis UpdateTablePlot() - + def OnOrigSet(event): data['testRBObj']['rbObj']['Orig'][0] = data['Drawing']['viewPoint'][0] for i,item in enumerate(Xsizers): - item.SetValue(data['testRBObj']['rbObj']['Orig'][0][i]) + item.ChangeValue(data['testRBObj']['rbObj']['Orig'][0][i]) UpdateSytSym() UpdateTablePlot() - + showAtom = [None] def showCryAtom(*args,**kwargs): - '''Respond to selection of a crystal atom + '''Respond to selection of a crystal atom ''' data['testRBObj']['CRYhighLight'] = [ i for i,a in enumerate(data['Atoms']) if a[0] == showAtom[0]] @@ -14444,55 +14560,55 @@ def showCryAtom(*args,**kwargs): atNames = data['testRBObj']['atNames'] topSizer = wx.BoxSizer(wx.HORIZONTAL) helpText = ''' -This window is used to insert a rigid body into a unit cell, determining -the initial settings for the position and orientation of the body, as well as -any internal torsion angles. The origin determines where the origin of the rigid body -will be placed in the unit cell (expressed in fractional coordinates). The -orientation is determined by a quaternion that is expressed here as vector -(in fraction coordinates) and an azimuthal rotation (in degrees) around that -quaternion vector. For systems where the rigid body is placed on a -crystallographic symmetry element, the "Rigid body symmetry axis" -("x", "y", "z", "x+y" or "x+y+z") specifies the rotation axis that aligns with -an allowed rotation for this symmetry element (NB: could be perpendicular to a -mirror). This places the selected Cartesian axis along the quaternion vector. -The quaternion vector may be set by the user to force the rotation to be about -a particular crystallographic direction (e.g. along a rotation axis or +This window is used to insert a rigid body into a unit cell, determining +the initial settings for the position and orientation of the body, as well as +any internal torsion angles. The origin determines where the origin of the rigid body +will be placed in the unit cell (expressed in fractional coordinates). The +orientation is determined by a quaternion that is expressed here as vector +(in fraction coordinates) and an azimuthal rotation (in degrees) around that +quaternion vector. For systems where the rigid body is placed on a +crystallographic symmetry element, the "Rigid body symmetry axis" +("x", "y", "z", "x+y" or "x+y+z") specifies the rotation axis that aligns with +an allowed rotation for this symmetry element (NB: could be perpendicular to a +mirror). This places the selected Cartesian axis along the quaternion vector. +The quaternion vector may be set by the user to force the rotation to be about +a particular crystallographic direction (e.g. along a rotation axis or perpendicular to a mirror). The rotation action can be tested via the slider. %%If there are atoms in the unit cell that are of the appropriate type and -are not already assigned to rigid bodies, a table shows each atom in the rigid -body and the closest crystallographic atom, and the distance between them. -Set this pairing by using the pulldown menu in the "Assign as atom" column by -selecting a particular atom. If the selection is changed, "Update Assignments" -recomputes distance between paired atoms. If one atom is paired manually using -"Assign as", the "Set Origin" button can be used to place the rigid body origin -to best fit the paired atom(s). Likewise, with two or more atoms assigned, the +are not already assigned to rigid bodies, a table shows each atom in the rigid +body and the closest crystallographic atom, and the distance between them. +Set this pairing by using the pulldown menu in the "Assign as atom" column by +selecting a particular atom. If the selection is changed, "Update Assignments" +recomputes distance between paired atoms. If one atom is paired manually using +"Assign as", the "Set Origin" button can be used to place the rigid body origin +to best fit the paired atom(s). Likewise, with two or more atoms assigned, the "Set Orientation" button will determine the quaternion azimuth and vector. Three -or more pairs are assignments allow use of the "Set both" button, which -sets the orientation and origin to best give the smallest distances between -the assigned atoms. NB: if apparently stuck with a poor fit, try shifting the +or more pairs are assignments allow use of the "Set both" button, which +sets the orientation and origin to best give the smallest distances between +the assigned atoms. NB: if apparently stuck with a poor fit, try shifting the Orientation azimuth slider and try Set both again. -Note that when a row in the table is selected, the corresponding atoms +Note that when a row in the table is selected, the corresponding atoms are highlighted in green. The tab key or the "Crystal Highlight" pulldown can be used to highlight differing unit cell atoms. Alt-Tab highlights different -RB atoms. - -%%If there are no unassigned atoms of the right type (existing RBs will have -orange sticks for bonds), then the table will show "Create new" in the -"Assign as atom" column. The proposed RB can be positioned via Alt mouse -operations and/or by entering appropriate values in the Orientation azimuth -and vector x,y,z boxes. Symmetry element issues should be attended to by -proper selections as noted above. "Add" will then add the rigid body atoms to +RB atoms. + +%%If there are no unassigned atoms of the right type (existing RBs will have +orange sticks for bonds), then the table will show "Create new" in the +"Assign as atom" column. The proposed RB can be positioned via Alt mouse +operations and/or by entering appropriate values in the Orientation azimuth +and vector x,y,z boxes. Symmetry element issues should be attended to by +proper selections as noted above. "Add" will then add the rigid body atoms to the Atom list. -%%The GSAS-II graphics window shows the unit cell contents (use the -"Ball & Sticks" or "Sticks" buttons to change the display of this) and -the rigid body is shown with small balls and green sticks. At the origin of the -RB the axes are indicated with red, green and blue lines (for x, y, & z). -A white line indicates the quaternion vector direction. -The mouse can also be used to position the rigid body in the plot by holding -the Alt key down while dragging with the mouse: Alt+left button to rotates -the RB in the screen x & y; Alt+middle mouse to rotate the RB around the viewing +%%The GSAS-II graphics window shows the unit cell contents (use the +"Ball & Sticks" or "Sticks" buttons to change the display of this) and +the rigid body is shown with small balls and green sticks. At the origin of the +RB the axes are indicated with red, green and blue lines (for x, y, & z). +A white line indicates the quaternion vector direction. +The mouse can also be used to position the rigid body in the plot by holding +the Alt key down while dragging with the mouse: Alt+left button to rotates +the RB in the screen x & y; Alt+middle mouse to rotate the RB around the viewing direction; Alt+right mouse translates the RB in the screen x-y plane. Note that dragging the mouse without the Alt button changes the view of the crystal structure. @@ -14580,7 +14696,7 @@ def showCryAtom(*args,**kwargs): RefSizer = wx.FlexGridSizer(0,7,5,5) mainSizer.Add(RefSizer) mainSizer.Add((5,5),0) - if Torsions: + if Torsions: rbSeq = RBData['Residue'][rbId]['rbSeq'] TorSizer = wx.FlexGridSizer(0,4,5,5) TorSizer.AddGrowableCol(1,1) @@ -14599,7 +14715,7 @@ def showCryAtom(*args,**kwargs): ang = G2G.ValidatedTxtCtrl(RigidBodies,torsion,0,nDig=(8,3),typeHint=float,OnLeave=OnTorAngle) Indx[torSlide.GetId()] = [t,ang] Indx[ang.GetId()] = [t,torSlide] - TorSizer.Add(ang,0,WACV) + TorSizer.Add(ang,0,WACV) mainSizer.Add(TorSizer,0,wx.EXPAND|wx.RIGHT) else: mainSizer.Add(wx.StaticText(RigidBodies,label='No side chain torsions'),0) @@ -14628,9 +14744,9 @@ def showCryAtom(*args,**kwargs): # G2plt.PlotStructure(G2frame,data,True) RigidBodies.atomsGrid = None return - + G2plt.PlotStructure(G2frame,data,True,UpdateTable) - + if rbType == 'Spin': mainSizer.Add(wx.StaticText(RigidBodies,label=' Spinning rigid body:'),0) else: @@ -14669,10 +14785,10 @@ def showCryAtom(*args,**kwargs): attr.SetReadOnly(True) for i in range(len(colLabels)-1): attr.IncRef() - RigidBodies.atomsGrid.SetColAttr(i, attr) + RigidBodies.atomsGrid.SetColAttr(i, attr) attr = wg.GridCellAttr() attr.SetAlignment(wx.ALIGN_RIGHT,wx.ALIGN_CENTRE) - RigidBodies.atomsGrid.SetColAttr(1, attr) + RigidBodies.atomsGrid.SetColAttr(1, attr) attr = wg.GridCellAttr() attr.SetEditor(choiceeditor) RigidBodies.atomsGrid.SetColAttr(4, attr) @@ -14698,7 +14814,7 @@ def showCryAtom(*args,**kwargs): btn.Bind(wx.EVT_BUTTON,UpdateTable) btnSizer.Add(btn,0,wx.ALIGN_CENTER) btnSizer.Add((-1,10)) - + btn = wx.Button(RigidBodies,label='Set Origin') btn.Bind(wx.EVT_BUTTON,onSetOrigin) btnSizer.Add(btn,0,wx.ALIGN_CENTER) @@ -14712,14 +14828,14 @@ def showCryAtom(*args,**kwargs): btnSizer.Add(btn,0,wx.ALIGN_CENTER) gridSizer.Add(btnSizer) mainSizer.Add(gridSizer) - + mainSizer.Layout() RigidBodies.SetScrollRate(10,10) RigidBodies.SendSizeEvent() RigidBodies.Scroll(0,0) RigidBodies.SetFocus() # make sure tab presses go to panel misc['UpdateTable'] = UpdateTable - + # start of OnRBAssign(event) rbAssignments = {} rbUsedIds = [] # Ids of atoms in current phase used inside RBs @@ -14729,7 +14845,7 @@ def showCryAtom(*args,**kwargs): for j in data['RBModels'][i]: rbUsedIds += j['Ids'] G2frame.GetStatusBar().SetStatusText('',1) - RBData = G2frame.GPXtree.GetItemPyData( + RBData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) rbNames = {} dups = [] @@ -14754,7 +14870,7 @@ def showCryAtom(*args,**kwargs): dups.append(key) else: rbNames[key] = ['Spin',rbSpn] - + if dups: msg = 'Two or more rigid bodies have the same name. This must be corrected before bodies can be added.' msg += '\n\n Duplicated name(s): ' @@ -14797,7 +14913,7 @@ def showCryAtom(*args,**kwargs): return rbType,rbId = rbNames[selection] if rbType == 'Spin': - data['testRBObj']['rbAtTypes'] = [RBData[rbType][rbId]['rbType'],] + data['testRBObj']['rbAtTypes'] = [RBData[rbType][rbId]['rbType'],] data['testRBObj']['AtInfo'] = {RBData[rbType][rbId]['rbType']:[1.0,(128, 128, 255)],} data['testRBObj']['rbType'] = rbType data['testRBObj']['rbData'] = RBData @@ -14836,11 +14952,11 @@ def showCryAtom(*args,**kwargs): data['testRBObj']['torAtms'] = [] for item in RBData[rbType][rbId].get('rbSeq',[]): data['testRBObj']['rbObj']['Torsions'].append([item[2],False]) - data['testRBObj']['torAtms'].append([-1,-1,-1]) + data['testRBObj']['torAtms'].append([-1,-1,-1]) wx.CallAfter(Draw) - + def OnAutoFindResRB(event): - RBData = G2frame.GPXtree.GetItemPyData( + RBData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) rbKeys = list(RBData['Residue'].keys()) rbKeys.remove('AtInfo') @@ -14919,7 +15035,7 @@ def OnAutoFindResRB(event): TXYZ = [] rbObj['Torsions'] = [] for i,xyz in enumerate(rbRes['rbXYZ']): - SXYZ.append(G2mth.prodQVQ(QuatC,xyz)) + SXYZ.append(G2mth.prodQVQ(QuatC,xyz)) TXYZ.append(np.inner(Amat,rbAtoms[i]-Orig)) for Oatm,Patm,x,Riders in rbRes['rbSeq']: VBR = SXYZ[Oatm]-SXYZ[Patm] @@ -14942,18 +15058,18 @@ def OnAutoFindResRB(event): finally: wx.EndBusyCursor() wx.CallAfter(FillRigidBodyGrid,True) - + def OnRBRemoveAll(event): data['RBModels']['Residue'] = [] data['RBModels']['Vector'] = [] data['RBModels']['Spin'] = [] - RBData = G2frame.GPXtree.GetItemPyData( + RBData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) for RBType in ['Vector','Residue','Spin']: for rbId in RBData[RBType]: - RBData[RBType][rbId]['useCount'] = 0 + RBData[RBType][rbId]['useCount'] = 0 FillRigidBodyGrid(True) - + def OnGlobalResRBTherm(event): RBObjs = data['RBModels']['Residue'] names = ['None','Uiso','T','TL','TLS'] @@ -14965,7 +15081,7 @@ def OnGlobalResRBTherm(event): parm = names[sel] Ttype = 'A' if parm == 'Uiso': - Ttype = 'I' + Ttype = 'I' for rbObj in RBObjs: rbObj['ThermalMotion'][0] = parm if parm != 'None': @@ -15045,19 +15161,19 @@ def OnGlobalResRBRef(event): finally: wx.EndBusyCursor() FillRigidBodyGrid() - + #### MC/SA routines ################################################################################ def UpdateMCSA(Scroll=0): Indx = {} - + def OnPosRef(event): Obj = event.GetEventObject() model,item,ix = Indx[Obj.GetId()] model[item][1][ix] = Obj.GetValue() - + def OnPosVal(invalid,value,tc): G2plt.PlotStructure(G2frame,data) - + def OnPosRange(event): event.Skip() Obj = event.GetEventObject() @@ -15070,10 +15186,10 @@ def OnPosRange(event): except (ValueError,IndexError): rmin,rmax = model[item][2][ix] model[item][2][ix] = [rmin,rmax] - Obj.SetValue('%.3f %.3f'%(rmin,rmax)) - + Obj.SetValue('%.3f %.3f'%(rmin,rmax)) + def atomSizer(model): - + atomsizer = wx.FlexGridSizer(0,7,5,5) atomsizer.Add(wx.StaticText(G2frame.MCSA,-1,' Atom: '+model['name']+': '),0,WACV) for ix,item in enumerate(['x','y','z']): @@ -15095,14 +15211,14 @@ def atomSizer(model): posRange.Bind(wx.EVT_KILL_FOCUS,OnPosRange) atomsizer.Add(posRange,0,WACV) return atomsizer - + def rbSizer(model): - + def OnOrVar(event): Obj = event.GetEventObject() model = Indx[Obj.GetId()] model['Ovar'] = Obj.GetValue() - + def OnOriVal(event): event.Skip() Obj = event.GetEventObject() @@ -15140,9 +15256,9 @@ def OnMolCent(event): model = Indx[Obj.GetId()] model['MolCent'][1] = Obj.GetValue() if model['MolCent'][1]: - G2mth.SetMolCent(model,RBData) + G2mth.SetMolCent(model,RBData) G2plt.PlotStructure(G2frame,data) - + rbsizer = wx.BoxSizer(wx.VERTICAL) rbsizer1 = wx.FlexGridSizer(0,7,5,5) rbsizer1.Add(wx.StaticText(G2frame.MCSA,-1,model['Type']+': '+model['name']+': '),0) @@ -15168,7 +15284,7 @@ def OnMolCent(event): posRange.Bind(wx.EVT_TEXT_ENTER,OnPosRange) posRange.Bind(wx.EVT_KILL_FOCUS,OnPosRange) rbsizer1.Add(posRange,0,WACV) - + rbsizer2 = wx.FlexGridSizer(0,6,5,5) Ori = model['Ori'][0] rbsizer2.Add(wx.StaticText(G2frame.MCSA,-1,'Oa: '),0,WACV) @@ -15209,8 +15325,8 @@ def OnMolCent(event): vecRange.Bind(wx.EVT_TEXT_ENTER,OnPosRange) vecRange.Bind(wx.EVT_KILL_FOCUS,OnPosRange) rbsizer2.Add(vecRange,0,WACV) - rbsizer.Add(rbsizer1) - rbsizer.Add(rbsizer2) + rbsizer.Add(rbsizer1) + rbsizer.Add(rbsizer2) if model['Type'] == 'Residue': try: atNames = RBData['Residue'][model['RBId']]['atNames'] @@ -15239,12 +15355,12 @@ def OnMolCent(event): data['MCSA'] = {'Models':[{'Type':'MD','Coef':[1.0,False,[.8,1.2],],'axis':[0,0,1]}],'Results':[],'AtInfo':{}} wx.CallAfter(UpdateMCSA) return rbsizer - + def MDSizer(POData): - + def OnPORef(event): POData['Coef'][1] = poRef.GetValue() - + def OnPORange(event): event.Skip() Range = poRange.GetValue().split() @@ -15257,8 +15373,8 @@ def OnPORange(event): except (ValueError,IndexError): rmin,rmax = POData['Coef'][2] POData['Coef'][2] = [rmin,rmax] - poRange.SetValue('%.3f %.3f'%(rmin,rmax)) - + poRange.SetValue('%.3f %.3f'%(rmin,rmax)) + def OnPOAxis(event): event.Skip() Saxis = poAxis.GetValue().split() @@ -15270,8 +15386,8 @@ def OnPOAxis(event): hkl = POData['axis'] POData['axis'] = hkl h,k,l = hkl - poAxis.SetValue('%3d %3d %3d'%(h,k,l)) - + poAxis.SetValue('%3d %3d %3d'%(h,k,l)) + poSizer = wx.BoxSizer(wx.HORIZONTAL) poRef = wx.CheckBox(G2frame.MCSA,-1,label=' March-Dollase ratio: ') poRef.SetValue(POData['Coef'][1]) @@ -15285,7 +15401,7 @@ def OnPOAxis(event): poRange = wx.TextCtrl(G2frame.MCSA,-1,'%.3f %.3f'%(rmin,rmax),style=wx.TE_PROCESS_ENTER) poRange.Bind(wx.EVT_TEXT_ENTER,OnPORange) poRange.Bind(wx.EVT_KILL_FOCUS,OnPORange) - poSizer.Add(poRange,0,WACV) + poSizer.Add(poRange,0,WACV) poSizer.Add(wx.StaticText(G2frame.MCSA,-1,' Unique axis, H K L: '),0,WACV) h,k,l = POData['axis'] # Zstep = G2G.ValidatedTxtCtrl(drawOptions,drawingData,'Zstep',nDig=(10,2),xmin=0.01,xmax=4.0) @@ -15294,9 +15410,9 @@ def OnPOAxis(event): poAxis.Bind(wx.EVT_KILL_FOCUS,OnPOAxis) poSizer.Add(poAxis,0,WACV) return poSizer - + def ResultsSizer(Results): - + def OnCellChange(event): r,c = event.GetRow(),event.GetCol() if c == 0: @@ -15318,7 +15434,7 @@ def OnCellChange(event): Results[r][1] = True resultsTable.SetValue(r,c,Results[r][1]) resultsGrid.ForceRefresh() - + resultsSizer = wx.BoxSizer(wx.VERTICAL) maxVary = 0 resultVals = [] @@ -15350,7 +15466,7 @@ def OnCellChange(event): def OnSelect(event): rbId = rbids[select.GetSelection()] wx.CallLater(100,RepaintRBInfo,rbId) - + def RepaintRBInfo(rbId,Scroll=0): oldFocus = wx.Window.FindFocus() if 'phoenix' in wx.version(): @@ -15364,11 +15480,11 @@ def RepaintRBInfo(rbId,Scroll=0): G2frame.dataWindow.Refresh() G2frame.dataWindow.SendSizeEvent() wx.CallAfter(oldFocus.SetFocus) - + def OnShoLabels(event): data['MCSA']['showLabels'] = not data['MCSA']['showLabels'] G2plt.PlotStructure(G2frame,data) - + # UpdateMCSA executable code starts here if G2frame.MCSA.GetSizer(): G2frame.MCSA.GetSizer().Clear(True) #patch @@ -15426,7 +15542,7 @@ def OnShoLabels(event): G2frame.bottomSizer = wx.BoxSizer(wx.VERTICAL) G2frame.bottomSizer.Add(rbSizer(data['MCSA']['Models'][rbids[0]])) mainSizer.Add(G2frame.bottomSizer) - + mainSizer.Add((5,5),0) bottomSizer = wx.BoxSizer(wx.HORIZONTAL) resStr = 'MC/SA results: ' @@ -15442,31 +15558,31 @@ def OnShoLabels(event): if data['MCSA']['Results']: Results = data['MCSA']['Results'] mainSizer.Add(ResultsSizer(Results),0,wx.EXPAND) - + SetPhaseWindow(G2frame.MCSA,mainSizer) - + def SetSolution(result,Models): for key,val in zip(result[-1],result[4:-1]): vals = key.split(':') nObj,name = int(vals[0]),vals[1] if 'A' in name: ind = ['Ax','Ay','Az'].index(name) - Models[nObj]['Pos'][0][ind] = val + Models[nObj]['Pos'][0][ind] = val elif 'Q' in name: ind = ['Qa','Qi','Qj','Qk'].index(name) Models[nObj]['Ori'][0][ind] = val elif 'P' in name: ind = ['Px','Py','Pz'].index(name) - Models[nObj]['Pos'][0][ind] = val + Models[nObj]['Pos'][0][ind] = val elif 'T' in name: tnum = int(name.split('Tor')[1]) - Models[nObj]['Tor'][0][tnum] = val + Models[nObj]['Tor'][0][tnum] = val else: #March Dollase Models[0]['Coef'][0] = val - + def OnRunMultiMCSA(event): RunMCSA('multi') - + def OnRunSingleMCSA(event): RunMCSA('single') @@ -15480,7 +15596,7 @@ def RunMCSA(process): for result in MCSAdata['Results']: if result[1]: #keep? saveResult.append(result) - MCSAdata['Results'] = saveResult + MCSAdata['Results'] = saveResult covData = {} if 'PWDR' in reflName: PatternId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root, reflName) @@ -15506,7 +15622,7 @@ def RunMCSA(process): return print ('MC/SA run:') print ('Reflection type:'+reflType+' Total No. reflections: %d'%len(reflData)) - RBdata = G2frame.GPXtree.GetItemPyData( + RBdata = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) MCSAmodels = MCSAdata['Models'] if not len(MCSAmodels): @@ -15515,7 +15631,7 @@ def RunMCSA(process): time1 = time.time() nprocs = GSASIIpath.GetConfigValue('Multiprocessing_cores',0) if process == 'single' or not nprocs: - pgbar = wx.ProgressDialog('MC/SA','Residual Rcf =',101, + pgbar = wx.ProgressDialog('MC/SA','Residual Rcf =',101, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -15560,17 +15676,17 @@ def OnMCSAaddAtom(event): El = dlg.Elem.strip() Info = G2elem.GetAtomInfo(El) dlg.Destroy() - + atom = {'Type':'Atom','atType':El,'Pos':[[0.,0.,0.], [False,False,False],[[0.,1.],[0.,1.],[0.,1.]]], - 'name':El+'('+str(len(data['MCSA']['Models']))+')'} + 'name':El+'('+str(len(data['MCSA']['Models']))+')'} data['MCSA']['Models'].append(atom) data['MCSA']['AtInfo'][El] = [Info['Drad'],Info['Color']] G2plt.PlotStructure(G2frame,data) UpdateMCSA() - + def OnMCSAaddRB(event): - rbData = G2frame.GPXtree.GetItemPyData( + rbData = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies')) rbNames = {} for rbVec in rbData['Vector']: @@ -15602,28 +15718,28 @@ def OnMCSAaddRB(event): data['MCSA']['AtInfo'].update(rbData[rbType]['AtInfo']) UpdateMCSA() wx.CallAfter(G2plt.PlotStructure,G2frame,data) - + def OnMCSAclear(event): data['MCSA'] = {'Models':[{'Type':'MD','Coef':[1.0,False,[.8,1.2],],'axis':[0,0,1]}],'Results':[],'AtInfo':{}} G2plt.PlotStructure(G2frame,data) UpdateMCSA() - + def OnMCSAmove(event): general = data['General'] Amat,Bmat = G2lat.cell2AB(general['Cell'][1:7]) xyz,aTypes = G2mth.UpdateMCSAxyz(Bmat,data['MCSA']) for iat,atype in enumerate(aTypes): x,y,z = xyz[iat] - AtomAdd(x,y,z,atype,Name=atype+'(%d)'%(iat+1)) + AtomAdd(x,y,z,atype,Name=atype+'(%d)'%(iat+1)) G2plt.PlotStructure(G2frame,data) - + def OnClearResults(event): data['MCSA']['Results'] = [] UpdateMCSA() - + ##### Pawley routines ################################################################################ def FillPawleyReflectionsGrid(): - + def onRefineDClick(event): '''Called after a double-click on a cell label''' c = event.GetCol() @@ -15641,7 +15757,7 @@ def onRefineDClick(event): else: for row in range(G2frame.PawleyRefl.GetNumberRows()): PawleyPeaks[row][c]=False wx.CallAfter(FillPawleyReflectionsGrid) - + def KeyEditPawleyGrid(event): colList = G2frame.PawleyRefl.GetSelectedCols() rowList = G2frame.PawleyRefl.GetSelectedRows() @@ -15668,9 +15784,9 @@ def KeyEditPawleyGrid(event): for row in rowList: del(PawleyPeaks[row]) FillPawleyReflectionsGrid() - + # FillPawleyReflectionsGrid executable starts here - G2frame.GetStatusBar().SetStatusText('To delete a Pawley reflection: select row & press Delete',1) + G2frame.GetStatusBar().SetStatusText('To delete a Pawley reflection: select row & press Delete',1) if G2frame.PawleyRefl in G2frame.phaseDisplay.gridList: G2frame.phaseDisplay.gridList.remove(G2frame.PawleyRefl) oldSizer = PawleyRefList.GetSizer() @@ -15679,11 +15795,11 @@ def KeyEditPawleyGrid(event): PawleyPeaks = data['Pawley ref'] mainSizer = wx.BoxSizer(wx.VERTICAL) - + topSizer = G2frame.dataWindow.topBox topSizer.Clear(True) parent = G2frame.dataWindow.topPanel - if len(PawleyPeaks) and generalData['doPawley']: + if len(PawleyPeaks) and generalData['doPawley']: lbl= f"Pawley reflections for {data['General']['Name']!r}"[:60] else: lbl= f"There are no Pawley reflections for {data['General']['Name']!r}"[:60] @@ -15691,7 +15807,7 @@ def KeyEditPawleyGrid(event): topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) wx.CallAfter(G2frame.dataWindow.SetDataSize) - + rowLabels = [] if len(PawleyPeaks) and generalData['doPawley']: for i in range(len(PawleyPeaks)): rowLabels.append(str(i)) @@ -15700,7 +15816,7 @@ def KeyEditPawleyGrid(event): Types = 5*[wg.GRID_VALUE_LONG,]+[wg.GRID_VALUE_FLOAT+':10,4',wg.GRID_VALUE_BOOL,]+ \ 2*[wg.GRID_VALUE_FLOAT+':10,2',] pos = [6,7] - else: + else: colLabels = ['h','k','l','mul','d','refine','Fsq(hkl)','sig(Fsq)'] Types = 4*[wg.GRID_VALUE_LONG,]+[wg.GRID_VALUE_FLOAT+':10,4',wg.GRID_VALUE_BOOL,]+ \ 2*[wg.GRID_VALUE_FLOAT+':10,2',] @@ -15731,23 +15847,23 @@ def KeyEditPawleyGrid(event): pass else: msg = ( -'''Pawley refinement has not yet been setup. Use the +'''Pawley refinement has not yet been setup. Use the Operations->"Pawley setttings" menu command to change this. -(Or, if Pawley settings have already been set on the General +(Or, if Pawley settings have already been set on the General tab, use Operations->"Pawley create")''') mainSizer.Add(wx.StaticText(PawleyRefList,label=msg),0,wx.ALIGN_CENTER) SetPhaseWindow(PawleyRefList,mainSizer) def OnPawleySet(event): - '''Open dialog to set Pawley parameters and optionally recompute reflections. - This is called from the Phase/Pawley Reflections "Pawley Settings" + '''Open dialog to set Pawley parameters and optionally recompute reflections. + This is called from the Phase/Pawley Reflections "Pawley Settings" menu command. These settings are also available on the Phase/General tab. ''' def DisablePawleyOpts(*args): 'dis-/enable Pawley options' for c in PawleyCtrlsList: c.Enable(generalData['doPawley']) - + PawleyCtrlsList = [] dmin,dmax,nhist,lbl = getPawleydRange(G2frame,data) generalData = data['General'] @@ -15758,7 +15874,7 @@ def DisablePawleyOpts(*args): else: # force limits on dmin & dmax generalData['Pawley dmax'] = min(generalData['Pawley dmax'],dmax) - generalData['Pawley dmin'] = max(generalData['Pawley dmin'],dmin) + generalData['Pawley dmin'] = max(generalData['Pawley dmin'],dmin) startDmin = generalData['Pawley dmin'] genDlg = wx.Dialog(G2frame,title='Set Pawley Parameters', style=wx.DEFAULT_DIALOG_STYLE) @@ -15782,7 +15898,7 @@ def DisablePawleyOpts(*args): #temp = {'Qmax':2 * math.pi / generalData['Pawley dmin']} #def Q2D(*args,**kw): # generalData['Pawley dmin'] = 2 * math.pi / temp['Qmax'] - # pawlVal.SetValue(generalData['Pawley dmin']) + # pawlVal.SetValue(generalData['Pawley dmin']) #pawlQVal = G2G.ValidatedTxtCtrl(genDlg,temp,'Qmax', # xmin=0.314,xmax=25.,nDig=(10,5),typeHint=float,OnLeave=Q2D) #PawleyCtrlsList.append(pawlQVal) @@ -15796,7 +15912,7 @@ def DisablePawleyOpts(*args): PawleyCtrlsList.append(pawlVal) pawleySizer.Add(pawlVal,0,WACV) mainSizer.Add(pawleySizer) - + pawleySizer = wx.BoxSizer(wx.HORIZONTAL) pawleySizer.Add(wx.StaticText(genDlg,label=' Pawley neg. wt.: '),0,WACV) pawlNegWt = G2G.ValidatedTxtCtrl(genDlg,generalData,'Pawley neg wt', @@ -15813,7 +15929,7 @@ def OnOK(event): genDlg.EndModal(wx.ID_OK) btn.Bind(wx.EVT_BUTTON, OnOK) btn.SetDefault() btnsizer.AddButton(btn) - btn = wx.Button(genDlg, wx.ID_CANCEL) + btn = wx.Button(genDlg, wx.ID_CANCEL) btnsizer.AddButton(btn) btnsizer.Realize() mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5) @@ -15846,7 +15962,7 @@ def OnOK(event): genDlg.EndModal(wx.ID_OK) wx.CallAfter(OnPawleyLoad,event) else: wx.CallAfter(FillPawleyReflectionsGrid) - + def OnPawleyLoad(event): generalData = data['General'] histograms = data['Histograms'].keys() @@ -15908,8 +16024,8 @@ def OnPawleyLoad(event): return finally: dlg.Destroy() - wx.CallAfter(OnPawleyEstimate,event) - + wx.CallAfter(OnPawleyEstimate,event) + def OnPawleyEstimate(event): #Algorithm thanks to James Hester try: @@ -15937,7 +16053,7 @@ def OnPawleyEstimate(event): cw = np.diff(xdata[0]) cw = np.append(cw,cw[-1]) gconst *= dx - + wx.BeginBusyCursor() try: for ref in Refs: @@ -15950,9 +16066,9 @@ def OnPawleyEstimate(event): indx = np.searchsorted(xdata[0],pos) try: FWHM = max(0.001,G2pwd.getFWHM(pos,Inst)) - # We want to estimate Pawley F^2 as a drop-in replacement for F^2 calculated by the structural + # We want to estimate Pawley F^2 as a drop-in replacement for F^2 calculated by the structural # routines, which use Icorr * F^2 * peak profile, where peak profile has an area of 1. So - # we multiply the observed peak height by sqrt(8 ln 2)/(FWHM*sqrt(pi)) to determine the value of Icorr*F^2 + # we multiply the observed peak height by sqrt(8 ln 2)/(FWHM*sqrt(pi)) to determine the value of Icorr*F^2 # then divide by Icorr to get F^2. ref[6+im] = (xdata[1][indx]-xdata[4][indx])*FWHM*np.sqrt(np.pi) #Area of Gaussian is height * FWHM * sqrt(pi) if 'E' not in Inst['Type'][0]: @@ -16012,19 +16128,19 @@ def OnPawleyUpdate(event): finally: wx.EndBusyCursor() wx.CallAfter(FillPawleyReflectionsGrid) - + def OnPawleySelAll(event): refcol = [G2frame.PawleyRefl.GetColLabelValue(c) for c in range(G2frame.PawleyRefl.GetNumberCols())].index('refine') for r in range(G2frame.PawleyRefl.GetNumberRows()): G2frame.PawleyRefl.GetTable().SetValue(r,refcol,True) - + G2frame.PawleyRefl.ForceRefresh() def OnPawleySelNone(event): refcol = [G2frame.PawleyRefl.GetColLabelValue(c) for c in range(G2frame.PawleyRefl.GetNumberCols())].index('refine') for r in range(G2frame.PawleyRefl.GetNumberRows()): G2frame.PawleyRefl.GetTable().SetValue(r,refcol,False) G2frame.PawleyRefl.ForceRefresh() - + def OnPawleyToggle(event): refcol = [G2frame.PawleyRefl.GetColLabelValue(c) for c in range(G2frame.PawleyRefl.GetNumberCols())].index('refine') @@ -16033,10 +16149,10 @@ def OnPawleyToggle(event): r,refcol, not G2frame.PawleyRefl.GetTable().GetValueAsBool(r,refcol)) G2frame.PawleyRefl.ForceRefresh() - + ##### Fourier routines ################################################################################ def FillMapPeaksGrid(): - + def RowSelect(event): r,c = event.GetRow(),event.GetCol() if r < 0 and c < 0: @@ -16045,9 +16161,9 @@ def RowSelect(event): else: for row in range(G2frame.MapPeaks.GetNumberRows()): G2frame.MapPeaks.SelectRow(row,True) - + elif c < 0: #only row clicks - if event.ControlDown(): + if event.ControlDown(): if r in getAtomSelections(G2frame.MapPeaks): G2frame.MapPeaks.DeselectRow(r) else: @@ -16074,8 +16190,8 @@ def RowSelect(event): return data['Map Peaks'] = mapPeaks wx.CallAfter(FillMapPeaksGrid) - G2plt.PlotStructure(G2frame,data) - + G2plt.PlotStructure(G2frame,data) + # beginning of FillMapPeaksGrid() G2frame.GetStatusBar().SetStatusText('',1) oldSizer = MapPeakList.GetSizer() @@ -16084,7 +16200,7 @@ def RowSelect(event): if type(i.GetWindow()) is G2G.GSGrid: oldSizer.Detach(i.GetWindow()) # don't delete them oldSizer.Clear(True) - + topSizer = G2frame.dataWindow.topBox topSizer.Clear(True) parent = G2frame.dataWindow.topPanel @@ -16093,11 +16209,11 @@ def RowSelect(event): topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) wx.CallAfter(G2frame.dataWindow.SetDataSize) - + mainSizer = wx.BoxSizer(wx.VERTICAL) if 'Map Peaks' in data: G2frame.GetStatusBar().SetStatusText('Double click any column heading to sort',1) - mapPeaks = data['Map Peaks'] + mapPeaks = data['Map Peaks'] rowLabels = [] for i in range(len(mapPeaks)): rowLabels.append(str(i)) colLabels = ['mag','x','y','z','dzero','dcent'] @@ -16119,13 +16235,13 @@ def RowSelect(event): mainSizer.Add(wx.StaticText(MapPeakList,label=' Map peak list is empty'),0,wx.ALL,10) G2frame.MapPeaks.Show(False) SetPhaseWindow(MapPeakList,mainSizer) - + def OnPeaksMove(event): if 'Map Peaks' in data: mapPeaks = np.array(data['Map Peaks']) peakMax = np.amax(mapPeaks.T[0]) Ind = getAtomSelections(G2frame.MapPeaks) - pgbar = wx.ProgressDialog('Move peaks','Map peak no. 0 processed',len(Ind)+1, + pgbar = wx.ProgressDialog('Move peaks','Map peak no. 0 processed',len(Ind)+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) for i,ind in enumerate(Ind): mag,x,y,z = mapPeaks[ind][:4] @@ -16133,26 +16249,26 @@ def OnPeaksMove(event): pgbar.Update(i+1,'Map peak no. %d processed'%ind) pgbar.Destroy() G2plt.PlotStructure(G2frame,data) - + def OnPeaksClear(event): data['Map Peaks'] = [] FillMapPeaksGrid() G2plt.PlotStructure(G2frame,data) - + def OnPeaksSave(event): if 'Map Peaks' in data: mapPeaks = data['Map Peaks'] pfName = PhaseName+'_peaks.csv' pfFile = '' pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose map peaks file name', pth, pfName, + dlg = wx.FileDialog(G2frame, 'Choose map peaks file name', pth, pfName, 'csv (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: pfFile = dlg.GetPath() finally: dlg.Destroy() - + if pfFile: pf = open(pfFile,'w') pf.write('"%s"\n'%(PhaseName)) @@ -16160,7 +16276,7 @@ def OnPeaksSave(event): for peak in mapPeaks: pf.write(' %.4f, %.4f, %.4f, %.4f, %.4f, %.4f \n'%(peak[0],peak[1],peak[2],peak[3],peak[4],peak[5])) pf.close() - + def OnPeaksDelete(event): if 'Map Peaks' in data: mapPeaks = np.array(data['Map Peaks']) @@ -16172,7 +16288,7 @@ def OnPeaksDelete(event): data['Map Peaks'] = mapPeaks FillMapPeaksGrid() G2plt.PlotStructure(G2frame,data) - + def OnPeaksInvert(event): if 'Map Peaks' in data: generalData = data['General'] @@ -16182,12 +16298,12 @@ def OnPeaksInvert(event): except TypeError: mapData['rho'] = np.flip(mapData['rho'],0) mapData['rho'] = np.flip(mapData['rho'],1) - mapData['rho'] = np.flip(mapData['rho'],2) + mapData['rho'] = np.flip(mapData['rho'],2) mapData['rho'] = np.roll(np.roll(np.roll(mapData['rho'],1,axis=0),1,axis=1),1,axis=2) OnSearchMaps(event) FillMapPeaksGrid() G2plt.PlotStructure(G2frame,data) - + def OnRollMap(event): if 'Map Peaks' in data: half = np.array([.5,.5,.5]) @@ -16197,13 +16313,13 @@ def OnRollMap(event): if mapData['Flip'] != True: wx.MessageBox('Only valid for charge flip maps',caption='Roll map error',style=wx.ICON_EXCLAMATION) return - Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) + Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) dims = mapData['rho'].shape dlg = G2G.MultiDataDialog(G2frame,title='Roll map shifts', prompts=['delt-X (-1. to 1.)','delt-Y (-1. to 1.)', 'delt-Z (-1. to 1.)'],values=[0.,0.,0.,], limits=[[-1.,1.],[-1.,1.],[-1.,1.]],formats=['%.6f','%.6f','%.6f']) - + if dlg.ShowModal() == wx.ID_OK: rollsteps = dlg.GetValues() dxy = np.array([float(R) for R in rollsteps]) @@ -16218,7 +16334,7 @@ def OnRollMap(event): FillMapPeaksGrid() G2plt.PlotStructure(G2frame,data) dlg.Destroy() - + def OnPeaksEquiv(event): if 'Map Peaks' in data: mapPeaks = np.array(data['Map Peaks']) @@ -16246,7 +16362,7 @@ def OnShowBonds(event): G2frame.dataWindow.MapPeaksEdit.SetLabel(G2G.wxID_SHOWBONDS,'Hide bonds') FillMapPeaksGrid() G2plt.PlotStructure(G2frame,data) - + def OnPeaksUnique(event): if 'Map Peaks' in data: mapPeaks = np.array(data['Map Peaks']) @@ -16260,14 +16376,14 @@ def OnPeaksUnique(event): else: dlg.Destroy() return - pgbar = wx.ProgressDialog('Unique peaks','Map peak no. 0 processed',len(Ind)+1, + pgbar = wx.ProgressDialog('Unique peaks','Map peak no. 0 processed',len(Ind)+1, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) Ind = G2mth.PeaksUnique(data,Ind,sel,pgbar) print (' No. unique peaks: %d Unique peak fraction: %.3f'%(len(Ind),float(len(Ind))/len(mapPeaks))) tbl = G2frame.MapPeaks.GetTable().data tbl[:] = [t for i,t in enumerate(tbl) if i in Ind] + [ - t for i,t in enumerate(tbl) if i not in Ind] + t for i,t in enumerate(tbl) if i not in Ind] for r in range(G2frame.MapPeaks.GetNumberRows()): if r < len(Ind): G2frame.MapPeaks.SelectRow(r,addToSelected=True) @@ -16275,7 +16391,7 @@ def OnPeaksUnique(event): G2frame.MapPeaks.DeselectRow(r) G2frame.MapPeaks.ForceRefresh() G2plt.PlotStructure(G2frame,data) - + def OnPeaksViewPoint(event): # set view point indx = getAtomSelections(G2frame.MapPeaks) @@ -16286,7 +16402,7 @@ def OnPeaksViewPoint(event): drawingData = data['Drawing'] drawingData['viewPoint'][0] = mapPeaks[indx[0]][1:4] G2plt.PlotStructure(G2frame,data) - + def OnPeaksDistVP(event): # distance to view point indx = getAtomSelections(G2frame.MapPeaks) @@ -16294,7 +16410,7 @@ def OnPeaksDistVP(event): G2frame.ErrorDialog('Peak distance','No peaks selected') return generalData = data['General'] - Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) + Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) mapPeaks = data['Map Peaks'] drawingData = data['Drawing'] viewPt = np.array(drawingData['viewPoint'][0]) @@ -16309,13 +16425,13 @@ def OnPeaksDistVP(event): print ('Peak: %5d mag= %8.2f distance = %.3f'%(i,peak[cm],dist)) def OnPeaksDA(event): - #distance, angle + #distance, angle indx = getAtomSelections(G2frame.MapPeaks) if len(indx) not in [2,3]: G2frame.ErrorDialog('Peak distance/angle','Wrong number of atoms for distance or angle calculation') return generalData = data['General'] - Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) + Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) mapPeaks = data['Map Peaks'] xyz = [] for i in indx: @@ -16337,12 +16453,12 @@ def OnFourierMaps(event): return phaseName = generalData['Name'] ReflData = GetReflData(G2frame,phaseName,reflNames) - if ReflData == None: + if ReflData == None: G2frame.ErrorDialog('Fourier map error','No reflections defined for Fourier map') return if 'Omit' in mapData['MapType']: dim = '3D ' - pgbar = wx.ProgressDialog('Omit map','Blocks done',65, + pgbar = wx.ProgressDialog('Omit map','Blocks done',65, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) mapData.update(G2mth.OmitMap(data,ReflData,pgbar)) pgbar.Destroy() @@ -16365,15 +16481,15 @@ def OnFourierMaps(event): G2frame.AddToNotebook('Fourier '+ftext,'FM') UpdateDrawAtoms() G2plt.PlotStructure(G2frame,data) - + def OnFourClear(event): generalData = data['General'] generalData['Map'] = mapDefault.copy() data['Drawing']['showMap'] = False G2plt.PlotStructure(G2frame,data) - + # map printing for testing purposes - def printRho(SGLaue,rho,rhoMax): + def printRho(SGLaue,rho,rhoMax): dim = len(rho.shape) if dim == 2: ix,jy = rho.shape @@ -16397,10 +16513,10 @@ def printRho(SGLaue,rho,rhoMax): r = int(100*rho[i,j,k]/rhoMax) line += '%4d'%(r) print (line+'\n') -## keep this - +## keep this + def OnSearchMaps(event): - + print (' Begin fourier map search - can take some time') time0 = time.time() generalData = data['General'] @@ -16420,16 +16536,16 @@ def OnSearchMaps(event): wx.EndBusyCursor() if len(peaks): mapPeaks = np.concatenate((mags,peaks,dzeros,dcents),axis=1) - data['Map Peaks'] = G2mth.sortArray(mapPeaks,0,reverse=True) + data['Map Peaks'] = G2mth.sortArray(mapPeaks,0,reverse=True) print (' Map search finished, time = %.2fs'%(time.time()-time0)) - print (' No.peaks found: %d'%len(peaks)) + print (' No.peaks found: %d'%len(peaks)) Page = G2frame.phaseDisplay.FindPage('Map peaks') G2frame.phaseDisplay.SetSelection(Page) wx.CallAfter(FillMapPeaksGrid) UpdateDrawAtoms() else: print ('No map available') - + def On4DChargeFlip(event): generalData = data['General'] mapData = generalData['Map'] @@ -16441,10 +16557,10 @@ def On4DChargeFlip(event): return phaseName = generalData['Name'] ReflData = GetReflData(G2frame,phaseName,reflNames) - if ReflData == None: + if ReflData == None: G2frame.ErrorDialog('Charge flip error','No reflections defined for charge flipping') return - pgbar = wx.ProgressDialog('Charge flipping','Residual Rcf =',101, + pgbar = wx.ProgressDialog('Charge flipping','Residual Rcf =',101, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -16458,7 +16574,7 @@ def On4DChargeFlip(event): G2frame.AddToNotebook(f'4D Charge flip: {result[2]}\n{result[3]}','CF') mapData.update(result[0]) map4DData.update(result[1]) - mapData['Flip'] = True + mapData['Flip'] = True mapSig = np.std(mapData['rho']) if not data['Drawing']: #if new drawing - no drawing data! SetupDrawingData() @@ -16469,7 +16585,7 @@ def On4DChargeFlip(event): OnSearchMaps(event) #does a plot structure at end else: print ('Bad charge flip map - no peak search done') - + def OnChargeFlip(event): generalData = data['General'] mapData = generalData['Map'] @@ -16480,10 +16596,10 @@ def OnChargeFlip(event): return phaseName = generalData['Name'] ReflData = GetReflData(G2frame,phaseName,reflNames) - if ReflData == None: + if ReflData == None: G2frame.ErrorDialog('Charge flip error','No reflections defined for charge flipping') return - pgbar = wx.ProgressDialog('Charge flipping','Residual Rcf =',101, + pgbar = wx.ProgressDialog('Charge flipping','Residual Rcf =',101, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) screenSize = wx.ClientDisplayRect() Size = pgbar.GetSize() @@ -16503,7 +16619,7 @@ def OnChargeFlip(event): Title='Test HKL phases',lines=True,names=testNames) finally: pgbar.Destroy() - mapData['Flip'] = True + mapData['Flip'] = True mapSig = np.std(mapData['rho']) if not data['Drawing']: #if new drawing - no drawing data! SetupDrawingData() @@ -16515,7 +16631,7 @@ def OnChargeFlip(event): OnSearchMaps(event) #does a plot structure at end else: print ('Bad charge flip map - no peak search done') - + def OnTextureRefine(event): General = data['General'] phaseName = General['Name'] @@ -16535,13 +16651,13 @@ def OnTextureRefine(event): RefDict = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Reflection Lists'))[phaseName] Refs = RefDict['RefList'].T #np.array! if RefDict['Super']: im = 1 #(3+1) offset for m - if 'T' in RefDict['Type']: + if 'T' in RefDict['Type']: it = 3 #TOF offset for alp, bet, wave tth = np.ones_like(Refs[0])*Inst[0]['2-theta'][0] refData[name] = np.column_stack((Refs[0],Refs[1],Refs[2],tth,Refs[8+im],Refs[12+im+it],np.zeros_like(Refs[0]))) else: # xray - typical caked 2D image data refData[name] = np.column_stack((Refs[0],Refs[1],Refs[2],Refs[5+im],Refs[8+im],Refs[12+im+it],np.zeros_like(Refs[0]))) - pgbar = wx.ProgressDialog('Texture fit','Residual = %5.2f'%(101.0),101, + pgbar = wx.ProgressDialog('Texture fit','Residual = %5.2f'%(101.0),101, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE) Error = G2mth.FitTexture(General,Gangls,refData,keyList,pgbar) pgbar.Destroy() @@ -16555,8 +16671,8 @@ def OnTextureRefine(event): XY.append(np.array(xy)) G2plt.PlotXY(G2frame,XY,labelX='POobs',labelY='POcalc',lines=False,newPlot=False,Title='Texture fit error') UpdateTexture() - G2plt.PlotTexture(G2frame,data,Start=False) - + G2plt.PlotTexture(G2frame,data,Start=False) + def OnTextureClear(event): print ('clear texture? - does nothing') @@ -16571,7 +16687,7 @@ def OnSelectPage(event): tabname = TabSelectionIdDict.get(event.GetId()) if not tabname: print ('Warning: menu item not in dict! id=%d'%event.GetId()) - return + return # find the matching tab for PageNum in range(G2frame.phaseDisplay.GetPageCount()): if tabname == G2frame.phaseDisplay.GetPageText(PageNum): @@ -16587,7 +16703,7 @@ def OnSelectPage(event): TabSelectionIdDict[Id] = page menu.Append(Id,page,'') G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) - + def OnPageChanged(event): '''This is called every time that a Notebook tab button is pressed on a Phase data item window @@ -16595,7 +16711,7 @@ def OnPageChanged(event): page = event.GetSelection() G2frame.phaseDisplay.SetSize(G2frame.dataWindow.GetClientSize()) #TODO -almost right ChangePage(page) - + def ChangePage(page): newlist = [] # force edits in open grids to complete @@ -16615,7 +16731,7 @@ def ChangePage(page): UpdateGeneral() elif text == 'Data': # only when conf 'SeparateHistPhaseTreeItem' is False G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) - G2plt.PlotSizeStrainPO(G2frame,data,hist='') + G2plt.PlotSizeStrainPO(G2frame,data,hist='') G2ddG.UpdateDData(G2frame,DData,data) elif text == 'Atoms': G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.AtomsMenu) @@ -16658,17 +16774,17 @@ def ChangePage(page): elif text == 'MC/SA': G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.MCSAMenu) G2plt.PlotStructure(G2frame,data,firstCall=True) - UpdateMCSA() + UpdateMCSA() elif text == 'Texture': G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.TextureMenu) - G2plt.PlotTexture(G2frame,data,Start=True) - UpdateTexture() + G2plt.PlotTexture(G2frame,data,Start=True) + UpdateTexture() elif text == 'Pawley reflections': G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.PawleyMenu) FillPawleyReflectionsGrid() else: G2gd.SetDataMenuBar(G2frame) - + def FillMenus(): '''Create the Select tab menus and bind to all menu items ''' @@ -16690,6 +16806,8 @@ def FillMenus(): G2frame.Bind(wx.EVT_MENU, OnUseBilbao, id=G2G.wxID_USEBILBAOMAG) G2frame.Bind(wx.EVT_MENU, OnApplySubgroups, id=G2G.wxID_USEBILBAOSUB) G2frame.Bind(wx.EVT_MENU, OnValidProtein, id=G2G.wxID_VALIDPROTEIN) + for Id in G2frame.dataWindow.ReplaceMenuId: #loop over submenu items + G2frame.Bind(wx.EVT_MENU, OnReplacePhase, id=Id) # Data (unless Hist/Phase tree entry shown) if not GSASIIpath.GetConfigValue('SeparateHistPhaseTreeItem',False): FillSelectPageMenu(TabSelectionIdDict, G2frame.dataWindow.DataMenu) @@ -16745,12 +16863,12 @@ def FillMenus(): G2frame.Bind(wx.EVT_MENU, OnLoadDysnomia, id=G2G.wxID_LOADDYSNOMIA) G2frame.Bind(wx.EVT_MENU, OnSaveDysnomia, id=G2G.wxID_SAVEDYSNOMIA) G2frame.Bind(wx.EVT_MENU, OnRunDysnomia, id=G2G.wxID_RUNDYSNOMIA) - # Stacking faults + # Stacking faults FillSelectPageMenu(TabSelectionIdDict, G2frame.dataWindow.LayerData) G2frame.Bind(wx.EVT_MENU, OnCopyPhase, id=G2G.wxID_COPYPHASE) G2frame.Bind(wx.EVT_MENU, OnLoadDIFFaX, id=G2G.wxID_LOADDIFFAX) G2frame.Bind(wx.EVT_MENU, OnSimulate, id=G2G.wxID_LAYERSIMULATE) - G2frame.Bind(wx.EVT_MENU, OnFitLayers, id=G2G.wxID_LAYERSFIT) + G2frame.Bind(wx.EVT_MENU, OnFitLayers, id=G2G.wxID_LAYERSFIT) G2frame.Bind(wx.EVT_MENU, OnSeqSimulate, id=G2G.wxID_SEQUENCESIMULATE) # Draw Options FillSelectPageMenu(TabSelectionIdDict, G2frame.dataWindow.DataDrawOptions) @@ -16782,12 +16900,12 @@ def FillMenus(): G2frame.Bind(wx.EVT_MENU, SelDrawList, id=G2G.wxID_DRAWSETSEL) G2frame.Bind(wx.EVT_MENU, DrawLoadSel, id=G2G.wxID_DRAWLOADSEL) G2frame.Bind(wx.EVT_MENU, RandomizedAction, id=G2G.wxID_DRAWRANDOM) - + # Deformation form factors FillSelectPageMenu(TabSelectionIdDict, G2frame.dataWindow.DeformationMenu) G2frame.Bind(wx.EVT_MENU, SelDeformAtom, id=G2G.wxID_DEFORMSETSEL) G2frame.Bind(wx.EVT_MENU, SetDefDist, id=G2G.wxID_DEFORMDISTSET) - + # RB Models FillSelectPageMenu(TabSelectionIdDict, G2frame.dataWindow.RigidBodiesMenu) G2frame.Bind(wx.EVT_MENU, OnAutoFindResRB, id=G2G.wxID_AUTOFINDRESRB) @@ -16845,7 +16963,7 @@ def FillMenus(): G2frame.Bind(wx.EVT_MENU, OnPawleySelAll, id=G2G.wxID_PAWLEYSELALL) G2frame.Bind(wx.EVT_MENU, OnPawleySelNone, id=G2G.wxID_PAWLEYSELNONE) G2frame.Bind(wx.EVT_MENU, OnPawleyToggle, id=G2G.wxID_PAWLEYSELTOGGLE) - + def rbKeyPress(event): '''Respond to a Tab to highlight the next RB or crystal atom TODO: this is not getting called in Windows. Is a bind needed elsewhere? @@ -16869,8 +16987,8 @@ def rbKeyPress(event): rows = [0] elif rows[0] < 0: rows[0] = RigidBodies.atomsGrid.GetNumberRows()-1 - RigidBodies.atomsGrid.SelectRow(rows[0]) - RigidBodies.atomsGrid.MakeCellVisible(rows[0],0) + RigidBodies.atomsGrid.SelectRow(rows[0]) + RigidBodies.atomsGrid.MakeCellVisible(rows[0],0) data['testRBObj']['RBhighLight'] = rows[0] else: Ind = data['testRBObj'].get('CRYhighLight',[]) @@ -16895,12 +17013,12 @@ def rbKeyPress(event): G2plt.PlotStructure(G2frame,data,False,misc['UpdateTable']) G2frame.Raise() return - + #### UpdatePhaseData execution starts here # make sure that the phase menu bars get created before selecting # any (this will only be true on the first call to UpdatePhaseData) if callable(G2frame.dataWindow.DataGeneral): - wx.BeginBusyCursor() + wx.BeginBusyCursor() G2frame.dataWindow.DataGeneral() wx.EndBusyCursor() @@ -16918,16 +17036,16 @@ def rbKeyPress(event): data['General']['doDysnomia'] = False if 'modulated' in data['General']['Type']: data['General']['Modulated'] = True - data['General']['Type'] = 'nuclear' + data['General']['Type'] = 'nuclear' if 'RMC' not in data: data['RMC'] = {'RMCProfile':{},'fullrmc':{},'PDFfit':{}} if 'ISODISTORT' not in data: data['ISODISTORT'] = {} if 'Deformations' not in data: data['Deformations'] = {} -#end patch +#end patch - global rbAtmDict + global rbAtmDict rbAtmDict = {} misc = {} PhaseName = G2frame.GPXtree.GetItemText(Item) @@ -16937,7 +17055,7 @@ def rbKeyPress(event): G2frame.dataWindow.SetSizer(mainSizer) mainSizer.Add(G2frame.phaseDisplay,1,wx.ALL|wx.EXPAND,1) G2frame.phaseDisplay.gridList = [] # list of all grids in notebook - Pages = [] + Pages = [] General = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(General,'General') Pages.append('General') @@ -16945,7 +17063,7 @@ def rbKeyPress(event): DData = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(DData,'Data') Pages.append('Data') - AtomList = wx.Panel(G2frame.phaseDisplay) + AtomList = wx.Panel(G2frame.phaseDisplay) Atoms = G2G.GSGrid(AtomList) G2frame.phaseDisplay.gridList.append(Atoms) G2frame.phaseDisplay.AddPage(AtomList,'Atoms') @@ -16953,11 +17071,11 @@ def rbKeyPress(event): if data['General']['Modulated']: G2frame.waveData = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(G2frame.waveData,'Wave Data') - Pages.append('Wave Data') + Pages.append('Wave Data') if data['General']['Type'] == 'faulted': G2frame.layerData = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(G2frame.layerData,'Layers') - Pages.append('Layers') + Pages.append('Layers') drawOptions = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(drawOptions,'Draw Options') Pages.append('Draw Options') @@ -16966,27 +17084,27 @@ def rbKeyPress(event): G2frame.phaseDisplay.gridList.append(drawAtoms) G2frame.phaseDisplay.AddPage(drawAtomsList,'Draw Atoms') Pages.append('Draw Atoms') - + if any('X' in item for item in G2frame.GetHistogramTypes()): deformation = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(deformation,'Deformation') - + Pages.append('Deformation') - + if data['General']['Type'] not in ['faulted',] and not data['General']['Modulated']: RigidBodies = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(RigidBodies,'RB Models') # note the bind is here so that it is only done once, but - # TODO: might need to be on a different widget for Windows + # TODO: might need to be on a different widget for Windows RigidBodies.Bind(wx.EVT_CHAR,rbKeyPress) Pages.append('RB Models') - + MapPeakList = wx.Panel(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(MapPeakList,'Map peaks') # create the grid once; N.B. need to reference at this scope G2frame.MapPeaks = G2G.GSGrid(MapPeakList) - G2frame.phaseDisplay.gridList.append(G2frame.MapPeaks) - + G2frame.phaseDisplay.gridList.append(G2frame.MapPeaks) + if data['General']['doDysnomia']: G2frame.MEMData = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(G2frame.MEMData,'Dysnomia') @@ -17002,7 +17120,7 @@ def rbKeyPress(event): G2frame.FRMC = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(G2frame.FRMC,'RMC') Pages.append('RMC') - + if data['General']['Type'] == 'nuclear': ISODIST = wx.ScrolledWindow(G2frame.phaseDisplay) G2frame.phaseDisplay.AddPage(ISODIST,'ISODISTORT') @@ -17107,10 +17225,10 @@ def CheckAddHKLF(G2frame,data): return result def checkPDFfit(G2frame): - '''Checks to see if PDFfit2 is available and can be imported. PDFfit2 can be installed + '''Checks to see if PDFfit2 is available and can be imported. PDFfit2 can be installed in a separate Python interpreter (saved in the pdffit2_exec config variable). If this is - defined, no attempt is made to check that it actually runs. - Otherwise, if diffpy.PDFfit has been installed with conda/pip, it is checked if the + defined, no attempt is made to check that it actually runs. + Otherwise, if diffpy.PDFfit has been installed with conda/pip, it is checked if the install command. :returns: False if PDFfit2 cannot be run/accessed. True if it appears it can be run. @@ -17123,6 +17241,7 @@ def checkPDFfit(G2frame): # see if diffpy has been installed directly try: from diffpy.pdffit2 import PdfFit + PdfFit return True except: pass @@ -17131,10 +17250,11 @@ def checkPDFfit(G2frame): # Python in a separate environment try: # have conda. Can we access it programmatically? import conda.cli.python_api + conda.cli.python_api except: G2G.G2MessageBox(G2frame,'You are running a directly installed Python. You will need to install PDFfit2 directly as well, preferably in a separate virtual environment.') return - + msg = ('Do you want to use conda to install PDFfit2 into a separate environment? '+ '\n\nIf successful, the pdffit2_exec configuration option will be set to the '+ 'this new Python environment.') @@ -17185,7 +17305,7 @@ def makeIsoNewPhase(phData,cell,atomList,sglbl,sgnum): print(sglbl,'empty structure') return # create a new phase - try: + try: sgnum = int(sgnum) sgsym = G2spc.spgbyNum[sgnum] #sgname = sgsym.replace(" ","") @@ -17202,7 +17322,7 @@ def makeIsoNewPhase(phData,cell,atomList,sglbl,sgnum): cx,ct,cs,cia = generalData['AtomPtrs'] Atoms = newPhase['Atoms'] = [] for nam,(x,y,z) in atomList: - try: + try: atom = [] atom.append(nam) if nam[1].isdigit(): @@ -17216,7 +17336,7 @@ def makeIsoNewPhase(phData,cell,atomList,sglbl,sgnum): atom.append(SytSym) atom.append(Mult) atom.append('I') - atom += [0.02,0.,0.,0.,0.,0.,0.,] + atom += [0.02,0.,0.,0.,0.,0.,0.,] atom.append(ran.randint(0,sys.maxsize)) Atoms.append(atom) except Exception as msg: @@ -17225,7 +17345,7 @@ def makeIsoNewPhase(phData,cell,atomList,sglbl,sgnum): return newPhase def saveIsoNewPhase(G2frame,phData,newPhase,orgFilName): - '''save the new phase generated by ISOCIF created in :func:`makeIsoNewPhase` + '''save the new phase generated by ISOCIF created in :func:`makeIsoNewPhase` into a GSAS-II project (.gpx) file ''' import re @@ -17247,3 +17367,36 @@ def saveIsoNewPhase(G2frame,phData,newPhase,orgFilName): G2frame.GSASprojectfile = f'{s[0]}_{num}.gpx' G2IO.ProjFileSave(G2frame) return G2frame.GSASprojectfile + +def renamePhaseNameTop(G2frame,data,phaseItem,generalData,newName): + '''Called to rename the phase. Updates the tree and items that + reference the file name. + ''' + oldName = generalData['Name'] + phaseRIdList,usedHistograms = G2frame.GetPhaseInfofromTree() + phaseNameList = usedHistograms.keys() # phase names in use + if newName and newName != oldName: + newName = G2obj.MakeUniqueLabel(newName,list(phaseNameList)) + generalData['Name'] = newName + G2frame.G2plotNB.Rename(oldName,generalData['Name']) + G2frame.GPXtree.SetItemText(phaseItem,generalData['Name']) + # change phase name key in Reflection Lists for each histogram + for hist in data['Histograms']: + ht = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist) + rt = G2gd.GetGPXtreeItemId(G2frame,ht,'Reflection Lists') + if not rt: continue + RfList = G2frame.GPXtree.GetItemPyData(rt) + if oldName not in RfList: + print('Warning: '+oldName+' not in Reflection List for '+ + hist) + continue + RfList[newName] = RfList[oldName] + del RfList[oldName] + # rename Restraints + resId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints') + Restraints = G2frame.GPXtree.GetItemPyData(resId) + i = G2gd.GetGPXtreeItemId(G2frame,resId,oldName) + if i: G2frame.GPXtree.SetItemText(i,newName) + if len(Restraints) and oldName in Restraints: + Restraints[newName] = Restraints[oldName] + del Restraints[oldName] diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index 0c4920cc4..c8ab749ef 100644 --- a/GSASII/GSASIIplot.py +++ b/GSASII/GSASIIplot.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ''' -Classes and routines defined in :mod:`GSASIIplot` follow. +Classes and routines defined in :mod:`GSASIIplot` follow. ''' # Note that documentation for GSASIIplot.py has been moved # to file docs/source/GSASIIplot.rst @@ -13,7 +13,7 @@ import numpy as np import numpy.ma as ma import numpy.linalg as nl -import GSASIIpath +from . import GSASIIpath # Don't depend on wx/matplotlib/scipy for scriptable; or for Sphinx docs try: import wx @@ -33,28 +33,32 @@ print('GSASIIplot: matplotlib not imported') if GSASIIpath.GetConfigValue('debug'): print('error msg:',err) -import GSASIIdataGUI as G2gd -import GSASIIimage as G2img -import GSASIIpwd as G2pwd -import GSASIImiscGUI as G2IO -import GSASIIpwdGUI as G2pdG -import GSASIIimgGUI as G2imG -import GSASIIphsGUI as G2phG -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIImath as G2mth -import GSASIIctrlGUI as G2G -import GSASIIobj as G2obj -import GSASIIpwdplot as G2pwpl +from . import GSASIIdataGUI as G2gd +from . import GSASIIimage as G2img +from . import GSASIIpwd as G2pwd +from . import GSASIImiscGUI as G2IO +from . import GSASIIpwdGUI as G2pdG +from . import GSASIIimgGUI as G2imG +from . import GSASIIphsGUI as G2phG +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIImath as G2mth +from . import GSASIIctrlGUI as G2G +from . import GSASIIobj as G2obj +from . import GSASIIpwdplot as G2pwpl +from . import GSASIIfiles as G2fil try: - import pytexture as ptx + if GSASIIpath.binaryPath: # TODO: I think this may use a fair amount of memory; delay import? + import pytexture as ptx + else: + import GSASII.pytexture as ptx ptx.pyqlmninit() except ImportError: print('binary load error: pytexture not found') #import scipy.special as spsp import OpenGL.GL as GL import OpenGL.GLU as GLU -import gltext +from . import gltext import matplotlib.colors as mpcls try: from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas @@ -70,6 +74,10 @@ from matplotlib.backends.backend_agg import FigureCanvas as hcCanvas # standard name except RuntimeError: # happens during doc builds pass +try: + import imageio +except ImportError: + G2fil.NeededPackage({'Saving movies':['imageio']}) # useful degree trig functions sind = lambda x: math.sin(x*math.pi/180.) @@ -100,7 +108,7 @@ nxs = np.newaxis plotDebug = False -#matplotlib 2.0.x dumbed down Paired to 16 colors - +#matplotlib 2.0.x dumbed down Paired to 16 colors - # this restores the pre 2.0 Paired color map found in matplotlib._cm.py try: _Old_Paired_data = {'blue': [(0.0, 0.89019608497619629, @@ -140,12 +148,12 @@ 0.7921568751335144, 0.7921568751335144), (0.81818181818181823, 0.41568627953529358, 0.41568627953529358), (0.90909090909090906, 1.0, 1.0), (1.0, 0.69411766529083252, 0.69411766529083252)]} - '''In matplotlib 2.0.x+ the Paired color map was dumbed down to 16 colors. - _Old_Paired_data is the pre-2.0 Paired color map found in + '''In matplotlib 2.0.x+ the Paired color map was dumbed down to 16 colors. + _Old_Paired_data is the pre-2.0 Paired color map found in matplotlib._cm.py and is used to creat color map GSPaired. - This can be done on request for other color maps. N.B. any new names - must be explicitly added to the color list obtained from + This can be done on request for other color maps. N.B. any new names + must be explicitly added to the color list obtained from mpl.cm.datad.keys() (currently 10 places in GSAS-II code). ''' oldpaired = mpl.colors.LinearSegmentedColormap('GSPaired',_Old_Paired_data,N=256) @@ -167,13 +175,13 @@ mpl.cm.register_cmap(cmap=oldpaired_r,name='GSPaired_r') #deprecated except Exception as err: if GSASIIpath.GetConfigValue('debug'): print('\nMPL CM setup error: {}\n'.format(err)) - + def GetColorMap(color): try: return mpl.colormaps[color] except: return mpl.cm.get_cmap(color) - + def Write2csv(fil,dataItems,header=False): '''Write a line to a CSV file @@ -191,7 +199,7 @@ def Write2csv(fil,dataItems,header=False): line += item fil.write(line+'\n') -class _tabPlotWin(wx.Panel): +class _tabPlotWin(wx.Panel): 'Creates a basic tabbed plot window for GSAS-II graphics' def __init__(self,parent,id=-1,dpi=None,**kwargs): self.replotFunction = None @@ -200,8 +208,8 @@ def __init__(self,parent,id=-1,dpi=None,**kwargs): self.plotInvalid = False # valid self.plotRequiresRedraw = True # delete plot if not updated wx.Panel.__init__(self,parent,id=id,**kwargs) - -class G2PlotMpl(_tabPlotWin): + +class G2PlotMpl(_tabPlotWin): 'Creates a Matplotlib 2-D plot in the GSAS-II graphics window' def __init__(self,parent,id=-1,dpi=None,publish=None,**kwargs): _tabPlotWin.__init__(self,parent,id=id,**kwargs) @@ -213,7 +221,7 @@ def __init__(self,parent,id=-1,dpi=None,publish=None,**kwargs): self.toolbar.Realize() self.plotStyle = {'qPlot':False,'dPlot':False,'sqrtPlot':False,'sqPlot':False, 'logPlot':False,'exclude':False,'partials':True,'chanPlot':False} - + sizer=wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas,1,wx.EXPAND) sizer.Add(self.toolbar,0,) @@ -224,14 +232,14 @@ def SetToolTipString(self,text): return self.canvas.SetToolTip(text) else: return self.canvas.SetToolTipString(text) - + def ToolBarDraw(self): mplv = eval(mpl.__version__.replace('.',',')) if mplv[0] >= 3 and mplv[1] >= 3: self.toolbar.canvas.draw_idle() else: self.toolbar.draw() - + class G2PlotOgl(_tabPlotWin): 'Creates an OpenGL plot in the GSAS-II graphics window' def __init__(self,parent,id=-1,dpi=None,**kwargs): @@ -253,13 +261,13 @@ def __init__(self,parent,id=-1,dpi=None,**kwargs): sizer=wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas,1,wx.EXPAND) self.SetSizer(sizer) - + def SetToolTipString(self,text): if 'phoenix' in wx.version(): self.canvas.SetToolTip(wx.ToolTip(text)) else: self.canvas.SetToolTipString(text) - + class G2Plot3D(_tabPlotWin): 'Creates a 3D Matplotlib plot in the GSAS-II graphics window' def __init__(self,parent,id=-1,dpi=None,**kwargs): @@ -269,18 +277,18 @@ def __init__(self,parent,id=-1,dpi=None,**kwargs): self.toolbar = GSASIItoolbar(self.canvas,Arrows=False) self.toolbar.Realize() - + sizer=wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas,1,wx.EXPAND) sizer.Add(self.toolbar,) self.SetSizer(sizer) - + def SetToolTipString(self,text): if 'phoenix' in wx.version(): self.canvas.SetToolTip(wx.ToolTip(text)) else: self.canvas.SetToolTipString(text) - + def ToolBarDraw(self): mplv = eval(mpl.__version__.replace('.',',')) if mplv[0] >= 3 and mplv[1] >= 3: @@ -292,7 +300,7 @@ def ToolBarDraw(self): # self.toolbar.draw_idle() # else: # self.toolbar.draw() - + class G2PlotNoteBook(wx.Panel): 'create a tabbed panel to hold a GSAS-II graphics window' def __init__(self,parent,id=-1,G2frame=None): @@ -303,24 +311,24 @@ def __init__(self,parent,id=-1,G2frame=None): sizer = wx.BoxSizer() sizer.Add(self.nb,1,wx.EXPAND) self.SetSizer(sizer) - self.status = parent.CreateStatusBar() + self.status = parent.CreateStatusBar() # self.status.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) # unneeded # self.status.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)) # ignored, alas self.status.SetFieldsCount(2) self.status.firstLen = 150 - self.status.SetStatusWidths([self.status.firstLen,-1]) + self.status.SetStatusWidths([self.status.firstLen,-1]) self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnPageChanged) self.nb.Bind(wx.EVT_KEY_UP,self.OnNotebookKey) self.G2frame = G2frame self.MPLwarn = False - + self.plotList = [] # contains the tab label for each plot self.panelList = [] # contains the panel object for each plot #self.skipPageChange = False # set to True when no plot update is needed self.allowZoomReset = True # this indicates plot should be updated not initialized # (BHT: should this be in tabbed panel rather than here?) self.lastRaisedPlotTab = None - + def OnNotebookKey(self,event): '''Called when a keystroke event gets picked up by the notebook window rather the child. This is not expected, but somehow it does sometimes @@ -369,7 +377,7 @@ def GetTabIndex(self,label): independent to the order it is dragged to -- at least in Windows) as well as the associated wx.Panel - An exception is raised if the label is not found + An exception is raised if the label is not found ''' for i in range(self.nb.GetPageCount()): if label == self.nb.GetPageText(i): @@ -395,7 +403,7 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None): Record the name of the this plot in self.lastRaisedPlotTab :param str label: title of plot - :param str Type: determines the type of plot that will be opened. + :param str Type: determines the type of plot that will be opened. 'mpl' for 2D graphs in matplotlib 'ogl' for openGL @@ -403,16 +411,16 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None): :param bool newImage: forces creation of a new graph for matplotlib plots only (defaults as True) - :param function publish: reference to routine used to create a - publication version of the current mpl plot (default is None, + :param function publish: reference to routine used to create a + publication version of the current mpl plot (default is None, which prevents use of this). - :returns: new,plotNum,Page,Plot,limits where + :returns: new,plotNum,Page,Plot,limits where * new: will be True if the tab was just created - * plotNum: is the tab number - * Page: is the subclassed wx.Panel (:class:`G2PlotMpl`, etc.) where + * plotNum: is the tab number + * Page: is the subclassed wx.Panel (:class:`G2PlotMpl`, etc.) where the plot appears - * Plot: the mpl.Axes object for the graphic (mpl) or the figure for + * Plot: the mpl.Axes object for the graphic (mpl) or the figure for openGL. * limits: for mpl plots, when a plot already exists, this will be a tuple with plot scaling. None otherwise. @@ -456,15 +464,15 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None): self.lastRaisedPlotTab = label self.RaisePageNoRefresh(Page) # Save the help name from the DataItem that has created the plot in Tabbed page object - # so we can use it in self.OnHelp(). + # so we can use it in self.OnHelp(). # Are there any cases where plot tabs are created that are not tied to Data Tree entries? - # One example is GSASII.SumDialog, where a test plot is created. Are there others? + # One example is GSASII.SumDialog, where a test plot is created. Are there others? try: Page.helpKey = self.G2frame.dataWindow.helpKey except AttributeError: Page.helpKey = 'Data tree' return new,plotNum,Page,Plot,limits - + def _addPage(self,name,page): '''Add the newly created page to the notebook and associated lists. :param name: the label placed on the tab, which should be unique @@ -476,7 +484,7 @@ def _addPage(self,name,page): self.nb.AddPage(page,name) self.plotList.append(name) # used to lookup plot in self.panelList # Note that order in lists make not agree with actual tab order; use self.nb.GetPageText(i) - # where (self=G2plotNB) for latter + # where (self=G2plotNB) for latter self.panelList.append(page) # panel object for plot self.lastRaisedPlotTab = name #page.plotInvalid = False # plot has just been drawn @@ -485,13 +493,13 @@ def _addPage(self,name,page): #page.replotArgs = [] #page.replotKWargs = {} #self.skipPageChange = False - + def addMpl(self,name="",publish=None): 'Add a tabbed page with a matplotlib plot' page = G2PlotMpl(self.nb,publish=publish) self._addPage(name,page) return page.figure - + def add3D(self,name=""): 'Add a tabbed page with a 3D plot' page = G2Plot3D(self.nb) @@ -503,16 +511,16 @@ def add3D(self,name=""): elif not self.MPLwarn: # patch for bad MPL 3D self.MPLwarn = True G2G.G2MessageBox(self,'3D plots with Matplotlib 3.1.x and 3.2.x are distorted, use MPL 3.0.3 or 3.3. You have '+mpl.__version__, - 'Avoid Matplotlib 3.1 & 3.2') + 'Avoid Matplotlib 3.1 & 3.2') return page.figure - + def addOgl(self,name=""): 'Add a tabbed page with an openGL plot' page = G2PlotOgl(self.nb) self._addPage(name,page) self.RaisePageNoRefresh(page) # need to give window focus before GL use return page.figure - + def Delete(self,name): 'delete a tabbed page' try: @@ -521,8 +529,8 @@ def Delete(self,name): del self.panelList[item] self.nb.DeletePage(item) except ValueError: #no plot of this name - do nothing - return - + return + def clear(self): 'clear all pages from plot window' for i in range(self.nb.GetPageCount()-1,-1,-1): @@ -530,7 +538,7 @@ def clear(self): self.plotList = [] self.panelList = [] self.status.DestroyChildren() #get rid of special stuff on status bar - + def Rename(self,oldName,newName): 'rename a tab' try: @@ -538,20 +546,20 @@ def Rename(self,oldName,newName): self.plotList[item] = newName self.nb.SetPageText(item,newName) except ValueError: #no plot of this name - do nothing - return - + return + def RaisePageNoRefresh(self,Page): 'Raises a plot tab without triggering a refresh via OnPageChanged' if plotDebug: print ('Raise'+str(self).split('0x')[1]) #self.skipPageChange = True Page.SetFocus() #self.skipPageChange = False - - def SetSelectionNoRefresh(self,plotNum): - 'Raises a plot tab without triggering a refresh via OnPageChanged' + + def SetSelectionNoRefresh(self,plotNum): + 'Raises a plot tab without triggering a refresh via OnPageChanged' if plotDebug: print ('Select'+str(self).split('0x')[1]) #self.skipPageChange = True - self.nb.SetSelection(plotNum) # raises plot tab + self.nb.SetSelection(plotNum) # raises plot tab Page = self.G2frame.G2plotNB.nb.GetPage(plotNum) Page.SetFocus() #self.skipPageChange = False @@ -566,7 +574,7 @@ def OnPageChanged(self,event): ''' tabLabel = event.GetEventObject().GetPageText(event.GetSelection()) self.lastRaisedPlotTab = tabLabel - if plotDebug: + if plotDebug: print ('PageChanged, self='+str(self).split('0x')[1]+tabLabel) print ('event type=',event.GetEventType()) self.status.DestroyChildren() #get rid of special stuff on status bar @@ -575,10 +583,10 @@ def OnPageChanged(self,event): def SetHelpButton(self,help): '''Adds a Help button to the status bar on plots. - - TODO: This has a problem with G2pwpl.PlotPatterns where creation of the - HelpButton causes the notebook tabs to be duplicated. A manual - resize fixes that, but the SendSizeEvent has not worked. + + TODO: This has a problem with G2pwpl.PlotPatterns where creation of the + HelpButton causes the notebook tabs to be duplicated. A manual + resize fixes that, but the SendSizeEvent has not worked. ''' hlp = G2G.HelpButton(self.status,helpIndex=help) rect = self.status.GetFieldRect(1) @@ -587,16 +595,16 @@ def SetHelpButton(self,help): rect.y += 1 hlp.SetRect(rect) #wx.CallLater(100,self.TopLevelParent.SendSizeEvent) - + def InvokeTreeItem(self,pid): '''This is called to select an item from the tree using the self.allowZoomReset flag to prevent a reset to the zoom of the plot (where implemented) ''' - self.allowZoomReset = False + self.allowZoomReset = False if pid: self.G2frame.GPXtree.SelectItem(pid) self.allowZoomReset = True if plotDebug: print ('invoke'+str(self).split('0x')[1]+str(pid)) - + class GSASIItoolbar(Toolbar): 'Override the matplotlib toolbar so we can add more icons' def __init__(self,plotCanvas,publish=None,Arrows=True): @@ -627,12 +635,12 @@ def __init__(self,plotCanvas,publish=None,Arrows=True): if publish: self.AddToolBarTool('Publish plot','Create publishable version of plot','publish.ico',publish) self.Realize() - + def set_message(self,s): ''' this removes spurious text messages from the tool bar ''' pass - + def AddToolBarTool(self,label,title,filename,callback): bmpFilename = GSASIIpath.getIconFile(filename) if bmpFilename is None: @@ -654,13 +662,13 @@ def _update_view(self): if self.updateActions: wx.CallAfter(*self.updateActions) Toolbar._update_view(self) - + def AnyActive(self): for Itool in range(self.GetToolsCount()): if self.GetToolState(self.GetToolByPos(Itool).GetId()): return True return False - + def GetActive(self): for Itool in range(self.GetToolsCount()): tool = self.GetToolByPos(Itool) @@ -727,13 +735,13 @@ def OnArrow(self,event): # self.parent.toolbar.push_current() if self.updateActions: wx.CallAfter(*self.updateActions) - + def OnHelp(self,event): 'Respond to press of help button on plot toolbar' bookmark = self.Parent.helpKey # get help category used to create plot #if GSASIIpath.GetConfigValue('debug'): print 'plot help: key=',bookmark G2G.ShowHelp(bookmark,self.TopLevelParent) - + def OnKey(self,event): '''Provide user with list of keystrokes defined for plot as well as an alternate way to access the same functionality @@ -809,7 +817,7 @@ def get_zoompan(self): # self.ToggleTool(self.wx_ids['Pan'], False) # else: # print('Unable to reset Pan button, please report this with matplotlib version') - + def SetCursor(page): mode = page.toolbar.GetActive() if mode == 'Pan': @@ -827,7 +835,7 @@ def SetCursor(page): page.canvas.Cursor = wx.Cursor(wx.CURSOR_CROSS) else: page.canvas.SetCursor(wx.StockCursor(wx.CURSOR_CROSS)) - + def PlotFPAconvolutors(G2frame,NISTpk,conv2T=None,convI=None,convList=None): '''Plot the convolutions used for the current peak computed with :func:`GSASIIfpaGUI.doFPAcalc` @@ -890,7 +898,7 @@ def SetupLegendPick(legend,new,delay=5): txt.set_pickradius(4) if new: legend.figure.canvas.mpl_connect('pick_event',onLegendPick) - + def onLegendPick(event): '''When a line in the legend is selected, find the matching line in the plot and then highlight it by adding/enlarging markers. @@ -922,7 +930,7 @@ def clearHighlight(event): else: #GSASIIpath.IPyBreak() return - + for l in plot.get_lines(): if lbl == l.get_label(): canvas.timer = wx.Timer() @@ -941,8 +949,8 @@ def clearHighlight(event): break else: print('Warning: artist matching ',lbl,' not found') - -#### PlotSngl ################################################################ + +#### PlotSngl ################################################################ def PlotSngl(G2frame,newPlot=False,Data=None,hklRef=None,Title=''): '''Structure factor plotting package - displays zone of reflections as rings proportional to F, F**2, etc. as requested via matpltlib; plots are not geometrically correct @@ -950,7 +958,7 @@ def PlotSngl(G2frame,newPlot=False,Data=None,hklRef=None,Title=''): from matplotlib.patches import Circle global HKL,HKLF,HKLref HKLref = hklRef - + def OnSCKeyPress(event): i = zones.index(Data['Zone']) newPlot = False @@ -977,11 +985,11 @@ def OnSCKeyPress(event): Data['Layer'] = 0 Data['Scale'] = 1.0 elif event.key in hklfChoice and 'HKLF' in Name: - Data['Type'] = hklfChoice[event.key] + Data['Type'] = hklfChoice[event.key] newPlot = True elif event.key in pwdrChoice and 'PWDR' in Name: - Data['Type'] = pwdrChoice[event.key] - newPlot = True + Data['Type'] = pwdrChoice[event.key] + newPlot = True PlotSngl(G2frame,newPlot,Data,HKLref,Title) def OnSCMotion(event): @@ -998,7 +1006,7 @@ def OnSCMotion(event): HKLtxt = '(%d,%d,%d)'%(xpos,ypos,zpos) Page.SetToolTipString(HKLtxt) G2frame.G2plotNB.status.SetStatusText('HKL = '+HKLtxt,0) - + def OnSCPress(event): zpos = Data['Layer'] xpos = event.xdata @@ -1016,15 +1024,15 @@ def OnSCPress(event): Fosq,sig,Fcsq = hklf[0] HKLtxt = '( %.2f %.3f %.2f %.2f)'%(Fosq,sig,Fcsq,(Fosq-Fcsq)/(scale*sig)) G2frame.G2plotNB.status.SetStatusText('Fosq, sig, Fcsq, delFsq/sig = '+HKLtxt,1) - + def OnPick(event): pick = event.artist HKLtext = pick.get_gid() Page.SetToolTipString(HKLtext) G2frame.G2plotNB.status.SetStatusText('H = '+HKLtext,0) - + if not G2frame.PatternId: - return + return Name = G2frame.GPXtree.GetItemText(G2frame.PatternId) if not Title: Title = Name @@ -1048,8 +1056,8 @@ def OnPick(event): Plot.set_aspect(aspect='equal') except: #broken in mpl 3.1.1; worked in mpl 3.0.3 pass - - Type = Data['Type'] + + Type = Data['Type'] scale = Data['Scale'] HKLmax = Data['HKLmax'] HKLmin = Data['HKLmin'] @@ -1110,7 +1118,7 @@ def OnPick(event): if sig > 0.: A = scale*(Fosq-Fcsq)/(3*sig) if abs(A) < 1.0: A = 0 - B = 0 + B = 0 elif Type == '|DFsq|>3sig': if sig > 0.: A = scale*(Fosq-Fcsq)/(3*sig) @@ -1121,10 +1129,10 @@ def OnPick(event): if refl[3]: hid = '(%d,%d,%d,%d)'%(refl[0],refl[1],refl[2],refl[3]) else: - hid = '(%d,%d,%d)'%(refl[0],refl[1],refl[2]) + hid = '(%d,%d,%d)'%(refl[0],refl[1],refl[2]) else: h = H - hid = '(%d,%d,%d)'%(refl[0],refl[1],refl[2]) + hid = '(%d,%d,%d)'%(refl[0],refl[1],refl[2]) xy = (h[pzone[izone][0]],h[pzone[izone][1]]) if Type in ['|DFsq|/sig','|DFsq|>sig','|DFsq|>3sig']: if A > 0.0: @@ -1145,10 +1153,10 @@ def OnPick(event): if radius > 0: if A > B: if refl[3+Super] < 0: - Plot.add_artist(Circle(xy,radius=radius,ec=(0.,1,.0,.1),fc='g')) + Plot.add_artist(Circle(xy,radius=radius,ec=(0.,1,.0,.1),fc='g')) else: Plot.add_artist(Circle(xy,radius=radius,fc='g',ec='g')) - else: + else: if refl[3+Super] < 0: Plot.add_artist(Circle(xy,radius=radius,fc=(1.,0.,0.,.1),ec='r')) else: @@ -1174,7 +1182,7 @@ def OnPick(event): Plot.set_xlim((HKLmin[pzone[izone][0]],HKLmax[pzone[izone][0]])) Plot.set_ylim((HKLmin[pzone[izone][1]],HKLmax[pzone[izone][1]])) Page.canvas.draw() - + #### Plot1DSngl ################################################################################ def Plot1DSngl(G2frame,newPlot=False,hklRef=None,Super=0,Title=False): '''1D Structure factor plotting package - displays reflections as sticks proportional @@ -1190,7 +1198,7 @@ def OnKeyPress(event): elif event.key == 'v': Page.vaxis = not Page.vaxis Draw() - + def OnPick(event): H = hkl[:,event.ind[0]] Page.SetToolTipString('#%d: %d,%d,%d'%(event.ind[0],H[0],H[1],H[2])) @@ -1235,7 +1243,7 @@ def OnMotion(event): except TypeError: G2frame.G2plotNB.status.SetStatusText('Select '+Title+Name+' pattern first',1) Page.SetToolTipString(s) - + def Draw(): global xylim,hkl Plot.clear() @@ -1258,7 +1266,7 @@ def Draw(): X = np.nan_to_num(np.sqrt(Fcsq)) Y = np.sqrt(Fosq) Z = 0.5*sig/np.sqrt(Fosq) - else: + else: Plot.set_xlabel(r'Fc'+super2,fontsize=14) Plot.set_ylabel(r'Fo'+super2,fontsize=14) X = Fcsq.copy() @@ -1267,17 +1275,17 @@ def Draw(): elif Page.qaxis: Plot.set_xlabel(r'q, '+Angstr+Pwrm1,fontsize=14) X = 2.*np.pi/d #q - else: + else: X = d #d if Page.faxis and not Page.vaxis: Plot.set_ylabel(r'F',fontsize=14) Y = np.nan_to_num(np.sqrt(Fcsq)) Z = np.nan_to_num(np.sqrt(Fosq)) - elif not Page.vaxis: + elif not Page.vaxis: Y = Fcsq.copy() Z = Fosq.copy() Ymax = np.max(Y) - + if not Page.vaxis: XY = np.vstack((X,X,np.zeros_like(X),Y)).reshape((2,2,-1)).T XZ = np.vstack((X,X,np.zeros_like(X),Z)).reshape((2,2,-1)).T @@ -1326,10 +1334,10 @@ def Draw(): Page.canvas.mpl_connect('motion_notify_event', OnMotion) Page.canvas.mpl_connect('pick_event', OnPick) Page.Offset = [0,0] - + Page.Choice = (' key press','g: toggle grid','f: toggle Fhkl/F^2hkl plot','q: toggle q/d plot','v: toggle Fo/Fc plot') Draw() - + #### Plot3DSngl ################################################################################ def Plot3DSngl(G2frame,newPlot=False,Data=None,hklRef=None,Title=False): '''3D Structure factor plotting package - displays reflections as spots proportional @@ -1370,7 +1378,7 @@ def OnKeyBox(event): cb.SetValue(' save as/key:') wx.CallAfter(OnKey,event) Page.canvas.SetFocus() # redirect the Focus from the button back to the plot - + def OnKey(event): #on key UP!! global ifBox Choice = {'F':'Fo','S':'Fosq','U':'Unit','D':'dFsq','W':'dFsq/sig'} @@ -1384,7 +1392,7 @@ def OnKey(event): #on key UP!! key = str(event.key).upper() if key in ['C','H','K','L']: if key == 'C': - Data['Zone'] = False + Data['Zone'] = False key = 'L' Data['viewKey'] = key drawingData['viewPoint'][0] = np.array(drawingData['default']) @@ -1447,7 +1455,7 @@ def OnKey(event): #on key UP!! elif key in Choice: Data['Type'] = Choice[key] Draw('key') - + Name = G2frame.GPXtree.GetItemText(G2frame.PatternId) if Title and Title in G2frame.GetPhaseData(): #NB: save image as e.g. jpeg will fail if False; MyDir is unknown generalData = G2frame.GetPhaseData()[Title]['General'] @@ -1474,11 +1482,11 @@ def OnKey(event): #on key UP!! Bl = np.array([0,0,255]) uBox = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0],[0,0,1],[1,0,1],[1,1,1],[0,1,1]]) uEdges = np.array([ - [uBox[0],uBox[1]],[uBox[0],uBox[3]],[uBox[0],uBox[4]],[uBox[1],uBox[2]], - [uBox[2],uBox[3]],[uBox[1],uBox[5]],[uBox[2],uBox[6]],[uBox[3],uBox[7]], + [uBox[0],uBox[1]],[uBox[0],uBox[3]],[uBox[0],uBox[4]],[uBox[1],uBox[2]], + [uBox[2],uBox[3]],[uBox[1],uBox[5]],[uBox[2],uBox[6]],[uBox[3],uBox[7]], [uBox[4],uBox[5]],[uBox[5],uBox[6]],[uBox[6],uBox[7]],[uBox[7],uBox[4]]]) uColors = [Rd,Gr,Bl, Wt,Wt,Wt, Wt,Wt,Wt, Wt,Wt,Wt] - + def FillHKLRC(): sumFo2 = 0. sumDF2 = 0. @@ -1552,7 +1560,7 @@ def FillHKLRC(): RF2 = 100. if sumFo and sumDF: RF = 100.*sumDF/sumFo - RF2 = 100.*sumDF2/sumFo2 + RF2 = 100.*sumDF2/sumFo2 return HKL,zip(list(R),C),RF,RF2 def GetTruePosition(xy): @@ -1571,16 +1579,16 @@ def GetTruePosition(xy): return [int(h),int(k),int(l)] except ValueError: return [int(h),int(k),int(l)] - - + + def SetTranslation(newxy): -#first get translation vector in screen coords. +#first get translation vector in screen coords. oldxy = drawingData['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy drawingData['oldxy'] = list(newxy) V = np.array([-dxy[0],dxy[1],0.]) -#then transform to rotated crystal coordinates & apply to view point +#then transform to rotated crystal coordinates & apply to view point Q = drawingData['Quaternion'] V = np.inner(Bmat,G2mth.prodQVQ(G2mth.invQ(Q),V)) Tx,Ty,Tz = drawingData['viewPoint'][0] @@ -1588,10 +1596,10 @@ def SetTranslation(newxy): Ty += V[1]*0.1 Tz += V[2]*0.1 drawingData['viewPoint'][0] = np.array([Tx,Ty,Tz]) - + def SetRotation(newxy): 'Perform a rotation in x-y space due to a left-mouse drag' - #first get rotation vector in screen coords. & angle increment + #first get rotation vector in screen coords. & angle increment oldxy = drawingData['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy @@ -1611,9 +1619,9 @@ def SetRotation(newxy): VD = np.inner(Bmat,G2mth.Q2Mat(Q)[2]) VD /= np.sqrt(np.sum(VD**2)) drawingData['viewDir'] = VD - - def SetRotationZ(newxy): -#first get rotation vector (= view vector) in screen coords. & angle increment + + def SetRotationZ(newxy): +#first get rotation vector (= view vector) in screen coords. & angle increment View = GL.glGetIntegerv(GL.GL_VIEWPORT) cent = [View[2]/2,View[3]/2] oldxy = drawingData['oldxy'] @@ -1628,7 +1636,7 @@ def SetRotationZ(newxy): if newxy[0] > cent[0]: A[0] *= -1 if newxy[1] < cent[1]: - A[1] *= -1 + A[1] *= -1 # next transform vector back to xtal coordinates & make new quaternion Q = drawingData['Quaternion'] V = np.inner(Amat,V) @@ -1641,12 +1649,12 @@ def SetRotationZ(newxy): def OnMouseDown(event): xy = event.GetPosition() drawingData['oldxy'] = list(xy) - + def OnMouseMove(event): if event.ShiftDown(): #don't want any inadvertant moves when picking return newxy = event.GetPosition() - + if event.Dragging(): if event.LeftIsDown(): SetRotation(newxy) @@ -1662,19 +1670,19 @@ def OnMouseMove(event): h,k,l = hkl Page.SetToolTipString('%d,%d,%d'%(h,k,l)) G2frame.G2plotNB.status.SetStatusText('hkl = %d,%d,%d'%(h,k,l),1) - + def OnMouseWheel(event): if event.ShiftDown(): return drawingData['cameraPos'] += event.GetWheelRotation()/120. drawingData['cameraPos'] = max(0.1,min(20.00,drawingData['cameraPos'])) Draw('wheel') - + def SetBackground(): R,G,B,A = Page.camera['backColor'] GL.glClearColor(R,G,B,A) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - + def SetLights(): try: GL.glEnable(GL.GL_DEPTH_TEST) @@ -1687,7 +1695,7 @@ def SetLights(): GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE,0) GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[1,1,1,1]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[1,1,1,1]) - + def RenderBox(x,y,z): GL.glEnable(GL.GL_COLOR_MATERIAL) GL.glLineWidth(1) @@ -1703,7 +1711,7 @@ def RenderBox(x,y,z): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderUnitVectors(x,y,z,labxyz=['','','']): GL.glEnable(GL.GL_COLOR_MATERIAL) GL.glLineWidth(1) @@ -1727,7 +1735,7 @@ def RenderUnitVectors(x,y,z,labxyz=['','','']): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderDots(XYZ,RC): GL.glEnable(GL.GL_COLOR_MATERIAL) XYZ = np.array(XYZ) @@ -1743,9 +1751,9 @@ def RenderDots(XYZ,RC): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def Draw(caller=''): -#useful debug? +#useful debug? # if caller: # print caller # end of useful debug @@ -1762,7 +1770,7 @@ def Draw(caller=''): GS[0][1] = GS[1][0] = math.sqrt(GS[0][0]*GS[1][1]) GS[0][2] = GS[2][0] = math.sqrt(GS[0][0]*GS[2][2]) GS[1][2] = GS[2][1] = math.sqrt(GS[1][1]*GS[2][2]) - + HKL,RC,RF,RF2 = FillHKLRC() if Data['Zone']: G2frame.G2plotNB.status.SetStatusText \ @@ -1776,11 +1784,11 @@ def Draw(caller=''): G2frame.G2plotNB.status.SetStatusText \ ('Plot type = %s for %s; N = %d, RF = %6.2f%%, RF%s = %6.2f%%'% \ (Data['Type'],Name,len(HKL),RF,super2,RF2),1) - + SetBackground() GL.glInitNames() GL.glPushName(0) - + GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() if sys.platform == "darwin": @@ -1790,8 +1798,8 @@ def Draw(caller=''): GL.glViewport(0,0,VS[0],VS[1]) GLU.gluPerspective(20.,aspect,cPos-Zclip,cPos+Zclip) GLU.gluLookAt(0,0,cPos,0,0,0,0,1,0) - SetLights() - + SetLights() + GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() matRot = G2mth.Q2Mat(Q) @@ -1922,10 +1930,10 @@ def PlotDeltSig(G2frame,kind,PatternName=None): Plot.legend(loc='upper left') np.seterr(invalid='warn') Page.canvas.draw() - + #### PlotISFG ################################################################ def PlotISFG(G2frame,data,newPlot=False,plotType='',peaks=None): - ''' Plotting package for PDF analysis; displays I(Q), S(Q), F(Q) and G(r) as single + ''' Plotting package for PDF analysis; displays I(Q), S(Q), F(Q) and G(r) as single or multiple plots with waterfall and contour plots as options ''' global Peaks @@ -1935,12 +1943,12 @@ def PlotISFG(G2frame,data,newPlot=False,plotType='',peaks=None): plotType = G2frame.G2plotNB.plotList[G2frame.G2plotNB.nb.GetSelection()] if plotType not in ['I(Q)','S(Q)','F(Q)','G(R)','g(r)','delt-G(R)']: return - - def OnPlotKeyUp(event): + + def OnPlotKeyUp(event): if event.key == 'shift': G2frame.ShiftDown = False return - + def OnPlotKeyPress(event): if event.key == 'shift': G2frame.ShiftDown = True @@ -1977,7 +1985,7 @@ def OnPlotKeyPress(event): G2frame.Cmin = 0.0 G2frame.Cmax = 1.0 else: - Page.Offset = [0,0] + Page.Offset = [0,0] elif event.key == 'm': G2frame.SinglePlot = not G2frame.SinglePlot elif event.key == 'c': @@ -1993,7 +2001,7 @@ def OnPlotKeyPress(event): elif event.key == 'f' and not G2frame.SinglePlot: choices = G2gd.GetGPXtreeDataNames(G2frame,'PDF ') dlg = G2G.G2MultiChoiceDialog(G2frame, - 'Select PDF(s) to plot\n(select all or none to reset)', + 'Select PDF(s) to plot\n(select all or none to reset)', 'Multidata plot selection',choices) if dlg.ShowModal() == wx.ID_OK: G2frame.PDFselections = [] @@ -2030,7 +2038,7 @@ def OnPlotKeyPress(event): elif event.key == 'g': mpl.rcParams['axes.grid'] = not mpl.rcParams['axes.grid'] PlotISFG(G2frame,data,newPlot=newPlot,plotType=plotType) - + def OnMotion(event): xpos = event.xdata if xpos: #avoid out of frame mouse position @@ -2040,10 +2048,10 @@ def OnMotion(event): if G2frame.Contour: G2frame.G2plotNB.status.SetStatusText('R =%.3fA pattern ID =%5d'%(xpos,int(ypos)),1) else: - G2frame.G2plotNB.status.SetStatusText('R =%.3fA %s =%.2f'%(xpos,plotType,ypos),1) + G2frame.G2plotNB.status.SetStatusText('R =%.3fA %s =%.2f'%(xpos,plotType,ypos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select '+plotType+' pattern first',1) - + def OnPick(event): def OnDragLine(event): @@ -2082,7 +2090,7 @@ def OnDragLine(event): Peaks['Peaks'] = G2mth.sortArray(Peaks['Peaks'],0,reverse=False) PlotISFG(G2frame,data,peaks=Peaks,newPlot=False) G2pdG.UpdatePDFPeaks(G2frame,Peaks,data) - + def OnRelease(event): if Peaks == None: return if G2frame.itemPicked == None: return @@ -2095,7 +2103,7 @@ def OnRelease(event): PlotISFG(G2frame,data,peaks=Peaks,newPlot=False) return lines = [] - for line in G2frame.Lines: + for line in G2frame.Lines: lines.append(line.get_xdata()[0]) try: lineNo = lines.index(G2frame.itemPicked.get_xdata()[0]) @@ -2125,7 +2133,7 @@ def OnRelease(event): Page.canvas.mpl_connect('pick_event', OnPick) Page.canvas.mpl_connect('button_release_event', OnRelease) Page.Offset = [0,0] - + G2frame.G2plotNB.status.DestroyChildren() #get rid of special stuff on status bar if Peaks == None: if G2frame.Contour: @@ -2192,12 +2200,12 @@ def OnRelease(event): ContourZ = [] ContourY = [] Nseq = 0 - + for N,Pattern in enumerate(PlotList): xye = np.array(Pattern[1]) X = xye[0] if not lenX: - lenX = len(X) + lenX = len(X) if G2frame.Contour and len(PlotList)>1: Y = xye[1] if lenX == len(X): @@ -2231,7 +2239,7 @@ def OnRelease(event): acolor = GetColorMap(G2frame.ContourColor) Img = Plot.imshow(ContourZ,cmap=acolor, vmin=Ymax*G2frame.Cmin,vmax=Ymax*G2frame.Cmax, - interpolation=G2frame.Interpolate, + interpolation=G2frame.Interpolate, extent=[ContourX[0],ContourX[-1],ContourY[0],ContourY[-1]],aspect='auto',origin='lower') Page.figure.colorbar(Img) else: @@ -2241,7 +2249,7 @@ def OnRelease(event): Xmax = np.amax(XYlist[0][0]) Ymax = np.amax(XYlist[0][1]) Ymin = np.amin(XYlist[2][1]) - else: + else: Xmin = np.amin(XYlist.T[0]) Xmax = np.amax(XYlist.T[0]) Ymin = np.amin(XYlist.T[1][1:]) @@ -2259,7 +2267,7 @@ def OnRelease(event): wx.BeginBusyCursor() if XYlist.shape[0]>1: if G2frame.Waterfall: - for xylist in XYlist: + for xylist in XYlist: ymin = np.amin(xylist.T[1]) ymax = np.amax(xylist.T[1]) normcl = mpcls.Normalize(ymin,ymax) @@ -2275,7 +2283,7 @@ def OnRelease(event): Plot.plot(XYlist[0][0],XYlist[0][1],color='b',marker='+',linewidth=0) Plot.plot(XYlist[1][0][ibeg:ifin],XYlist[1][1][ibeg:ifin],color='g') Plot.plot(XYlist[2][0][ibeg:ifin],XYlist[2][1][ibeg:ifin],color='r') - else: + else: lines = mplC.LineCollection(XYlist,cmap=acolor) lines.set_array(np.arange(XYlist.shape[0])) Plot.add_collection(lines) @@ -2335,7 +2343,7 @@ def OnRelease(event): picker=True,pickradius=2.)) Xb = [0.,peaks['Limits'][1]] Yb = [0.,Xb[1]*peaks['Background'][1][1]] - Plot.plot(Xb,Yb,color='k',dashes=(5,5)) + Plot.plot(Xb,Yb,color='k',dashes=(5,5)) # elif G2frame.Legend: # Plot.legend(loc='best') if not newPlot: @@ -2352,7 +2360,7 @@ def OnRelease(event): def PlotCalib(G2frame,Inst,XY,Sigs,newPlot=False): '''plot of CW or TOF peak calibration ''' - + global Plot def OnMotion(event): xpos = event.xdata @@ -2360,7 +2368,7 @@ def OnMotion(event): ypos = event.ydata SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('X =%9.3f %s =%9.3g'%(xpos,Title,ypos),1) + G2frame.G2plotNB.status.SetStatusText('X =%9.3f %s =%9.3g'%(xpos,Title,ypos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select '+Title+' pattern first',1) found = [] @@ -2369,7 +2377,7 @@ def OnMotion(event): found = XY[np.where(np.fabs(XY.T[0]-xpos) < 0.005*wid)] if len(found): pos = found[0][1] - if 'C' in Inst['Type'][0]: + if 'C' in Inst['Type'][0]: Page.SetToolTipString('position=%.4f'%(pos)) else: Page.SetToolTipString('position=%.2f'%(pos)) @@ -2385,7 +2393,7 @@ def OnMotion(event): else: newPlot = True Page.canvas.mpl_connect('motion_notify_event', OnMotion) - + Page.Choice = None G2frame.G2plotNB.status.DestroyChildren() #get rid of special stuff on status bar Plot.set_title(Title,fontsize=14) @@ -2439,7 +2447,7 @@ def OnMotion(event): def PlotXY(G2frame,XY,XY2=[],labelX='X',labelY='Y',newPlot=False, Title='',lines=False,names=[],names2=[],vertLines=[]): '''simple plot of xy data - + :param wx.Frame G2frame: The main GSAS-II tree "window" :param list XY: a list of X,Y array pairs; len(X) = len(Y) :param list XY2: a secondary list of X,Y pairs @@ -2450,9 +2458,9 @@ def PlotXY(G2frame,XY,XY2=[],labelX='X',labelY='Y',newPlot=False, :param bool lines: = True if lines desired for XY plot; XY2 always plotted as lines :param list names: legend names for each XY plot as list a of str values :param list names2: legend names for each XY2 plot as list a of str values - :param list vertLines: lists of vertical line x-positions; can be one for each XY + :param list vertLines: lists of vertical line x-positions; can be one for each XY :returns: nothing - + ''' global xylim def OnKeyPress(event): @@ -2483,10 +2491,10 @@ def OnMotion(event): ypos = event.ydata SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('X =%9.3f %s =%9.3f'%(xpos,Title,ypos),1) + G2frame.G2plotNB.status.SetStatusText('X =%9.3f %s =%9.3f'%(xpos,Title,ypos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select '+Title+' pattern first',1) - + def Draw(): global xylim Plot.clear() @@ -2498,7 +2506,7 @@ def Draw(): NC = len(colors) Page.keyPress = OnKeyPress Xmax = 0. - Ymax = 0. + Ymax = 0. for ixy,xy in enumerate(XY): X,Y = XY[ixy] Xmax = max(Xmax,max(X)) @@ -2549,19 +2557,19 @@ def Draw(): Page.canvas.mpl_connect('key_press_event', OnKeyPress) Page.canvas.mpl_connect('motion_notify_event', OnMotion) Page.Offset = [0,0] - + if lines: Page.Choice = (' key press','l: offset left','r: offset right','d: offset down', 'u: offset up','o: reset offset','g: toggle grid','s: save data as csv file') else: Page.Choice = ('g: toggle grid','s: save data as csv file') Draw() - - + + #### PlotXYZ ################################################################################ def PlotXYZ(G2frame,XY,Z,labelX='X',labelY='Y',newPlot=False,Title='',zrange=None,color=None,buttonHandler=None): '''simple contour plot of xyz data - + :param wx.Frame G2frame: The main GSAS-II tree "window" :param list XY: a list of X,Y arrays :param list Z: a list of Z values for each X,Y pair @@ -2572,16 +2580,16 @@ def PlotXYZ(G2frame,XY,Z,labelX='X',labelY='Y',newPlot=False,Title='',zrange=Non :param list zrange: [zmin,zmax]; default=None to use limits in Z :param str color: one of mpl.cm.dated.keys(); default=None to use G2frame.ContourColor :returns: nothing - + ''' def OnKeyPress(event): if event.key == 'u': - G2frame.Cmax = min(1.0,G2frame.Cmax*1.2) + G2frame.Cmax = min(1.0,G2frame.Cmax*1.2) elif event.key == 'd': G2frame.Cmax = max(0.0,G2frame.Cmax*0.8) elif event.key == 'o': G2frame.Cmax = 1.0 - + elif event.key == 'g': mpl.rcParams['axes.grid'] = not mpl.rcParams['axes.grid'] @@ -2596,7 +2604,7 @@ def OnKeyPress(event): else: G2frame.Interpolate = 'nearest' dlg.Destroy() - + elif event.key == 's': choice = [m for m in mpl.cm.datad.keys()]+['GSPaired','GSPaired_r',] # if not m.endswith("_r") choice.sort() @@ -2608,7 +2616,7 @@ def OnKeyPress(event): G2frame.ContourColor = GSASIIpath.GetConfigValue('Contour_color','RdYlGn') dlg.Destroy() wx.CallAfter(PlotXYZ,G2frame,XY,Z,labelX,labelY,False,Title) - + def OnMotion(event): xpos = event.xdata if xpos and Xminthresh[1][1],'r','b')) Plot2.bar(resNums,Probs2,color=colors,linewidth=0,picker=True) if thresh is not None: @@ -2834,7 +2842,7 @@ def Draw(): Plot2.axhline(item,dashes=(5,5),picker=False) Page.xylim = [Plot1.get_xlim(),Plot1.get_ylim(),Plot2.get_ylim()] Page.canvas.draw() - + new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab(Title,'mpl') Page.canvas.mpl_connect('pick_event', OnPick) Page.canvas.mpl_connect('motion_notify_event', OnMotion) @@ -2850,7 +2858,7 @@ def OnMotion(event): ypos = event.ydata SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('d-spacing =%9.5f Azimuth =%9.3f'%(ypos,xpos),1) + G2frame.G2plotNB.status.SetStatusText('d-spacing =%9.5f Azimuth =%9.3f'%(ypos,xpos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select Strain pattern first',1) @@ -2861,7 +2869,7 @@ def OnMotion(event): else: newPlot = True Page.canvas.mpl_connect('motion_notify_event', OnMotion) - + Page.Choice = None G2frame.G2plotNB.status.DestroyChildren() #get rid of special stuff on status bar Plot.set_title('Strain') @@ -2886,24 +2894,24 @@ def OnMotion(event): Page.ToolBarDraw() else: Page.canvas.draw() - + #### PlotBarGraph ################################################################################ def PlotBarGraph(G2frame,Xarray,Xname='',Yname='Number',Title='',PlotName=None,ifBinned=False,maxBins=None): ''' does a vertical bar graph ''' - + def OnPageChanged(event): PlotText = G2frame.G2plotNB.nb.GetPageText(G2frame.G2plotNB.nb.GetSelection()) if PlotText == PlotName: PlotBarGraph(G2frame,Xarray,Xname,Title,PlotText) - + def OnMotion(event): xpos = event.xdata if xpos: #avoid out of frame mouse position ypos = event.ydata SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('X =%9.3f Number =%9.3g'%(xpos,ypos),1) + G2frame.G2plotNB.status.SetStatusText('X =%9.3f Number =%9.3g'%(xpos,ypos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select %s first'%PlotName,1) @@ -2935,12 +2943,12 @@ def OnMotion(event): def PlotNamedFloatHBarGraph(G2frame,Xvals,Ynames,Xlabel='Value',Ylabel='',Title='',PlotName=None): ''' does a horizintal bar graph ''' - + def OnPageChanged(event): PlotText = G2frame.G2plotNB.nb.GetPageText(G2frame.G2plotNB.nb.GetSelection()) if PlotText == PlotName: PlotNamedFloatHBarGraph(G2frame,Xvals,Ynames,Xlabel,Ylabel,Title,PlotText) - + def OnMotion(event): if event.ydata is None: return @@ -2948,7 +2956,7 @@ def OnMotion(event): if ypos and ypos < len(Ynames): #avoid out of frame mouse position SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('X =%s %s = %9.3g'%(Ynames[ypos],Xlabel,Xvals[ypos]),1) + G2frame.G2plotNB.status.SetStatusText('X =%s %s = %9.3g'%(Ynames[ypos],Xlabel,Xvals[ypos]),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select %s first'%PlotName,1) @@ -2966,11 +2974,11 @@ def OnMotion(event): Plot.set_ylabel(Ylabel,fontsize=14) Plot.barh(Yvals,Xvals,height=0.8,align='center',color=colors,edgecolor='black',tick_label=Ynames) Page.canvas.draw() - + #### PlotSASDSizeDist ################################################################################ def PlotSASDSizeDist(G2frame): 'Needs a description' - + def OnPageChanged(event): PlotText = G2frame.G2plotNB.nb.GetPageText(G2frame.G2plotNB.nb.GetSelection()) if 'Powder' in PlotText: @@ -2979,14 +2987,14 @@ def OnPageChanged(event): PlotSASDSizeDist(G2frame) elif 'Pair' in PlotText: PlotSASDPairDist(G2frame) - + def OnMotion(event): xpos = event.xdata if xpos: #avoid out of frame mouse position ypos = event.ydata SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('diameter =%9.3f f(D) =%9.3g'%(xpos,ypos),1) + G2frame.G2plotNB.status.SetStatusText('diameter =%9.3f f(D) =%9.3g'%(xpos,ypos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select Size pattern first',1) @@ -3022,7 +3030,7 @@ def OnMotion(event): #### PlotSASDPairDist ################################################################################ def PlotSASDPairDist(G2frame): 'Needs a description' - + def OnPageChanged(event): PlotText = G2frame.G2plotNB.nb.GetPageText(G2frame.G2plotNB.nb.GetSelection()) if 'Powder' in PlotText: @@ -3031,14 +3039,14 @@ def OnPageChanged(event): PlotSASDSizeDist(G2frame) elif 'Pair' in PlotText: PlotSASDPairDist(G2frame) - + def OnMotion(event): xpos = event.xdata if xpos: #avoid out of frame mouse position ypos = event.ydata SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('pair dist =%9.3f f(D) =%9.3g'%(xpos,ypos),1) + G2frame.G2plotNB.status.SetStatusText('pair dist =%9.3f f(D) =%9.3g'%(xpos,ypos),1) except TypeError: G2frame.G2plotNB.status.SetStatusText('Select Pair pattern first',1) @@ -3080,7 +3088,7 @@ def OnMotion(event): findx = np.where(np.fabs(G2frame.HKL.T[-2]-xpos) < 0.002*wid) found = G2frame.HKL[findx] if len(found): - h,k,l = found[0][:3] + h,k,l = found[0][:3] Page.SetToolTipString('%d,%d,%d'%(int(h),int(k),int(l))) else: Page.SetToolTipString('') @@ -3107,12 +3115,12 @@ def OnMotion(event): Page.canvas.draw() Page.toolbar.push_current() -##### PlotPeakWidths +##### PlotPeakWidths def PlotPeakWidths(G2frame,PatternName=None): ''' Plotting of instrument broadening terms as function of Q Seen when "Instrument Parameters" chosen from powder pattern data tree. Parameter PatternName allows the PWDR to be referenced as a string rather than - a wx tree item, defined in G2frame.PatternId. + a wx tree item, defined in G2frame.PatternId. ''' # sig = lambda Th,U,V,W: 1.17741*math.sqrt(U*tand(Th)**2+V*tand(Th)+W)*math.pi/18000. # gam = lambda Th,X,Y: (X/cosd(Th)+Y*tand(Th))*math.pi/18000. @@ -3122,8 +3130,8 @@ def OnMotion(event): xpos = event.xdata if xpos: #avoid out of frame mouse position ypos = event.ydata - G2frame.G2plotNB.status.SetStatusText('Q =%.3f%s %sQ/Q =%.4f'%(xpos,Angstr+Pwrm1,GkDelta,ypos),1) - + G2frame.G2plotNB.status.SetStatusText('Q =%.3f%s %sQ/Q =%.4f'%(xpos,Angstr+Pwrm1,GkDelta,ypos),1) + def OnKeyPress(event): if event.key == 'g': mpl.rcParams['axes.grid'] = not mpl.rcParams['axes.grid'] @@ -3174,7 +3182,7 @@ def OnKeyPress(event): except TypeError: print ("Your peak list needs reformatting...",end='') item = G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Peak List') - G2frame.GPXtree.SelectItem(item) + G2frame.GPXtree.SelectItem(item) item = G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Instrument Parameters') G2frame.GPXtree.SelectItem(item) print ("done") @@ -3182,14 +3190,14 @@ def OnKeyPress(event): xylim = [] new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('Peak Widths','mpl') Page.Choice = (' key press','g: toggle grid','s: save as .csv file') - Page.keyPress = OnKeyPress + Page.keyPress = OnKeyPress if not new: if not G2frame.G2plotNB.allowZoomReset: # save previous limits xylim = copy.copy(lim) else: Page.canvas.mpl_connect('motion_notify_event', OnMotion) Page.canvas.mpl_connect('key_press_event', OnKeyPress) - G2frame.G2plotNB.SetHelpButton(G2frame.dataWindow.helpKey) + G2frame.G2plotNB.SetHelpButton(G2frame.dataWindow.helpKey) # save information needed to reload from tree and redraw G2frame.G2plotNB.RegisterRedrawRoutine(G2frame.G2plotNB.lastRaisedPlotTab, PlotPeakWidths,(G2frame,G2frame.GPXtree.GetItemText(G2frame.PatternId))) @@ -3243,7 +3251,7 @@ def OnKeyPress(event): Plot.plot(Q,Sf,color='b',dashes=(5,5),label='Gaussian fit') Plot.plot(Q,Gf,color='m',label='Lorentzian fit') Plot.plot(Q,Wf,color='g',dashes=(5,5),label='FWHM fit (GL+ab)') - + Tp = [] Ap = [] Bp = [] @@ -3258,8 +3266,8 @@ def OnKeyPress(event): Qp.append(2.*np.pi*difC/peak[0]) Sp.append(1.17741*np.sqrt(peak[8])/peak[0]) Gp.append(peak[10]/peak[0]) - - if Qp: + + if Qp: Plot.plot(Qp,Ap,'+',color='r',label='Alpha peak') Plot.plot(Qp,Bp,'+',color='orange',label='Beta peak') Plot.plot(Qp,Sp,'+',color='b',label='Gaussian peak') @@ -3284,7 +3292,7 @@ def OnKeyPress(event): Plot.plot(Q,Y,color='r',label='Gaussian') Plot.plot(Q,Z,color='g',label='Lorentzian') Plot.plot(Q,W,color='b',label='G+L') - + fit = G2mth.setPeakparms(Parms,Parms2,X,Z,useFit=True) for did in [isig,igam]: if np.any(fit[did] < 0.): @@ -3321,7 +3329,7 @@ def OnKeyPress(event): legend = Plot.legend(loc='best') SetupLegendPick(legend,new) Page.canvas.draw() - + else: #'A', 'C' & 'B' isig = 4 igam = 6 @@ -3343,7 +3351,7 @@ def OnKeyPress(event): Plot.plot(Q,Y,color='r',label='Gaussian') Plot.plot(Q,Z,color='g',label='Lorentzian') Plot.plot(Q,W,color='b',label='G+L') - + fit = G2mth.setPeakparms(Parms,Parms2,X,Z,useFit=True) for did in [isig,igam]: if np.any(fit[did] < 0.): @@ -3357,7 +3365,7 @@ def OnKeyPress(event): Plot.plot(Q,Yf,color='r',dashes=(5,5),label='Gaussian fit') Plot.plot(Q,Zf,color='g',dashes=(5,5),label='Lorentzian fit') Plot.plot(Q,Wf,color='b',dashes=(5,5),label='G+L fit') - + Xp = [] Yp = [] Zp = [] @@ -3382,7 +3390,7 @@ def OnKeyPress(event): Page.canvas.draw() if negWarn: Plot.set_title('WARNING: profile coefficients yield negative peak widths; peaks may be skipped in calcuations') - + if xylim and not G2frame.G2plotNB.allowZoomReset: # this restores previous plot limits (but I'm not sure why there are two .push_current calls) Page.toolbar.push_current() @@ -3392,7 +3400,7 @@ def OnKeyPress(event): Page.ToolBarDraw() else: Page.canvas.draw() - + #### PlotDeform ###################################################################################### def PlotDeform(G2frame,general,atName,atType,deform,UVmat,radial,neigh): ''' Plot deformation atoms & neighbors @@ -3451,16 +3459,16 @@ def PlotDeform(G2frame,general,atName,atType,deform,UVmat,radial,neigh): Plot.set_aspect('equal') except NotImplementedError: pass - - + + Page.canvas.draw() - + #### PlotSizeStrainPO ################################################################################ def PlotSizeStrainPO(G2frame,data,hist=''): '''Plot 3D mustrain/size/preferred orientation figure. In this instance data is for a phase ''' - + def OnPick(event): if 'Inv. pole figure' not in plotType: return @@ -3473,7 +3481,7 @@ def rp2xyz(r,p): z = npcosd(r) xy = np.sqrt(1.-z**2) return xy*npcosd(p),xy*npsind(p),z - + def OnMotion(event): if 'Inv. pole figure' not in plotType: return @@ -3496,7 +3504,7 @@ def OnMotion(event): y,x,z = list(xyz/np.max(np.abs(xyz))) G2frame.G2plotNB.status.SetStatusText( 'psi =%9.3f, beta =%9.3f, MRD =%9.3f hkl=%5.2f,%5.2f,%5.2f'%(r,p,ipf,x,y,z),1) - + import scipy.interpolate as si generalData = data['General'] SGData = generalData['SGData'] @@ -3510,7 +3518,7 @@ def OnMotion(event): for ptype in plotDict: G2frame.G2plotNB.Delete(ptype) if plotType in ['None'] or not useList: - return + return if hist == '': hist = list(useList.keys())[0] @@ -3526,7 +3534,7 @@ def OnMotion(event): Page.canvas.mpl_connect('motion_notify_event', OnMotion) Page.Choice = None G2frame.G2plotNB.status.SetStatusText('',1) - + PHI = np.linspace(0.,360.,40,True) PSI = np.linspace(0.,180.,40,True) X = np.outer(npcosd(PHI),npsind(PSI)) @@ -3544,16 +3552,16 @@ def OnMotion(event): if coeff[0] == 'isotropic': X *= diam*coeff[1][0] Y *= diam*coeff[1][0] - Z *= diam*coeff[1][0] + Z *= diam*coeff[1][0] elif coeff[0] == 'uniaxial': - + def uniaxCalc(xyz,iso,aniso,axes): Z = np.array(axes) cp = abs(np.dot(xyz,Z)) sp = np.sqrt(1.-cp**2) R = iso*aniso/np.sqrt((iso*cp)**2+(aniso*sp)**2) return R*xyz*diam - + iso,aniso = coeff[1][:2] axes = np.inner(Amat,np.array(coeff[3])) axes /= nl.norm(axes) @@ -3564,13 +3572,13 @@ def uniaxCalc(xyz,iso,aniso,axes): X = X[:,:,0] Y = Y[:,:,0] Z = Z[:,:,0] - + elif coeff[0] == 'ellipsoidal': - + def ellipseCalc(xyz,E,R): XYZ = xyz*E.T return np.inner(XYZ.T,R)*diam - + S6 = coeff[4] Sij = G2lat.U6toUij(S6) E,R = nl.eigh(Sij) @@ -3580,9 +3588,9 @@ def ellipseCalc(xyz,E,R): X = X[:,:,0] Y = Y[:,:,0] Z = Z[:,:,0] - + elif coeff[0] == 'generalized': - + def genMustrain(xyz,SGData,A,Shkl): uvw = np.inner(Amat.T,xyz) Strm = np.array(G2spc.MustrainCoeff(uvw,SGData)) @@ -3590,7 +3598,7 @@ def genMustrain(xyz,SGData,A,Shkl): Sum = np.where(Sum > 0.01,Sum,0.01) Sum = np.sqrt(Sum) return Sum*xyz - + Shkl = np.array(coeff[4]) if np.any(Shkl): XYZ = np.dstack((X,Y,Z)) @@ -3599,7 +3607,7 @@ def genMustrain(xyz,SGData,A,Shkl): X = X[:,:,0] Y = Y[:,:,0] Z = Z[:,:,0] - + if np.any(X) and np.any(Y) and np.any(Z): np.seterr(all='ignore') Plot.plot_surface(X,Y,Z,rstride=1,cstride=1,color='g',linewidth=1) @@ -3620,7 +3628,7 @@ def genMustrain(xyz,SGData,A,Shkl): Plot.set_xlabel(r'X, $\mu$m') Plot.set_ylabel(r'Y, $\mu$m') Plot.set_zlabel(r'Z, $\mu$m') - else: + else: Plot.set_title(r'$\mu$strain for '+phase+'; '+coeff[0]+' model') Plot.set_xlabel(r'X, $\mu$strain') Plot.set_ylabel(r'Y, $\mu$strain') @@ -3629,14 +3637,14 @@ def genMustrain(xyz,SGData,A,Shkl): h,k,l = generalData['POhkl'] if coeff[0] == 'MD': print ('March-Dollase preferred orientation plot') - + else: PH = np.array(generalData['POhkl']) phi,beta = G2lat.CrsAng(PH,cell[:6],SGData) SHCoef = {} for item in coeff[5]: L,N = eval(item.strip('C')) - SHCoef['C%d,0,%d'%(L,N)] = coeff[5][item] + SHCoef['C%d,0,%d'%(L,N)] = coeff[5][item] ODFln = G2lat.Flnh(SHCoef,phi,beta,SGData) X = np.linspace(0,90.0,26) Y = G2lat.polfcal(ODFln,'0',X,0.0) @@ -3691,9 +3699,9 @@ def genMustrain(xyz,SGData,A,Shkl): Rmd = np.array(Rmd) Rmd = np.where(Rmd<0.,0.,Rmd) if 'Eq. area' in plotType: - x,y = np.sin(Beta/2.)*np.cos(Phi)/sq2,np.sin(Beta/2.)*np.sin(Phi)/sq2 + x,y = np.sin(Beta/2.)*np.cos(Phi)/sq2,np.sin(Beta/2.)*np.sin(Phi)/sq2 else: - x,y = np.tan(Beta/2.)*np.cos(Phi),np.tan(Beta/2.)*np.sin(Phi) + x,y = np.tan(Beta/2.)*np.cos(Phi),np.tan(Beta/2.)*np.sin(Phi) npts = 101 X,Y = np.meshgrid(np.linspace(1.,-1.,npts),np.linspace(-1.,1.,npts)) R,P = np.sqrt(X**2+Y**2).flatten(),npatan2d(Y,X).flatten() @@ -3717,7 +3725,7 @@ def genMustrain(xyz,SGData,A,Shkl): G2frame.G2plotNB.Delete(plotType) G2G.G2MessageBox(G2frame,'IVP interpolate error: scipy needs to be 0.11.0 or newer', 'IVP error') - return + return Z = np.reshape(Z,(npts,npts)) try: CS = Plot.contour(Y,X,Z) @@ -3730,9 +3738,9 @@ def genMustrain(xyz,SGData,A,Shkl): Page.figure.colorbar(Img) Plot.axis('off') Plot.set_title('0 0 1 Inverse pole figure for %s\n%s'%(phase,hist)) - + Page.canvas.draw() - + #### PlotTexture ################################################################################ def PlotTexture(G2frame,data,Start=False): '''Pole figure, inverse pole figure plotting. @@ -3754,12 +3762,12 @@ def PlotTexture(G2frame,data,Start=False): cell = generalData['Cell'][1:7] Amat,Bmat = G2lat.cell2AB(cell) sq2 = 1.0/math.sqrt(2.0) - + def rp2xyz(r,p): z = npcosd(r) xy = np.sqrt(1.-z**2) return xy*npsind(p),xy*npcosd(p),z - + def OnMotion(event): SHData = data['General']['SH Texture'] if event.xdata and event.ydata: #avoid out of frame errors @@ -3768,31 +3776,31 @@ def OnMotion(event): if 'Inverse' in SHData['PlotType']: r = xpos**2+ypos**2 if r <= 1.0: - if 'equal' in G2frame.Projection: + if 'equal' in G2frame.Projection: r,p = 2.*npasind(np.sqrt(r)*sq2),npatan2d(xpos,ypos) else: r,p = 2.*npatand(np.sqrt(r)),npatan2d(xpos,ypos) ipf = G2lat.invpolfcal(IODFln,SGData,np.array([r,]),np.array([p,])) xyz = np.inner(Amat,np.array([rp2xyz(r,p)])) y,x,z = list(xyz/np.max(np.abs(xyz))) - + G2frame.G2plotNB.status.SetStatusText( 'psi =%9.3f, beta =%9.3f, MRD =%9.3f hkl=%5.2f,%5.2f,%5.2f'%(r,p,ipf,x,y,z),1) - + elif 'Axial' in SHData['PlotType']: pass - + else: #ordinary pole figure z = xpos**2+ypos**2 if z <= 1.0: z = np.sqrt(z) - if 'equal' in G2frame.Projection: + if 'equal' in G2frame.Projection: r,p = 2.*npasind(z*sq2),npatan2d(ypos,xpos) else: r,p = 2.*npatand(z),npatan2d(ypos,xpos) pf = G2lat.polfcal(ODFln,SamSym[textureData['Model']],np.array([r,]),np.array([p,])) G2frame.G2plotNB.status.SetStatusText('phi =%9.3f, gam =%9.3f, MRD =%9.3f'%(r,p,pf),1) - + def OnPick(event): pick = event.artist Dettext = pick.get_gid() @@ -3809,7 +3817,7 @@ def OnPick(event): Page.canvas.mpl_connect('motion_notify_event', OnMotion) Page.canvas.mpl_connect('pick_event', OnPick) Page.Choice = None - G2frame.G2plotNB.status.SetStatusText('') + G2frame.G2plotNB.status.SetStatusText('') G2frame.G2plotNB.status.SetStatusWidths([G2frame.G2plotNB.status.firstLen,-1]) PH = np.array(SHData['PFhkl']) phi,beta = G2lat.CrsAng(PH,cell,SGData) @@ -3831,8 +3839,8 @@ def OnPick(event): Plot.set_title('%d %d %d Axial distribution for %s'%(h,k,l,pName)) Plot.set_xlabel(r'$\psi$',fontsize=16) Plot.set_ylabel('MRD',fontsize=14) - - else: + + else: npts = 101 if 'Inverse' in SHData['PlotType']: X,Y = np.meshgrid(np.linspace(1.,-1.,npts),np.linspace(-1.,1.,npts)) @@ -3856,19 +3864,19 @@ def OnPick(event): Plot.axis('off') Plot.set_title('%d %d %d Inverse pole figure for %s'%(int(x),int(y),int(z),pName)) Plot.set_xlabel(G2frame.Projection.capitalize()+' projection') - + elif '3D' in SHData['PlotType']: PSI,GAM = np.mgrid[0:31,0:31] PSI = PSI.flatten()*6. GAM = GAM.flatten()*12. - P = G2lat.polfcal(ODFln,SamSym[textureData['Model']],PSI,GAM).reshape((31,31)) + P = G2lat.polfcal(ODFln,SamSym[textureData['Model']],PSI,GAM).reshape((31,31)) GAM = np.linspace(0.,360.,31,True) PSI = np.linspace(0.,180.,31,True) X = np.outer(npsind(GAM),npsind(PSI))*P.T Y = np.outer(npcosd(GAM),npsind(PSI))*P.T Z = np.outer(np.ones(np.size(GAM)),npcosd(PSI))*P.T h,k,l = SHData['PFhkl'] - + if np.any(X) and np.any(Y) and np.any(Z): np.seterr(all='ignore') Plot.plot_surface(X,Y,Z,rstride=1,cstride=1,color='g',linewidth=1) @@ -3931,7 +3939,7 @@ def ModulationPlot(G2frame,data,atom,ax,off=0): Off = off Atom = atom Ax = ax - + def OnMotion(event): xpos = event.xdata if xpos: #avoid out of frame mouse position @@ -3940,11 +3948,11 @@ def OnMotion(event): iy = int(round((Slab.shape[0]-1)*(ypos+0.5-Off*0.005))) SetCursor(Page) try: - G2frame.G2plotNB.status.SetStatusText('t =%9.3f %s =%9.3f %s=%9.3f'%(xpos,GkDelta+Ax,ypos,Gkrho,Slab[iy,ix]/8.),1) -# GSASIIpath.IPyBreak() + G2frame.G2plotNB.status.SetStatusText('t =%9.3f %s =%9.3f %s=%9.3f'%(xpos,GkDelta+Ax,ypos,Gkrho,Slab[iy,ix]/8.),1) +# GSASIIpath.IPyBreak() except (TypeError,IndexError): G2frame.G2plotNB.status.SetStatusText('Select '+Title+' pattern first',1) - + def OnPlotKeyPress(event): global Off,Atom,Ax if event.key == '0': @@ -4028,7 +4036,7 @@ def OnPlotKeyPress(event): Plot.set_title(Title) Plot.set_xlabel('t') Plot.set_ylabel(r'$\mathsf{\Delta}$%s'%(Ax)) - Slab = np.hstack((slab,slab,slab)) + Slab = np.hstack((slab,slab,slab)) acolor = GetColorMap('RdYlGn') if 'delt' in MapType: Plot.contour(Slab[:,:21],20,extent=(0.,2.,-.5+Doff,.5+Doff),cmap=acolor) @@ -4036,11 +4044,11 @@ def OnPlotKeyPress(event): Plot.contour(Slab[:,:21],20,extent=(0.,2.,-.5+Doff,.5+Doff)) Plot.set_ylim([-0.25,0.25]) Page.canvas.draw() - + #### PlotCovariance ################################################################################ def PlotCovariance(G2frame,Data,Cube=False): - '''Plots the covariance matrix. Also shows values for parameters - and their standard uncertainties (esd's) or the correlation between + '''Plots the covariance matrix. Also shows values for parameters + and their standard uncertainties (esd's) or the correlation between variables. ''' def OnPlotKeyPress(event): @@ -4101,13 +4109,13 @@ def OnMotion(event): value = Page.values[xpos] name = Page.varyList[xpos] if Page.varyList[xpos] in Page.newAtomDict: - name,value = Page.newAtomDict[name] + name,value = Page.newAtomDict[name] msg = '%s value = %.4g, esd = %.4g'%(name,value,Page.sig[xpos]) else: msg = '%s - %s: %5.3f'%(Page.varyList[xpos],Page.varyList[ypos],Page.covArray[xpos][ypos]) Page.SetToolTipString(msg) G2frame.G2plotNB.status.SetStatusText(msg,1) - + #==== PlotCovariance(G2frame,Data) starts here ========================= if not Data: print ('No covariance matrix available') @@ -4159,11 +4167,11 @@ def OnMotion(event): Plot.set_xlabel('Variable number') Plot.set_ylabel('Variable name') Page.canvas.draw() - + #### PlotTorsion ################################################################################ def PlotTorsion(G2frame,phaseName,Torsion,TorName,Names=[],Angles=[],Coeff=[]): 'needs a doc string' - + global names names = Names sum = np.sum(Torsion) @@ -4172,7 +4180,7 @@ def PlotTorsion(G2frame,phaseName,Torsion,TorName,Names=[],Angles=[],Coeff=[]): tMax = np.max(torsion) torsion = 3.*(torsion-tMin)/(tMax-tMin) X = np.linspace(0.,360.,num=45) - + def OnPick(event): ind = event.ind[0] msg = 'atoms:'+names[ind] @@ -4188,7 +4196,7 @@ def OnPick(event): if names[ind] in torGrid.GetCellValue(row,0): torGrid.SelectRow(row) torGrid.ForceRefresh() - + def OnMotion(event): if event.xdata and event.ydata: #avoid out of frame errors xpos = event.xdata @@ -4218,7 +4226,7 @@ def OnMotion(event): Plot.set_xlabel('angle',fontsize=16) Plot.set_ylabel('Energy',fontsize=16) Page.canvas.draw() - + #### PlotRama ################################################################################ def PlotRama(G2frame,phaseName,Rama,RamaName,Names=[],PhiPsi=[],Coeff=[]): 'needs a doc string' @@ -4243,7 +4251,7 @@ def OnPlotKeyPress(event): G2frame.RamaColor = 'RdYlGn' dlg.Destroy() PlotRama(G2frame,phaseName,Rama,RamaName,Names,PhiPsi,Coeff) - + def OnPick(event): ind = event.ind[0] msg = 'atoms:'+names[ind] @@ -4266,7 +4274,7 @@ def OnMotion(event): ypos = event.ydata msg = 'phi/psi: %5.3f %5.3f'%(xpos,ypos) Page.SetToolTipString(msg) - + new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('Ramachandran','mpl') if not new: if not Page.IsShown(): @@ -4281,7 +4289,7 @@ def OnMotion(event): G2frame.G2plotNB.status.SetStatusText('Use mouse LB to identify phi/psi atoms',1) acolor = GetColorMap(G2frame.RamaColor) if RamaName == 'All' or '-1' in RamaName: - if len(Coeff): + if len(Coeff): X,Y = np.meshgrid(np.linspace(-180.,180.,45),np.linspace(-180.,180.,45)) Z = np.array([-G2mth.calcRamaEnergy(x,y,Coeff)[1] for x,y in zip(X.flatten(),Y.flatten())]) Plot.contour(X,Y,np.reshape(Z,(45,45))) @@ -4294,7 +4302,7 @@ def OnMotion(event): Plot.set_xlim((-180.,180.)) Plot.set_ylim((-180.,180.)) else: - if len(Coeff): + if len(Coeff): X,Y = np.meshgrid(np.linspace(0.,360.,45),np.linspace(0.,360.,45)) Z = np.array([-G2mth.calcRamaEnergy(x,y,Coeff)[1] for x,y in zip(X.flatten(),Y.flatten())]) Plot.contour(X,Y,np.reshape(Z,(45,45))) @@ -4404,7 +4412,7 @@ def SeqDraw(): Page.fitvals = Page.fitvals[::-1] Plot.plot(X,Page.fitvals,label='Fit',color=colors[(ic+2)%NC]) - #### Begin self.testSeqRefineMode() ===================== + #### Begin self.testSeqRefineMode() ===================== Plot.legend(loc='best') if Title: Plot.set_title(Title) @@ -4442,9 +4450,9 @@ def SeqDraw(): Page.seqYaxisList = ColumnList Page.seqTableGet = TableGet Page.fitvals = fitvals - + SeqDraw() - + #### PlotExposedImage & PlotImage ################################################################################ def PlotExposedImage(G2frame,newPlot=False,event=None): '''General access module for 2D image plotting @@ -4458,9 +4466,9 @@ def PlotExposedImage(G2frame,newPlot=False,event=None): def OnStartMask(G2frame): '''Initiate the start of a Frame or Polygon map, etc. - Called from a menu command (GSASIIimgGUI) or from OnImPlotKeyPress. + Called from a menu command (GSASIIimgGUI) or from OnImPlotKeyPress. Variable G2frame.MaskKey contains a single letter ('f' or 'p', etc.) that - determines what type of mask is created. + determines what type of mask is created. :param wx.Frame G2frame: The main GSAS-II tree "window" ''' @@ -4501,13 +4509,13 @@ def OnStartMask(G2frame): Page.canvas.draw() G2imG.UpdateMasks(G2frame,Masks) - + def OnStartNewDzero(G2frame): '''Initiate the start of adding a new d-zero to a strain data set :param wx.Frame G2frame: The main GSAS-II tree "window" :param str eventkey: a single letter ('a') that - triggers the addition of a d-zero. + triggers the addition of a d-zero. ''' G2frame.GetStatusBar().SetStatusText('Add strain ring active - LB pick d-zero value',0) G2frame.PickId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Stress/Strain') @@ -4592,8 +4600,8 @@ def PlotImage(G2frame,newPlot=False,event=None,newImage=True): G2frame.ShiftDown = False G2frame.cid = None #Dsp = lambda tth,wave: wave/(2.*npsind(tth/2.)) - global Data,Masks,StrSta,Plot1,Page # RVD: these are needed for multiple image controls/masks -# colors=['b','g','r','c','m','k'] + global Data,Masks,StrSta,Plot1,Page # RVD: these are needed for multiple image controls/masks +# colors=['b','g','r','c','m','k'] colors = ['xkcd:blue','xkcd:red','xkcd:green','xkcd:cyan','xkcd:magenta','xkcd:black', 'xkcd:pink','xkcd:brown','xkcd:teal','xkcd:orange','xkcd:grey','xkcd:violet',] NC = len(colors) @@ -4700,7 +4708,7 @@ def OnImPlotKeyPress(event): G2frame.spotSize = dlg.GetValue() print('Spot size set to {} mm'.format(G2frame.spotSize)) ShowSpotMaskInfo(G2frame,Page) - dlg.Destroy() + dlg.Destroy() elif event.key == 't': try: # called from menu? Xpos,Ypos = event.xdata,event.ydata @@ -4719,7 +4727,7 @@ def OnImPlotKeyPress(event): artist.itemType = 'Spot' G2imG.UpdateMasks(G2frame,Masks) Page.canvas.draw() - return + return elif event.key in ['l','p','f','a','r','x','y']: G2frame.MaskKey = event.key OnStartMask(G2frame) @@ -4728,13 +4736,13 @@ def OnImPlotKeyPress(event): OnStartMask(G2frame) elif event.key == 'shift': G2frame.ShiftDown = True - + elif treeItem == 'Stress/Strain': if event.key in ['a',]: G2frame.StrainKey = event.key OnStartNewDzero(G2frame) wx.CallAfter(PlotImage,G2frame,newImage=False) - + elif treeItem == 'Image Controls': if event.key in ['c',]: Xpos = event.xdata @@ -4785,16 +4793,16 @@ def OnImPlotKeyPress(event): np.seterr(invalid=olderr['invalid']) Plot1.plot(xy[0],xy[1]) Plot1.set_xlim(xlim) - Plot1.set_xscale("linear") + Plot1.set_xscale("linear") Plot1.set_title('Line scan at azm= %6.1f'%(azm+AzmthOff)) Page.canvas.draw() else: return wx.CallAfter(PlotImage,G2frame,newPlot=True) - + def OnImPick(event): 'A object has been picked' - + def OnDragIntBound(event): 'Respond to the dragging of one of the integration boundaries' if event.xdata is None or event.ydata is None: @@ -4848,7 +4856,7 @@ def OnDragIntBound(event): xyO.append(xy) if len(xyO): xyO = np.array(xyO) - arcxO,arcyO = xyO.T + arcxO,arcyO = xyO.T Page.canvas.restore_region(savedplot) if 'Itth' in itemPicked: @@ -4877,13 +4885,13 @@ def OnDragIntBound(event): np.seterr(invalid=olderr['invalid']) Plot1.plot(xy[0],xy[1]) Plot1.set_xlim(xlim) - Plot1.set_xscale("linear") + Plot1.set_xscale("linear") Plot1.set_title('Line scan at azm= %6.1f'%(azm+AzmthOff)) Page.canvas.draw() - + Page.figure.gca().draw_artist(pick) Page.canvas.blit(Page.figure.gca().bbox) - + def OnDragMask(event): 'Respond to the dragging of a mask' if event.xdata is None or event.ydata is None: @@ -4973,7 +4981,7 @@ def OnDragMask(event): angI = tth - thick/2 off = azmN - azm[1] newRange = azm[1] - azm[0] - 2*off - if newRange < 2 or newRange > 358: + if newRange < 2 or newRange > 358: return # don't let the azimuthal range get too small or large azm[0] += off azm[1] -= off @@ -5163,22 +5171,22 @@ def OnImRelease(event): artist.itemType = 'Spot' G2imG.UpdateMasks(G2frame,Masks) Page.canvas.draw() - return + return elif G2frame.MaskKey == 'r': if event.button == 1: tth = G2img.GetTth(Xpos,Ypos,Data) - t = GSASIIpath.GetConfigValue('Ring_mask_thickness',0.1) + t = GSASIIpath.GetConfigValue('Ring_mask_thickness',0.1) Masks['Rings'].append([tth,t]) G2imG.UpdateMasks(G2frame,Masks) - G2frame.MaskKey = '' + G2frame.MaskKey = '' wx.CallAfter(PlotImage,G2frame,newImage=True) return elif G2frame.MaskKey == 'a': if event.button == 1: tth,azm = G2img.GetTthAzm(Xpos,Ypos,Data) - azm = int(azm) - t = GSASIIpath.GetConfigValue('Ring_mask_thickness',0.1) - a = GSASIIpath.GetConfigValue('Arc_mask_azimuth',10.0) + azm = int(azm) + t = GSASIIpath.GetConfigValue('Ring_mask_thickness',0.1) + a = GSASIIpath.GetConfigValue('Arc_mask_azimuth',10.0) Masks['Arcs'].append([tth,[azm-a/2.,azm+a/2.],t]) G2imG.UpdateMasks(G2frame,Masks) G2frame.MaskKey = '' @@ -5249,7 +5257,7 @@ def OnImRelease(event): StrSta['d-zero'] = G2mth.sortArray(StrSta['d-zero'],'Dset',reverse=True) G2frame.StrainKey = '' G2imG.UpdateStressStrain(G2frame,StrSta) - wx.CallAfter(PlotImage,G2frame,newPlot=False) + wx.CallAfter(PlotImage,G2frame,newPlot=False) else: # start here after dragging of integration range lines or a mask Xpos,Ypos = [event.xdata,event.ydata] if not Xpos or not Ypos or Page.toolbar.AnyActive(): #got point out of frame or zoom/pan selected @@ -5259,7 +5267,7 @@ def OnImRelease(event): try: pickType = G2frame.itemPicked.itemType except: - pickType = '?' + pickType = '?' if G2frame.ifGetRing: #delete a calibration ring pick xypos = [Xpos,Ypos] rings = Data['ring'] @@ -5279,7 +5287,7 @@ def OnImRelease(event): Data['LRazimuth'][0] %= 360 Data['LRazimuth'][1] %= 360 if Data['LRazimuth'][0] > Data['LRazimuth'][1]: - Data['LRazimuth'][1] += 360 + Data['LRazimuth'][1] += 360 if Data['fullIntegrate']: Data['LRazimuth'][1] = Data['LRazimuth'][0]+360 @@ -5328,16 +5336,16 @@ def OnImRelease(event): G2imG.UpdateMasks(G2frame,Masks) else: # nothing was done, nothing was changed, don't replot G2frame.itemPicked = None - return + return wx.CallAfter(PlotImage,G2frame,newImage=True) G2frame.itemPicked = None - + #### PlotImage execution starts here if not len(G2frame.ImageZ): return xylim = [] new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=newImage) - + if Data.get('linescan',[False,0.])[0]: Plot.set_visible(False) GS_kw = {'width_ratios':[1,2],} @@ -5437,7 +5445,7 @@ def OnImRelease(event): interpolation='nearest',vmin=0,vmax=2,extent=[0,Xmax,Ymax,0]) Page.ImgObj = Plot.imshow(A,aspect='equal',cmap=acolor, interpolation='nearest',vmin=Imin,vmax=Imax,extent=[0,Xmax,Ymax,0]) - + Plot.plot(xcent,ycent,'x') if Data['showLines']: # draw integration range arc/circles/lines LRAzim = Data['LRazimuth'] #NB: integers @@ -5470,7 +5478,7 @@ def OnImRelease(event): xyO.append(xy) if len(xyO): xyO = np.array(xyO) - arcxO,arcyO = xyO.T + arcxO,arcyO = xyO.T Plot.plot(arcxO,arcyO,picker=True,pickradius=3,label='Otth') if ellO and ellI and len(arcxO): Plot.plot([arcxI[0],arcxO[0]],[arcyI[0],arcyO[0]], @@ -5495,7 +5503,7 @@ def OnImRelease(event): Plot.plot([xyI[0],xyO[0]],[xyI[1],xyO[1]], # picker=True,pickradius=3,label='linescan') picker=False,label='linescan') - + if G2frame.PickId and G2frame.GPXtree.GetItemText(G2frame.PickId) in ['Image Controls',]: for xring,yring in Data['ring']: Plot.plot(xring,yring,'r+',picker=True,pickradius=3) @@ -5545,14 +5553,14 @@ def OnImRelease(event): if ring: tth,thick = ring (x1,y1),(x2,y2) = ComputeArc(tth-thick/2.,tth+thick/2.,Data['wavelength']) - artistO, = Plot.plot(x1,y1,'r',picker=True,pickradius=3) + artistO, = Plot.plot(x1,y1,'r',picker=True,pickradius=3) artistO.itemNumber = iring artistO.itemType = 'RingOuter' artistI, = Plot.plot(x2,y2,'r',picker=True,pickradius=3) artistI.itemNumber = iring artistI.itemType = 'RingInner' G2frame.ringList.append([artistI,artistO]) - + G2frame.arcList = [] for iarc,arc in enumerate(Masks['Arcs']): # drawing arc masks if arc: @@ -5563,9 +5571,9 @@ def OnImRelease(event): arcList.append(Plot.plot(x2,y2,'r',picker=True,pickradius=3)[0]) # 'inner' arcList[-1].itemNumber = iarc arcList[-1].itemType = 'ArcInner' - arcList.append(Plot.plot(x1,y1,'r',picker=True,pickradius=3)[0]) # 'outer' + arcList.append(Plot.plot(x1,y1,'r',picker=True,pickradius=3)[0]) # 'outer' arcList[-1].itemNumber = iarc - arcList[-1].itemType = 'ArcOuter' + arcList[-1].itemType = 'ArcOuter' arcList.append(Plot.plot([x1[0],x2[0]],[y1[0],y2[0]],'r', picker=True,pickradius=3)[0]) # 'lower' arcList[-1].itemNumber = iarc @@ -5575,10 +5583,10 @@ def OnImRelease(event): arcList[-1].itemNumber = iarc arcList[-1].itemType = 'ArcUpper' G2frame.arcList.append(arcList) - + G2frame.polyList = [] for ipoly,polygon in enumerate(Masks['Polygons']): - if not polygon: continue # ignore if empty + if not polygon: continue # ignore if empty if polygon[0] != polygon[-1]: print('Closing polygon {}'.format(ipoly)) polygon.append(polygon[0][:]) @@ -5589,7 +5597,7 @@ def OnImRelease(event): artist.itemNumber = ipoly artist.itemType = 'Polygon' artist.pointNumber = i - + G2frame.frameArtist = [] if Masks['Frames']: polygon = Masks['Frames'] @@ -5646,12 +5654,12 @@ def OnImRelease(event): Page.canvas.draw() finally: wx.EndBusyCursor() - + #### PlotIntegration ################################################################################ def PlotIntegration(G2frame,newPlot=False,event=None): '''Plot of 2D image after image integration with 2-theta and azimuth as coordinates ''' - + def OnMotion(event): Page.SetToolTipString('') SetCursor(Page) @@ -5660,7 +5668,7 @@ def OnMotion(event): if azm and tth: G2frame.G2plotNB.status.SetStatusText(\ 'Detector 2-th =%9.3fdeg, azm = %7.2fdeg'%(tth,azm),1) - + new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Integration','mpl') if not new: if not newPlot: @@ -5669,7 +5677,7 @@ def OnMotion(event): Page.canvas.mpl_connect('motion_notify_event', OnMotion) Page.views = False Page.Choice = None - + Data = G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls')) image = G2frame.Integrate[0] @@ -5683,7 +5691,7 @@ def OnMotion(event): Img = Plot.imshow(image,cmap=acolor,vmin=Imin,vmax=Imax,interpolation='nearest', \ extent=[ysc[0],ysc[-1],xsc[-1],xsc[0]],aspect='auto') Page.figure.colorbar(Img) -# if Data['ellipses']: +# if Data['ellipses']: # for ellipse in Data['ellipses']: # x,y = np.array(G2img.makeIdealRing(ellipse[:3])) #skip color # tth,azm = G2img.GetTthAzm(x,y,Data) @@ -5698,10 +5706,10 @@ def OnMotion(event): Page.ToolBarDraw() else: Page.canvas.draw() - + #### PlotRawImage ################################################################################ def PlotRawImage(G2frame,image,label,newPlot=False): - '''Plot an image without axes etc. + '''Plot an image without axes etc. ''' new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab(label,'mpl') Plot.remove() # delete original axes @@ -5709,12 +5717,12 @@ def PlotRawImage(G2frame,image,label,newPlot=False): Plot.axis('off') Plot.imshow(image) Page.canvas.draw() - + #### PlotTRImage ################################################################################ def PlotTRImage(G2frame,tax,tay,taz,newPlot=False): '''a test plot routine - not normally used - ''' - + ''' + def OnMotion(event): Page.SetToolTipString('') SetCursor(Page) @@ -5723,7 +5731,7 @@ def OnMotion(event): if azm and tth: G2frame.G2plotNB.status.SetStatusText(\ 'Detector 2-th =%9.3fdeg, azm = %7.2fdeg'%(tth,azm),1) - + new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Transformed Powder Image','mpl') if not new: if not newPlot: @@ -5757,8 +5765,8 @@ def OnMotion(event): rings = np.concatenate((Data['rings']),axis=0) for xring,yring,dsp in rings: x,y = G2img.GetTthAzm(xring,yring,Data) - Plot.plot(y,x,'r+') - if Data['ellipses']: + Plot.plot(y,x,'r+') + if Data['ellipses']: for ellipse in Data['ellipses']: ring = np.array(G2img.makeIdealRing(ellipse[:3])) #skip color x,y = np.hsplit(ring,2) @@ -5773,11 +5781,11 @@ def OnMotion(event): Page.ToolBarDraw() else: Page.canvas.draw() - + #### PlotStructure ################################################################################ def PlotStructure(G2frame,data,firstCall=False,pageCallback=None): '''Crystal structure plotting package. Can show structures as balls, sticks, lines, - thermal motion ellipsoids and polyhedra. Magnetic moments shown as black/red + thermal motion ellipsoids and polyhedra. Magnetic moments shown as black/red arrows according to spin state :param wx.Frame G2frame: main GSAS-II window @@ -5785,7 +5793,7 @@ def PlotStructure(G2frame,data,firstCall=False,pageCallback=None): (see :ref:`Phase Tree object`) :param bool firstCall: If True, this is the initial call and causes the plot to be shown twice (needed for Mac and possibly linux) - :param function pageCallback: a callback function to + :param function pageCallback: a callback function to update items on the parent page. Currently implemented for RB Models tab only ''' @@ -5801,12 +5809,16 @@ def FindPeaksBonds(XYZ): Bonds[i].append(Dx[j]/2.) Bonds[j].append(-Dx[j]/2.) return Bonds - + def SetCursorStatus(newxy,contours=False): View = GL.glGetIntegerv(GL.GL_VIEWPORT) Tx,Ty,Tz = drawingData['viewPoint'][0] tx,ty,tz = GLU.gluProject(Tx,Ty,Tz) - Cx,Cy,Cz = GLU.gluUnProject(newxy[0],View[3]-newxy[1],tz) + try: + Cx,Cy,Cz = GLU.gluUnProject(newxy[0],View[3]-newxy[1],tz) + except: # not sure why this happens, pyOpenGL 3.1.6 bug on MacOS? (3.1.9 OK) + G2frame.G2plotNB.status.SetStatusText('Cursor position calc failed',1) + return rho = G2mth.getRho([Cx,Cy,Cz],mapData) if contours: try: @@ -5817,7 +5829,7 @@ def SetCursorStatus(newxy,contours=False): G2frame.G2plotNB.status.SetStatusText('Cursor position: %.4f, %.4f, %.4f; density: %.4f'%(Cx,Cy,Cz,rho),1) else: G2frame.G2plotNB.status.SetStatusText('Cursor position: %.4f, %.4f, %.4f; density: %.4f'%(Cx,Cy,Cz,rho),1) - + def OnKeyBox(event): mode = cb.GetValue() if mode in ['jpeg','bmp','tiff',]: @@ -5833,13 +5845,13 @@ def OnKeyBox(event): if projFile: Fname = (os.path.splitext(projFile)[0]+'.'+mode).replace('*','+') else: - dlg = wx.FileDialog(G2frame, 'Choose graphics save file',G2G.GetExportPath(G2frame), + dlg = wx.FileDialog(G2frame, 'Choose graphics save file',G2G.GetExportPath(G2frame), wildcard='Graphics file (*.'+mode+')|*.'+mode,style=wx.FD_OPEN| wx.FD_CHANGE_DIR) try: if dlg.ShowModal() == wx.ID_OK: Fname = dlg.GetPath() finally: - dlg.Destroy() + dlg.Destroy() size = Page.canvas.GetSize() if Fname: GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1) @@ -5904,7 +5916,7 @@ def OnKey(event): #on key UP!! G2phG.ranDrwDict['delAtomsList'].append(i) data['Drawing']['Atoms'][i][cx+6] = (0,0,0) # mark atoms to be deleted in black & delete later elif G2phG.ranDrwDict['opt'] == 2: - cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) + cx,ct,cs,ci = G2mth.getAtomPtrs(data,draw=True) data['Drawing']['Atoms'][i][cs] = G2phG.ranDrwDict['style'] elif G2phG.ranDrwDict['opt'] == 3: G2phG.ranDrwDict['2call']( @@ -5954,7 +5966,7 @@ def OnKey(event): #on key UP!! atoms = drawingData['Atoms'] elif G2frame.phaseDisplay.GetPageText(getSelection()) == 'Atoms': cx,ct = data['General']['AtomPtrs'][:2] - atoms = data['Atoms'] + atoms = data['Atoms'] else: return if not len(atoms): #no atoms @@ -5965,7 +5977,7 @@ def OnKey(event): #on key UP!! if key in ['N',]: pI[1] += 1 else: - pI[1] -= 1 + pI[1] -= 1 pI[1] %= len(atoms) Tx,Ty,Tz = atoms[pI[1]][cx:cx+3] rho = G2mth.getRho([Tx,Ty,Tz],mapData) @@ -5979,11 +5991,11 @@ def OnKey(event): #on key UP!! line += ', density = %.2f'%rho G2frame.G2plotNB.status.SetStatusText(line,1) NPkey = True - + elif key in ['K'] and generalData['Map']['MapType']: drawingData['showSlice'] = (drawingData['showSlice']+1)%4 SetShowCS(drawingData['showSlice']) - + elif key in ['S']: choice = [m for m in mpl.cm.datad.keys()]+['GSPaired','GSPaired_r',] # if not m.endswith("_r") choice.sort() @@ -5992,7 +6004,7 @@ def OnKey(event): #on key UP!! sel = dlg.GetSelection() drawingData['contourColor'] = choice[sel] dlg.Destroy() - + elif key in ['U','D','L','R'] and mapData['Flip'] == True: dirDict = {'U':[0,1],'D':[0,-1],'L':[-1,0],'R':[1,0]} SetMapRoll(dirDict[key]) @@ -6012,7 +6024,7 @@ def OnKey(event): #on key UP!! Fname = generalData['Name']+'.gif' size = Page.canvas.GetSize() G2frame.tau = 0.0 - data['Drawing']['Atoms'],Fade = G2mth.ApplyModulation(data,G2frame.tau) #modifies drawing atom array! + data['Drawing']['Atoms'],Fade = G2mth.ApplyModulation(data,G2frame.tau) #modifies drawing atom array! SetDrawAtomsText(data['Drawing']['Atoms']) G2phG.FindBondsDraw(data) #rebuild bonds & polygons Draw('key down',Fade) @@ -6024,7 +6036,7 @@ def OnKey(event): #on key UP!! G2frame.tau = 0. for i in range(steps): G2frame.G2plotNB.status.SetStatusText('Modulation tau = %.2f'%(G2frame.tau),1) - data['Drawing']['Atoms'],Fade = G2mth.ApplyModulation(data,G2frame.tau) #modifies drawing atom array! + data['Drawing']['Atoms'],Fade = G2mth.ApplyModulation(data,G2frame.tau) #modifies drawing atom array! SetDrawAtomsText(data['Drawing']['Atoms']) G2phG.FindBondsDraw(data) #rebuild bonds & polygons Draw('key down',Fade) @@ -6036,7 +6048,7 @@ def OnKey(event): #on key UP!! except AttributeError: im.fromstring(Pix) im = im.transpose(Im.FLIP_TOP_BOTTOM) - writer.append_data(np.array(im)) + writer.append_data(np.array(im)) G2frame.tau += delt return elif key in ['+','-','=','0']: @@ -6044,7 +6056,7 @@ def OnKey(event): #on key UP!! OnKeyPressed(event) return Draw('key up',NPkey=NPkey) - + def OnKeyPressed(event): #On key down for repeating operation - used to change tau... try: keyCode = event.GetKeyCode() @@ -6064,7 +6076,7 @@ def OnKeyPressed(event): #On key down for repeating operation - used to chang G2frame.tau -= tstep G2frame.tau %= 1. #force 0-1 range; makes loop G2frame.G2plotNB.status.SetStatusText('Modulation tau = %.4f'%(G2frame.tau),1) - data['Drawing']['Atoms'],Fade = G2mth.ApplyModulation(data,G2frame.tau) #modifies drawing atom array! + data['Drawing']['Atoms'],Fade = G2mth.ApplyModulation(data,G2frame.tau) #modifies drawing atom array! SetDrawAtomsText(data['Drawing']['Atoms']) G2phG.FindBondsDraw(data) #rebuild bonds & polygons if not np.any(Fade): @@ -6110,7 +6122,7 @@ def OnKeyPressed(event): #On key down for repeating operation - used to chang data['Drawing']['Atoms'] = G2mth.ApplySeqData(data,seqData,PF2) SetDrawAtomsText(data['Drawing']['Atoms']) G2phG.FindBondsDrawCell(data,cell) #rebuild bonds & polygons - Draw('key down') + Draw('key down') except: #no useful sequential data; do Z-displacement instead if key in ['=','-']: #meaning '+','-' if key == '=': #'+' @@ -6123,11 +6135,11 @@ def OnKeyPressed(event): #On key down for repeating operation - used to chang VP += Zstep*VD VP = np.inner(Bmat,VP) drawingData['viewPoint'][0] = VP - SetViewPointText(VP) + SetViewPointText(VP) Draw('key down') newxy = event.GetPosition() - SetCursorStatus(newxy,drawingData.get('showSlice',False) in [1,3]) - + SetCursorStatus(newxy,drawingData.get('showSlice',False) in [1,3]) + def GetTruePosition(xy,Add=False): View = GL.glGetIntegerv(GL.GL_VIEWPORT) Proj = GL.glGetDoublev(GL.GL_PROJECTION_MATRIX) @@ -6158,7 +6170,7 @@ def GetTruePosition(xy,Add=False): elif G2frame.phaseDisplay.GetPageText(getSelection()) == 'Draw Atoms': atomList = drawAtoms cx,ct,cs,cia = G2mth.getAtomPtrs(data,True) - cs -= 1 #cs points at style for drawings; want sytsym + cs -= 1 #cs points at style for drawings; want sytsym else: atomList = data['Atoms'] cx,ct,cs,ciax = G2mth.getAtomPtrs(data) @@ -6182,7 +6194,7 @@ def GetTruePosition(xy,Add=False): lbl += ' ' + atom[cs] G2frame.G2plotNB.status.SetStatusText(' Selected atom: {}'.format(lbl),1) return - + def OnMouseDown(event): xy = event.GetPosition() if event.ShiftDown(): @@ -6193,9 +6205,9 @@ def OnMouseDown(event): Draw('Shift atom select') else: drawingData['oldxy'] = list(xy) - + def OnMouseUp(event): - '''This is used to initiate a FillCell action after a rigid body has been + '''This is used to initiate a FillCell action after a rigid body has been "dragged" if selected. Flags are used to try to make sure that this is only done once, even if multiple mouse up/down actions are made. ''' @@ -6206,12 +6218,12 @@ def OnMouseUp(event): rbObj['FillInProgress'] = True G2frame.testRBObjSizers['FillUnitCell'](None,selectAll=True) rbObj['FillInProgress'] = False - + def OnMouseMove(event): if event.ShiftDown(): #don't want any inadvertant moves when picking return newxy = event.GetPosition() - + if event.Dragging(): if event.AltDown() and rbObj: # dragging of a rigid body if event.CmdDown(): # Mac middlebutton workaround @@ -6256,7 +6268,7 @@ def OnMouseMove(event): Draw('move') elif drawingData.get('showSlice',False): SetCursorStatus(newxy,drawingData.get('showSlice',False) in [1,3]) - + def OnMouseWheel(event): if event.ShiftDown(): return @@ -6276,26 +6288,26 @@ def OnMouseWheel(event): G2frame.phaseDisplay.cameraPosTxt.ChangeValue(drawingData['cameraPos']) G2frame.phaseDisplay.cameraSlider.SetScaledValue(drawingData['cameraPos']) Draw('wheel') - + def getSelection(): try: return G2frame.phaseDisplay.GetSelection() except: G2frame.G2plotNB.status.SetStatusText('Select this from Phase data window!',1) return 0 - + def SetViewPointText(VP): page = getSelection() if page: if G2frame.phaseDisplay.GetPageText(page) == 'Draw Options': G2frame.phaseDisplay.viewPoint.ChangeValue('%.3f %.3f %.3f'%(VP[0],VP[1],VP[2])) - + def SetShowCS(CS): page = getSelection() if page: if G2frame.phaseDisplay.GetPageText(page) == 'Draw Options': G2frame.phaseDisplay.showCS.SetSelection(CS) - + def SetRBText(): '''Called w/Locate & Insert Rigid Body to update text in DataWindow when the RB orientation/origin is changed via a mouse drag @@ -6309,13 +6321,13 @@ def SetRBText(): pageCallback() except: pass - + def SetViewDirText(VD): page = getSelection() if page: if G2frame.phaseDisplay.GetPageText(page) == 'Draw Options': G2frame.phaseDisplay.viewDir.ChangeValue('%.3f %.3f %.3f'%(VD[0],VD[1],VD[2])) - + def SetMapPeaksText(mapPeaks): data['Map Peaks'] = mapPeaks page = getSelection() @@ -6323,7 +6335,7 @@ def SetMapPeaksText(mapPeaks): if G2frame.phaseDisplay.GetPageText(page) == 'Map peaks': G2frame.MapPeaksTable.SetData(data['Map Peaks']) G2frame.MapPeaks.Refresh() - + def SetDrawAtomsText(drawAtoms): page = getSelection() if page: @@ -6336,7 +6348,7 @@ def SetDrawAtomsText(drawAtoms): table[i][2:5] = atom[2:5] G2frame.atomTable.SetData(table) G2frame.drawAtoms.Refresh() - + def ClearSelectedAtoms(): page = getSelection() if page: @@ -6347,8 +6359,8 @@ def ClearSelectedAtoms(): widget.ClearSelection() # this is a grid break except AttributeError: - pass - + pass + def SetSelectedAtoms(ind,Add=False): page = getSelection() if page: @@ -6365,7 +6377,7 @@ def SetSelectedAtoms(ind,Add=False): widget.SelectRow(i,Add) #this is the Atoms grid in Atoms else: widget.SelectRow(ind,Add) # this is a grid - + def GetSelectedAtoms(): page = getSelection() Ind = [] @@ -6382,12 +6394,12 @@ def GetSelectedAtoms(): if 'testRBObj' not in data: return [] Ind = data['testRBObj'].get('CRYhighLight',[]) return Ind - + def SetBackground(): R,G,B,A = Page.camera['backColor'] GL.glClearColor(R,G,B,A) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - + def SetLights(): try: GL.glEnable(GL.GL_DEPTH_TEST) @@ -6402,26 +6414,26 @@ def SetLights(): GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[.7,.7,.7,1]) # glLightfv(GL_LIGHT0,GL_SPECULAR,[1,1,1,1]) # glLightfv(GL_LIGHT0,GL_POSITION,[0,0,1,1]) - + def GetRoll(newxy,rhoshape): Q = drawingData['Quaternion'] dxy = G2mth.prodQVQ(G2mth.invQ(Q),np.inner(Bmat,newxy+[0,])) - dxy = np.array(dxy*rhoshape) + dxy = np.array(dxy*rhoshape) roll = np.where(dxy>0.5,1,np.where(dxy<-.5,-1,0)) return roll - + def SetMapRoll(newxy): rho = generalData['Map']['rho'] roll = GetRoll(newxy,rho.shape) generalData['Map']['rho'] = np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) drawingData['oldxy'] = list(newxy) - + def Set4DMapRoll(newxy): rho = generalData['4DmapData']['rho'] if len(rho): roll = GetRoll(newxy,rho.shape[:3]) generalData['4DmapData']['rho'] = np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) - + def SetPeakRoll(newxy): rho = generalData['Map']['rho'] roll = GetRoll(newxy,rho.shape) @@ -6431,15 +6443,15 @@ def SetPeakRoll(newxy): peak[1:4] += dxy peak[1:4] %= 1. peak[4] = np.sqrt(np.sum(np.inner(Amat,peak[1:4])**2)) - + def SetTranslation(newxy): -#first get translation vector in screen coords. +#first get translation vector in screen coords. oldxy = drawingData['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy drawingData['oldxy'] = list(newxy) V = np.array([-dxy[0],dxy[1],0.]) -#then transform to rotated crystal coordinates & apply to view point +#then transform to rotated crystal coordinates & apply to view point Q = drawingData['Quaternion'] V = np.inner(Bmat,G2mth.prodQVQ(G2mth.invQ(Q),V)) Tx,Ty,Tz = drawingData['viewPoint'][0] @@ -6448,7 +6460,7 @@ def SetTranslation(newxy): Tz += V[2]*0.01 drawingData['viewPoint'][0] = np.array([Tx,Ty,Tz]) SetViewPointText([Tx,Ty,Tz]) - + def SetRBTranslation(newxy): #first get translation vector in screen coords. if 'fixOrig' in rbObj: @@ -6458,7 +6470,7 @@ def SetRBTranslation(newxy): dxy = newxy-oldxy drawingData['oldxy'] = list(newxy) V = np.array([-dxy[0],dxy[1],0.]) -#then transform to rotated crystal coordinates & apply to RB origin +#then transform to rotated crystal coordinates & apply to RB origin Q = drawingData['Quaternion'] V = np.inner(Bmat,G2mth.prodQVQ(G2mth.invQ(Q),V)) Tx,Ty,Tz = rbObj['Orig'][0] @@ -6467,10 +6479,10 @@ def SetRBTranslation(newxy): Tz -= V[2]*0.01 rbObj['Orig'][0][:] = Tx,Ty,Tz SetRBText() - + def SetRotation(newxy): 'Perform a rotation in x-y space due to a left-mouse drag' - #first get rotation vector in screen coords. & angle increment + #first get rotation vector in screen coords. & angle increment oldxy = drawingData['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy @@ -6491,9 +6503,9 @@ def SetRotation(newxy): VD /= np.sqrt(np.sum(VD**2)) drawingData['viewDir'] = VD SetViewDirText(VD) - - def SetRotationZ(newxy): -#first get rotation vector (= view vector) in screen coords. & angle increment + + def SetRotationZ(newxy): +#first get rotation vector (= view vector) in screen coords. & angle increment View = GL.glGetIntegerv(GL.GL_VIEWPORT) cent = [View[2]/2,View[3]/2] oldxy = drawingData['oldxy'] @@ -6508,7 +6520,7 @@ def SetRotationZ(newxy): if newxy[0] > cent[0]: A[0] *= -1 if newxy[1] < cent[1]: - A[1] *= -1 + A[1] *= -1 # next transform vector back to xtal coordinates & make new quaternion Q = drawingData['Quaternion'] V = np.inner(Amat,V) @@ -6519,7 +6531,7 @@ def SetRotationZ(newxy): drawingData['Quaternion'] = Q def SetRBRotation(newxy): -#first get rotation vector in screen coords. & angle increment +#first get rotation vector in screen coords. & angle increment oldxy = drawingData['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy @@ -6537,9 +6549,9 @@ def SetRBRotation(newxy): Q = G2mth.prodQQ(Q,DQ) rbObj['Orient'][0][:] = Q SetRBText() - - def SetRBRotationZ(newxy): -#first get rotation vector (= view vector) in screen coords. & angle increment + + def SetRBRotationZ(newxy): +#first get rotation vector (= view vector) in screen coords. & angle increment View = GL.glGetIntegerv(GL.GL_VIEWPORT) cent = [View[2]/2,View[3]/2] oldxy = drawingData['oldxy'] @@ -6554,7 +6566,7 @@ def SetRBRotationZ(newxy): if newxy[0] < cent[0]: A[0] *= -1 if newxy[1] > cent[1]: - A[1] *= -1 + A[1] *= -1 # next transform vector back to RB coordinates & make new quaternion Q = rbObj['Orient'][0] #rotate RB to cart V = np.inner(Amat,V) @@ -6584,7 +6596,7 @@ def RenderBox(): GL.glDisable(GL.GL_BLEND) GL.glDisable(GL.GL_COLOR_MATERIAL) GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[.2,.2,.2,1]) - + def RenderUnitVectors(x,y,z): GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[.7,.7,.7,1]) GL.glEnable(GL.GL_COLOR_MATERIAL) @@ -6610,7 +6622,7 @@ def RenderUnitVectors(x,y,z): def RenderRBtriplet(orig,Q,Bmat,symAxis=None): '''draw an axes triplet located at the origin of a rigid body - and with the x, y & z axes drawn as red, green and blue. + and with the x, y & z axes drawn as red, green and blue. ''' GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[.7,.7,.7,1]) GL.glEnable(GL.GL_COLOR_MATERIAL) @@ -6658,7 +6670,7 @@ def RenderPlane(plane,color): GL.glPopMatrix() GL.glDisable(GL.GL_BLEND) GL.glShadeModel(GL.GL_SMOOTH) - + def RenderViewPlane(plane,Z,width,height): global txID GL.glShadeModel(GL.GL_FLAT) @@ -6695,10 +6707,16 @@ def RenderViewPlane(plane,Z,width,height): GL.glDisable(GL.GL_BLEND) GL.glShadeModel(GL.GL_SMOOTH) - def RenderTextureSphere(x,y,z,radius,color,shape=[20,10],Fade=None): - SpFade = np.zeros(list(Fade.shape)+[4,],dtype=np.dtype('B')) - SpFade[:,:,:3] = Fade[:,:,nxs]*list(color) - SpFade[:,:,3] = 60 + def RenderTextureSphere(x,y,z,radius,ATcolor=None,shape=[20,10],Texture=None,ifFade=True): + SpFade = np.zeros(list(Texture.shape)+[4,],dtype=np.dtype('B')) + if ATcolor is None: + acolor = GetColorMap('RdYlGn') + SpFade = acolor(Texture)*255 + else: + SpFade[:,:,:3] = Texture[:,:,nxs]*list(ATcolor) + SpFade[:,:,3] = 255 + if ifFade: + SpFade[:,:,3] = 60 spID = GL.glGenTextures(1) GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1) GL.glEnable(GL.GL_BLEND) @@ -6730,7 +6748,7 @@ def RenderSphere(x,y,z,radius,color,fade=False,shape=[20,10]): GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) if fade: Fade = list(color) + [0.2,] - GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,Fade) #GL.GL_AMBIENT_AND_DIFFUSE causes striping + GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,Fade) #GL.GL_AMBIENT_AND_DIFFUSE causes striping GL.glShadeModel(GL.GL_FLAT) GL.glFrontFace(GL.GL_CCW) #shows outside GL.glEnable(GL.GL_CULL_FACE) #removes striping @@ -6746,7 +6764,7 @@ def RenderSphere(x,y,z,radius,color,fade=False,shape=[20,10]): GL.glDisable(GL.GL_CULL_FACE) GL.glDisable(GL.GL_BLEND) GL.glShadeModel(GL.GL_SMOOTH) - + def RenderFadeSphere(x,y,z,radius,color): fade = list(color) + [1.0,] GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_AMBIENT_AND_DIFFUSE,fade) @@ -6777,7 +6795,7 @@ def RenderDots(XYZ,RC): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderSmallSphere(x,y,z,radius,color): GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) GL.glPushMatrix() @@ -6786,7 +6804,7 @@ def RenderSmallSphere(x,y,z,radius,color): q = GLU.gluNewQuadric() GLU.gluSphere(q,radius,4,2) GL.glPopMatrix() - + def RenderEllipsoid(x,y,z,ellipseProb,E,R4,color): s1,s2,s3 = E GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) @@ -6800,7 +6818,7 @@ def RenderEllipsoid(x,y,z,ellipseProb,E,R4,color): GLU.gluSphere(q,ellipseProb,20,10) GL.glDisable(GL.GL_NORMALIZE) GL.glPopMatrix() - + def RenderBonds(x,y,z,Bonds,radius,color,slice=20): if not len(Bonds): return @@ -6819,9 +6837,9 @@ def RenderBonds(x,y,z,Bonds,radius,color,slice=20): GL.glRotate(phi,1,0,0) q = GLU.gluNewQuadric() GLU.gluCylinder(q,radius,radius,Z,slice,2) - GL.glPopMatrix() + GL.glPopMatrix() GL.glPopMatrix() - + def RenderMoment(x,y,z,Moment,color,slice=20): Dx = np.inner(Amat,Moment/ABC)/2. Z = np.sqrt(np.sum(Dx**2)) @@ -6845,8 +6863,8 @@ def RenderMoment(x,y,z,Moment,color,slice=20): GLU.gluDisk(q,.1,.2,slice,1) GLU.gluQuadricOrientation(q,GLU.GLU_OUTSIDE) GLU.gluCylinder(q,.2,0.,.4,slice,2) - GL.glPopMatrix() - + GL.glPopMatrix() + def RenderLines(x,y,z,Bonds,color): GL.glShadeModel(GL.GL_FLAT) xyz = np.array([x,y,z]) @@ -6863,7 +6881,7 @@ def RenderLines(x,y,z,Bonds,color): GL.glPopMatrix() GL.glDisable(GL.GL_COLOR_MATERIAL) GL.glShadeModel(GL.GL_SMOOTH) - + def RenderPolyhedra(x,y,z,Faces,color): GL.glShadeModel(GL.GL_FLAT) GL.glPushMatrix() @@ -6898,7 +6916,7 @@ def RenderMapPeak(x,y,z,color,den): GL.glPopMatrix() GL.glDisable(GL.GL_COLOR_MATERIAL) GL.glShadeModel(GL.GL_SMOOTH) - + def RenderBackbone(Backbone,BackboneColor,radius): GL.glPushMatrix() GL.glMultMatrixf(B4mat.T) @@ -6906,13 +6924,13 @@ def RenderBackbone(Backbone,BackboneColor,radius): GL.glShadeModel(GL.GL_SMOOTH) # gle.gleSetJoinStyle(TUBE_NORM_EDGE | TUBE_JN_ANGLE | TUBE_JN_CAP) # gle.glePolyCylinder(Backbone,BackboneColor,radius) - GL.glPopMatrix() + GL.glPopMatrix() GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderLabel(x,y,z,label,r,color,matRot,offset=wx.RealPoint(0.,0.)): ''' color wx.Colour object - ''' + ''' GL.glPushMatrix() GL.glTranslate(x,y,z) GL.glMultMatrixf(B4mat.T) @@ -6923,11 +6941,11 @@ def RenderLabel(x,y,z,label,r,color,matRot,offset=wx.RealPoint(0.,0.)): text.draw_text(scale=0.025,position=offset) GL.glEnable(GL.GL_LIGHTING) GL.glPopMatrix() - + def RenderLabel2(x,y,z,label,r,color,matRot,offset=wx.RealPoint(0.,0.)): ''' color wx.Colour object - doesn't work - ''' + ''' figure = mplfig.Figure(figsize=(1.,1.),facecolor=(0.,0.,0.,0.)) figure.clf() canvas = hcCanvas(figure) @@ -6940,8 +6958,8 @@ def RenderLabel2(x,y,z,label,r,color,matRot,offset=wx.RealPoint(0.,0.)): img, (width, height) = agg.print_to_buffer() Timg = np.frombuffer(img, np.uint8).reshape((height, width, 4)) RenderViewPlane(1.0*eplane,Timg,width,height) - - + + def RenderMap(rho,rhoXYZ,indx,Rok): GL.glShadeModel(GL.GL_FLAT) cLevel = drawingData['contourLevel'] @@ -6962,7 +6980,7 @@ def RenderMap(rho,rhoXYZ,indx,Rok): RC.append([0.2*alpha,2*Gr]) RenderDots(XYZ,RC) GL.glShadeModel(GL.GL_SMOOTH) - + def distances2Peaks(x,y,z,PeakDistRadius,mapPeaks,peakMax,radius,Amat,matRot): ''' show distances to other peaks within PeakDistRadius A ''' @@ -6989,7 +7007,7 @@ def distances2Atoms(x,y,z,atomsExpandRadius,atomsdistRadius,Amat,matRot): vdwScale = drawingData['vdwScale'] ballScale = drawingData['ballScale'] xyzA = np.array((x,y,z)) - cx,ct,cs,ci = G2mth.getAtomPtrs(data) + cx,ct,cs,ci = G2mth.getAtomPtrs(data) cellArray = G2lat.CellBlock(1) radDict = dict(zip(*G2phG.getAtomRadii(data))) Names = [] @@ -7042,7 +7060,7 @@ def distances2Atoms(x,y,z,atomsExpandRadius,atomsdistRadius,Amat,matRot): ax,ay,az = xyz if len(bondData[i]): RenderBonds(ax,ay,az,bondData[i],bondR,Color[i]) - + def Draw(caller='',Fade=[],NPkey=False): #reinitialize geometry stuff - needed after tab change global cell, Vol, Amat, Bmat, A4mat, B4mat, BondRadii @@ -7109,11 +7127,11 @@ def Draw(caller='',Fade=[],NPkey=False): GS[0][2] = GS[2][0] = math.sqrt(GS[0][0]*GS[2][2]) GS[1][2] = GS[2][1] = math.sqrt(GS[1][1]*GS[2][2]) ellipseProb = G2lat.criticalEllipse(drawingData['ellipseProb']/100.) - + SetBackground() GL.glInitNames() GL.glPushName(0) - + GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() if sys.platform == "darwin": @@ -7124,7 +7142,7 @@ def Draw(caller='',Fade=[],NPkey=False): GLU.gluPerspective(20.,aspect,cPos-Zclip,cPos+Zclip) GLU.gluLookAt(0,0,cPos,0,0,0,0,1,0) SetLights() - + GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() matRot = G2mth.Q2Mat(Q) @@ -7186,13 +7204,6 @@ def Draw(caller='',Fade=[],NPkey=False): radius = ballScale*drawingData['sizeH'] else: radius = 0.0 - # elif 'Q' in atom[ct]: #spinning rigid body - set shell color - # for Srb in RBdata.get('Spin',[]): - # if Srb == generalData['SpnIds'][atom[ci]]: - # fade = True - # Info = G2elem.GetAtomInfo(RBdata['Spin'][Srb]['atType']) - # atColor = [Info['Color'],] - # break else: if 'vdW' in atom[cs]: radius = vdwScale*vdWRadii[atNum] @@ -7208,8 +7219,10 @@ def Draw(caller='',Fade=[],NPkey=False): SytSym = G2spc.SytSym(atom[cx:cx+3],SGData)[0] radius = SpnData.get('Radius',[[1.0,False],]) #patch for missing Radius atColor = SpnData['atColor'] + ifFade = SpnData.get('fadeSh',True) + useAtColor = SpnData.get('useAtColor',True) symAxis = np.array(SpnData.get('symAxis',[0,0,1])) - Npsi,Ngam = 60,30 #seems acceptable - don't use smaller! + Npsi,Ngam = 90,45 QA = G2mth.invQ(SpnData['Orient'][0]) #rotate about chosen axis QB = G2mth.make2Quat(symAxis,np.array([0,0,1.]))[0] #position obj polar axis QP = G2mth.AVdeg2Q(360./Npsi,np.array([0,0,1.])) #this shifts by 1 azimuth pixel @@ -7222,21 +7235,49 @@ def Draw(caller='',Fade=[],NPkey=False): SpnData['hide'] = SpnData.get('hide',[False for i in range(len(SpnData['atType']))]) for ish,nSH in enumerate(SpnData['nSH']): if not SpnData['hide'][ish]: - if nSH > 0: + if nSH > 0: SHC = SpnData['SHC'][ish] + atcolor = None + if useAtColor: + atcolor = atColor[ish] P = G2lat.SHarmcal(SytSym,SHC,PSIp,GAMp).reshape((Npsi,Ngam)) if np.min(P) < np.max(P): P = (P-np.min(P))/(np.max(P)-np.min(P)) - RenderTextureSphere(x,y,z,radius[ish][0],atColor[ish],shape=[Npsi,Ngam],Fade=P.T) + RenderTextureSphere(x,y,z,radius[ish][0],atcolor,shape=[Npsi,Ngam],Texture=P.T,ifFade=ifFade) else: RenderSphere(x,y,z,radius[ish][0],atColor[ish],True,shape=[60,30]) else: - RenderSphere(x,y,z,radius,atColor) + #### put deformation texture on sphere here + if atom[ci] in deformationData: + defCtrls = deformationData[-atom[ci]] + defParms = deformationData[atom[ci]] + SytSym = G2spc.SytSym(atom[cx:cx+3],SGData)[0] + if defCtrls.get('showDef',False) and defCtrls['Radial'] == 'Slater': + useAtColor = defCtrls.get('atColor',True) + atcolor = None + if useAtColor: + atcolor = atColor*255 + SHC = defParms[0][1] + SHC = {item.replace('D','C'):SHC[item] for item in SHC if item not in ['Ne','kappa']} + UVMat = defCtrls['UVmat'] + Npsi,Ngam = 90,45 + PSI,GAM = np.mgrid[0:Npsi,0:Ngam] #[azm,pol] + PSI = PSI.flatten()*360./Npsi #azimuth 0-360 ncl + GAM = GAM.flatten()*180./Ngam #polar 0-180 incl + Rp,PSIp,GAMp = G2mth.RotPolbyM(np.ones_like(PSI),PSI,GAM,UVMat) + P = G2lat.SHarmcal(SytSym,SHC,PSIp,GAMp).reshape((Npsi,Ngam)) + if np.min(P) < np.max(P): + P = (P-np.min(P))/(np.max(P)-np.min(P)) + RenderTextureSphere(x,y,z,radius,atcolor,shape=[Npsi,Ngam],Texture=P.T,ifFade=False) + else: + RenderSphere(x,y,z,radius,atColor) + else: + RenderSphere(x,y,z,radius,atColor) if 'sticks' in atom[cs]: RenderBonds(x,y,z,Bonds,bondR,bndColor) elif 'ellipsoids' in atom[cs]: RenderBonds(x,y,z,Bonds,bondR,bndColor) - if atom[cs+3] == 'A': + if atom[cs+3] == 'A': Uij = atom[cs+5:cs+11] U = np.multiply(G2spc.Uij2U(Uij),GS) U = np.inner(Amat,np.inner(U,Amat).T) @@ -7246,7 +7287,7 @@ def Draw(caller='',Fade=[],NPkey=False): if atom[ct] == 'H' and not drawingData['showHydrogen']: pass else: - RenderEllipsoid(x,y,z,ellipseProb,E,R4,atColor) + RenderEllipsoid(x,y,z,ellipseProb,E,R4,atColor) else: if atom[ct] == 'H' and not drawingData['showHydrogen']: pass @@ -7267,7 +7308,7 @@ def Draw(caller='',Fade=[],NPkey=False): Backbones[atom[2]] = [] Backbones[atom[2]].append(list(np.inner(Amat,np.array([x,y,z])))) BackboneColor.append(list(atColor)) - + if generalData['Type'] == 'magnetic': magMult = drawingData.get('magMult',1.0) SymOp = int(atom[cs-1].split('+')[0]) @@ -7278,7 +7319,7 @@ def Draw(caller='',Fade=[],NPkey=False): color = Rd/255. if SymFade and atom[cs-1] != '1': color *= .5 - RenderMoment(x,y,z,Moment,color) + RenderMoment(x,y,z,Moment,color) if atom[cs+1] == 'type': RenderLabel(x,y,z,' '+atom[ct],radius,wxGreen,matRot) @@ -7294,10 +7335,13 @@ def Draw(caller='',Fade=[],NPkey=False): RenderLabel(x,y,z,' '+atom[ct-2],radius,wxGreen,matRot) if not FourD and len(rhoXYZ) and drawingData['showMap']: #no green dot map for 4D - it's wrong! RenderMap(rho,rhoXYZ,indx,Rok) - if (pageName == 'Draw Atoms' or pageName == 'Draw Options') and ( - drawingData['VPPeakDistRad'] + drawingData['VPatomsExpandRad'] - + drawingData['VPatomsDistRad'] - ) > 0: + try: + disSum = (drawingData['VPPeakDistRad'] + + drawingData['VPatomsExpandRad'] + + drawingData['VPatomsDistRad']) + except: + disSum = 0 + if (pageName == 'Draw Atoms' or pageName == 'Draw Options') and disSum > 0: PeakDistRadius = drawingData['VPPeakDistRad'] atomsExpandRadius = drawingData['VPatomsExpandRad'] atomsdistRadius = drawingData['VPatomsDistRad'] @@ -7374,8 +7418,10 @@ def Draw(caller='',Fade=[],NPkey=False): Backbone = Backbones[chain] RenderBackbone(Backbone,BackboneColor,bondR) if drawingData['showVoids']: - for x,y,z in drawingData['Voids']: - RenderSphere(x,y,z,.05,(0.,0.,1.),True) + RC = len(drawingData['Voids'])*[[0.05,2*Bl]] + RenderDots(drawingData['Voids'],RC) + # for x,y,z in drawingData['Voids']: + # RenderSphere(x,y,z,.05,(0.,0.,1.),True) if drawingData['unitCellBox']: RenderBox() if drawingData['Plane'][1]: @@ -7416,15 +7462,13 @@ def Draw(caller='',Fade=[],NPkey=False): if drawingData.get('showSlice') in [1,]: contourSet = ax0.contour(Z,colors='k',linewidths=1) if drawingData.get('showSlice') in [2,3]: - acolor = GetColorMap(drawingData.get('contourColor','Paired')) + acolor = GetColorMap(drawingData.get('contourColor','GSPaired')) ax0.imshow(ZU,aspect='equal',cmap=acolor,alpha=0.7,interpolation='bilinear') if drawingData.get('showSlice') in [3,]: contourSet = ax0.contour(ZU,colors='k',linewidths=1) ax0.axis("off") figure.subplots_adjust(bottom=0.,top=1.,left=0.,right=1.,wspace=0.,hspace=0.) - agg = canvas.switch_backends(hcCanvas) - agg.draw() - img, (width, height) = agg.print_to_buffer() + img, (width,height) = canvas.print_to_buffer() Zimg = np.frombuffer(img, np.uint8).reshape((height, width, 4)) RenderViewPlane(msize*eplane,Zimg,width,height) try: @@ -7432,14 +7476,14 @@ def Draw(caller='',Fade=[],NPkey=False): except: pass Page.canvas.SwapBuffers() - + def OnSize(event): Draw('size') - + def OnFocus(event): - Draw('focus') - Draw('focus') #to get correct drawing after tab change - + Draw('focus') + Draw('focus') #to get correct drawing after tab change + #### PlotStructure starts here global mcsaXYZ,mcsaTypes,mcsaBonds,txID,contourSet global cell, Vol, Amat, Bmat, A4mat, B4mat, BondRadii @@ -7470,7 +7514,7 @@ def OnFocus(event): drawingData = data['Drawing'] if not drawingData: return #nothing setup, nothing to draw - + G2phG.SetDrawingDefaults(drawingData) if 'Map Peaks' in data: mapPeaks = np.array(data['Map Peaks']) @@ -7501,7 +7545,7 @@ def OnFocus(event): equiv = list(G2spc.GenAtom(xyz,SGData,All=True,Move=False)) neqv = max(neqv,len(equiv)) for item in equiv: - mcsaXYZ.append(item[0]) + mcsaXYZ.append(item[0]) mcsaTypes.append(atyp) mcsaXYZ = np.array(mcsaXYZ) mcsaTypes = np.array(mcsaTypes) @@ -7513,10 +7557,10 @@ def OnFocus(event): mcsaXYZ = np.swapaxes(mcsaXYZ,0,1)-cent[:,np.newaxis,:] mcsaTypes = np.swapaxes(mcsaTypes,0,1) mcsaXYZ = np.reshape(mcsaXYZ,(nuniq*neqv,3)) - mcsaTypes = np.reshape(mcsaTypes,(nuniq*neqv)) - mcsaBonds = FindPeaksBonds(mcsaXYZ) + mcsaTypes = np.reshape(mcsaTypes,(nuniq*neqv)) + mcsaBonds = FindPeaksBonds(mcsaXYZ) drawAtoms = drawingData.get('Atoms',[]) - + deformationData = data.get('Deformations',{}) mapData = {'MapType':False, 'rho':[]} showBonds = False if 'Map' in generalData: @@ -7533,8 +7577,8 @@ def OnFocus(event): eBox = np.array([[0,1],[0,0],[1,0],[1,1],]) eplane = np.array([[-1,-1,0],[-1,1,0],[1,1,0],[1,-1,0]]) uEdges = np.array([ - [uBox[0],uBox[1]],[uBox[0],uBox[3]],[uBox[0],uBox[4]],[uBox[1],uBox[2]], - [uBox[2],uBox[3]],[uBox[1],uBox[5]],[uBox[2],uBox[6]],[uBox[3],uBox[7]], + [uBox[0],uBox[1]],[uBox[0],uBox[3]],[uBox[0],uBox[4]],[uBox[1],uBox[2]], + [uBox[2],uBox[3]],[uBox[1],uBox[5]],[uBox[2],uBox[6]],[uBox[3],uBox[7]], [uBox[4],uBox[5]],[uBox[5],uBox[6]],[uBox[6],uBox[7]],[uBox[7],uBox[4]]]) mD = 0.1 mV = np.array([[[-mD,0,0],[mD,0,0]],[[0,-mD,0],[0,mD,0]],[[0,0,-mD],[0,0,mD]]]) @@ -7545,7 +7589,7 @@ def OnFocus(event): uColors = [Rd,Gr,Bl,Wt-Bc, Wt-Bc,Wt-Bc,Wt-Bc,Wt-Bc, Wt-Bc,Wt-Bc,Wt-Bc,Wt-Bc] G2frame.tau = 0. G2frame.seq = 0 - + new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab(generalData['Name'],'ogl') if new: Page.views = False @@ -7607,7 +7651,7 @@ def OnMouseDown(event): def OnMouseMove(event): newxy = event.GetPosition() - + if event.Dragging(): if event.LeftIsDown(): SetRotation(newxy) @@ -7618,18 +7662,18 @@ def OnMouseMove(event): Q = defaults['Quaternion'] G2frame.G2plotNB.status.SetStatusText('New quaternion: %.2f+, %.2fi+ ,%.2fj+, %.2fk'%(Q[0],Q[1],Q[2],Q[3]),1) Draw('move') - + def OnMouseWheel(event): defaults['cameraPos'] += event.GetWheelRotation()/24 defaults['cameraPos'] = max(10,min(500,defaults['cameraPos'])) G2frame.G2plotNB.status.SetStatusText('New camera distance: %.2f'%(defaults['cameraPos']),1) Draw('wheel') - + def SetBackground(): R,G,B,A = Page.camera['backColor'] GL.glClearColor(R,G,B,A) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - + def SetLights(): try: GL.glEnable(GL.GL_DEPTH_TEST) @@ -7642,9 +7686,9 @@ def SetLights(): GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE,0) GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[1,1,1,.8]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[1,1,1,1]) - + def SetRotation(newxy): -#first get rotation vector in screen coords. & angle increment +#first get rotation vector in screen coords. & angle increment oldxy = defaults['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy @@ -7663,9 +7707,9 @@ def SetRotation(newxy): VD = G2mth.Q2Mat(Q)[2] VD /= np.sqrt(np.sum(VD**2)) defaults['viewDir'] = VD - - def SetRotationZ(newxy): -#first get rotation vector (= view vector) in screen coords. & angle increment + + def SetRotationZ(newxy): +#first get rotation vector (= view vector) in screen coords. & angle increment View = GL.glGetIntegerv(GL.GL_VIEWPORT) cent = [View[2]/2,View[3]/2] oldxy = defaults['oldxy'] @@ -7680,7 +7724,7 @@ def SetRotationZ(newxy): if newxy[0] > cent[0]: A[0] *= -1 if newxy[1] < cent[1]: - A[1] *= -1 + A[1] *= -1 # next transform vector back to xtal coordinates & make new quaternion Q = defaults['Quaternion'] Qx = G2mth.AVdeg2Q(A[0],V) @@ -7703,7 +7747,7 @@ def RenderUnitVectors(x,y,z): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderSphere(x,y,z,radius,color,fade=False): if fade: Fade = list(color) + [0.5,] @@ -7714,7 +7758,7 @@ def RenderSphere(x,y,z,radius,color,fade=False): q = GLU.gluNewQuadric() GLU.gluSphere(q,radius,40,20) GL.glPopMatrix() - + def Draw(caller=''): cPos = defaults['cameraPos'] VS = np.array(Page.canvas.GetSize()) @@ -7723,7 +7767,7 @@ def Draw(caller=''): SetBackground() GL.glInitNames() GL.glPushName(0) - + GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() if sys.platform == "darwin": @@ -7733,8 +7777,8 @@ def Draw(caller=''): GL.glViewport(0,0,VS[0],VS[1]) GLU.gluPerspective(50.,aspect,1.,500.) GLU.gluLookAt(0,0,cPos,0,0,0,0,1,0) - SetLights() - + SetLights() + GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() matRot = G2mth.Q2Mat(Q) @@ -7754,10 +7798,10 @@ def Draw(caller=''): def OnSize(event): Draw('size') - + def OnFocus(event): Draw('focus') - + def OnKeyBox(event): mode = cb.GetValue() if mode in ['jpeg','bmp','tiff',]: @@ -7769,7 +7813,7 @@ def OnKeyBox(event): except ImportError: print ("PIL/pillow Image module not present. Cannot save images without this") raise Exception("PIL/pillow Image module not found") - + Fname = os.path.join(Mydir,Page.name+'.'+mode) print (Fname+' saved') size = Page.canvas.GetSize() @@ -7792,7 +7836,7 @@ def OnKeyBox(event): PDB.write('ATOM %4d CA ALA A%4d %8.3f%8.3f%8.3f 1.00 0.00\n'%(iatm+1,iatm+1,xyz[0],xyz[1],xyz[2])) PDB.close() G2frame.G2plotNB.status.SetStatusText('PDB model saved to: '+Fname,1) - + # PlotBeadModel execution starts here Mydir = G2frame.dirname Rd = np.array([255,0,0]) @@ -7812,7 +7856,7 @@ def OnKeyBox(event): size=(G2frame.G2plotNB.status.firstLen,-1)) cb.Bind(wx.EVT_COMBOBOX, OnKeyBox) cb.SetValue(' save as/key:') - + Page.canvas.Bind(wx.EVT_MOUSEWHEEL, OnMouseWheel) Page.canvas.Bind(wx.EVT_LEFT_DOWN, OnMouseDown) Page.canvas.Bind(wx.EVT_RIGHT_DOWN, OnMouseDown) @@ -7851,14 +7895,14 @@ def FindBonds(XYZ): Bonds[i].append(Dx[j]*Radii[i]/sumR[j]) Bonds[j].append(-Dx[j]*Radii[j]/sumR[j]) return Bonds - + def OnMouseDown(event): xy = event.GetPosition() defaults['oldxy'] = list(xy) def OnMouseMove(event): newxy = event.GetPosition() - + if event.Dragging(): if event.LeftIsDown(): SetRotation(newxy) @@ -7869,18 +7913,18 @@ def OnMouseMove(event): Q = defaults['Quaternion'] G2frame.G2plotNB.status.SetStatusText('New quaternion: %.2f+, %.2fi+ ,%.2fj+, %.2fk'%(Q[0],Q[1],Q[2],Q[3]),1) Draw('move') - + def OnMouseWheel(event): defaults['cameraPos'] += event.GetWheelRotation()/24 defaults['cameraPos'] = max(10,min(500,defaults['cameraPos'])) G2frame.G2plotNB.status.SetStatusText('New camera distance: %.2f'%(defaults['cameraPos']),1) Draw('wheel') - + def SetBackground(): R,G,B,A = Page.camera['backColor'] GL.glClearColor(R,G,B,A) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - + def SetLights(): try: GL.glEnable(GL.GL_DEPTH_TEST) @@ -7893,9 +7937,9 @@ def SetLights(): GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE,0) GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[1,1,1,.8]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[1,1,1,1]) - + def SetRotation(newxy): -#first get rotation vector in screen coords. & angle increment +#first get rotation vector in screen coords. & angle increment oldxy = defaults['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy @@ -7914,9 +7958,9 @@ def SetRotation(newxy): VD = G2mth.Q2Mat(Q)[2] VD /= np.sqrt(np.sum(VD**2)) defaults['viewDir'] = VD - - def SetRotationZ(newxy): -#first get rotation vector (= view vector) in screen coords. & angle increment + + def SetRotationZ(newxy): +#first get rotation vector (= view vector) in screen coords. & angle increment View = GL.glGetIntegerv(GL.GL_VIEWPORT) cent = [View[2]/2,View[3]/2] oldxy = defaults['oldxy'] @@ -7931,7 +7975,7 @@ def SetRotationZ(newxy): if newxy[0] > cent[0]: A[0] *= -1 if newxy[1] < cent[1]: - A[1] *= -1 + A[1] *= -1 # next transform vector back to xtal coordinates & make new quaternion Q = defaults['Quaternion'] Qx = G2mth.AVdeg2Q(A[0],V) @@ -7954,7 +7998,7 @@ def RenderUnitVectors(x,y,z): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderSphere(x,y,z,radius,color): GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) GL.glPushMatrix() @@ -7962,7 +8006,7 @@ def RenderSphere(x,y,z,radius,color): q = GLU.gluNewQuadric() GLU.gluSphere(q,radius,20,10) GL.glPopMatrix() - + def RenderBonds(x,y,z,Bonds,radius,color,slice=20): GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) GL.glPushMatrix() @@ -7977,10 +8021,10 @@ def RenderBonds(x,y,z,Bonds,radius,color,slice=20): GL.glRotate(phi,1,0,0) q = GLU.gluNewQuadric() GLU.gluCylinder(q,radius,radius,Z,slice,2) - GL.glPopMatrix() + GL.glPopMatrix() GL.glPopMatrix() - - def RenderLabel(x,y,z,label,matRot): + + def RenderLabel(x,y,z,label,matRot): GL.glPushMatrix() GL.glTranslate(x,y,z) GL.glDisable(GL.GL_LIGHTING) @@ -7991,9 +8035,9 @@ def RenderLabel(x,y,z,label,matRot): text.draw_text(scale=0.025) GL.glEnable(GL.GL_LIGHTING) GL.glPopMatrix() - + def Draw(caller=''): -#useful debug? +#useful debug? # if caller: # print caller # end of useful debug @@ -8004,7 +8048,7 @@ def Draw(caller=''): SetBackground() GL.glInitNames() GL.glPushName(0) - + GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() if sys.platform == "darwin": @@ -8014,8 +8058,8 @@ def Draw(caller=''): GL.glViewport(0,0,VS[0],VS[1]) GLU.gluPerspective(20.,aspect,1.,500.) GLU.gluLookAt(0,0,cPos,0,0,0,0,1,0) - SetLights() - + SetLights() + GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() matRot = G2mth.Q2Mat(Q) @@ -8047,10 +8091,10 @@ def Draw(caller=''): def OnSize(event): Draw('size') - + def OnFocus(event): Draw('focus') - + def OnKeyBox(event): mode = cb.GetValue() if mode in ['jpeg','bmp','tiff',]: @@ -8062,7 +8106,7 @@ def OnKeyBox(event): except ImportError: print ("PIL/pillow Image module not present. Cannot save images without this") raise Exception("PIL/pillow Image module not found") - + Fname = os.path.join(Mydir,Page.name+'.'+mode) print (Fname+' saved') size = Page.canvas.GetSize() @@ -8092,7 +8136,7 @@ def UpdateDraw(): Bonds[i] = newBonds[i] Draw() # drawing twice seems needed sometimes at least on mac Draw() - + # PlotRigidBody execution starts here Mydir = G2frame.dirname Rd = np.array([255,0,0]) @@ -8136,7 +8180,7 @@ def UpdateDraw(): size=(G2frame.G2plotNB.status.firstLen,-1)) cb.Bind(wx.EVT_COMBOBOX, OnKeyBox) cb.SetValue(' save as/key:') - + Font = Page.GetFont() Page.canvas.Bind(wx.EVT_MOUSEWHEEL, OnMouseWheel) Page.canvas.Bind(wx.EVT_LEFT_DOWN, OnMouseDown) @@ -8154,11 +8198,11 @@ def UpdateDraw(): Draw('main') Draw('main') #to fill both buffers so save works if rbType == 'Vector': return UpdateDraw + #### Plot Layers ################################################################################ -def PlotLayers(G2frame,Layers,laySeq,defaults): +def PlotLayers(G2frame,Layers,laySeq,defaults,firstCall=False): '''Layer plotting package. Can show layer structures as balls & sticks ''' - global AtNames,AtTypes,XYZ,Bonds,Faces def FindBonds(atTypes,XYZ): @@ -8196,8 +8240,8 @@ def FindFaces(Bonds): norm /= np.sqrt(np.sum(norm**2)) faces.append([face,norm]) Faces.append(faces) - return Faces - + return Faces + def getAtoms(): global AtNames,AtTypes,XYZ,Bonds,Faces AtNames = [] @@ -8205,7 +8249,7 @@ def getAtoms(): newXYZ = np.zeros((0,3)) TX = np.zeros(3) for il in range(len(laySeq)): - layer = laySeq[il] + layer = laySeq[il] if Layers['Layers'][layer]['SameAs']: layer = Names.index(Layers['Layers'][layer]['SameAs']) atNames = [atom[0] for atom in Layers['Layers'][layer]['Atoms']] @@ -8237,10 +8281,10 @@ def getAtoms(): AtTypes *= len(Units) XYZ = newXYZ # GSASIIpath.IPyBreak() - + Bonds = FindBonds(AtTypes,XYZ) Faces = FindFaces(Bonds) - + def OnKeyBox(event): mode = cb.GetValue() if mode in ['jpeg','bmp','tiff',]: @@ -8315,20 +8359,20 @@ def OnPlotKeyPress(event): SetTransText(Yi,Xi,Trans[Yi][Xi],3) getAtoms() Draw('shift') - + def SetTransText(Yi,Xi,XYZ,id): page = G2frame.phaseDisplay.GetSelection() if page: if G2frame.phaseDisplay.GetPageText(page) == 'Layers': G2frame.phaseDisplay.GetPage(page).transGrids[Yi].Refresh() - + def OnMouseDown(event): xy = event.GetPosition() defaults['oldxy'] = list(xy) def OnMouseMove(event): newxy = event.GetPosition() - + if event.Dragging(): if event.LeftIsDown(): SetRotation(newxy) @@ -8338,17 +8382,17 @@ def OnMouseMove(event): elif event.MiddleIsDown(): SetRotationZ(newxy) Draw('move') - + def OnMouseWheel(event): defaults['cameraPos'] += event.GetWheelRotation()/24 defaults['cameraPos'] = max(10,min(500,defaults['cameraPos'])) Draw('wheel') - + def SetBackground(): R,G,B,A = Page.camera['backColor'] GL.glClearColor(R,G,B,A) GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - + def SetLights(): try: GL.glEnable(GL.GL_DEPTH_TEST) @@ -8361,15 +8405,15 @@ def SetLights(): GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE,0) GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[1,1,1,.8]) GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[1,1,1,1]) - + def SetTranslation(newxy): -#first get translation vector in screen coords. +#first get translation vector in screen coords. oldxy = defaults['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy defaults['oldxy'] = list(newxy) V = np.array([-dxy[0],dxy[1],0.]) -#then transform to rotated crystal coordinates & apply to view point +#then transform to rotated crystal coordinates & apply to view point Q = defaults['Quaternion'] V = np.inner(Bmat,G2mth.prodQVQ(G2mth.invQ(Q),V)) Tx,Ty,Tz = defaults['viewPoint'][0] @@ -8378,9 +8422,9 @@ def SetTranslation(newxy): Ty += V[1]*delt Tz += V[2]*delt defaults['viewPoint'][0] = np.array([Tx,Ty,Tz]) - + def SetRotation(newxy): -#first get rotation vector in screen coords. & angle increment +#first get rotation vector in screen coords. & angle increment oldxy = defaults['oldxy'] if not len(oldxy): oldxy = list(newxy) dxy = newxy-oldxy @@ -8399,9 +8443,9 @@ def SetRotation(newxy): VD = G2mth.Q2Mat(Q)[2] VD /= np.sqrt(np.sum(VD**2)) defaults['viewDir'] = VD - - def SetRotationZ(newxy): -#first get rotation vector (= view vector) in screen coords. & angle increment + + def SetRotationZ(newxy): +#first get rotation vector (= view vector) in screen coords. & angle increment View = GL.glGetIntegerv(GL.GL_VIEWPORT) cent = [View[2]/2,View[3]/2] oldxy = defaults['oldxy'] @@ -8416,7 +8460,7 @@ def SetRotationZ(newxy): if newxy[0] > cent[0]: A[0] *= -1 if newxy[1] < cent[1]: - A[1] *= -1 + A[1] *= -1 # next transform vector back to xtal coordinates & make new quaternion Q = defaults['Quaternion'] Qx = G2mth.AVdeg2Q(A[0],V) @@ -8439,7 +8483,7 @@ def RenderUnitVectors(x,y,z): GL.glPopMatrix() GL.glColor4ubv([0,0,0,0]) GL.glDisable(GL.GL_COLOR_MATERIAL) - + def RenderSphere(x,y,z,radius,color): GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) GL.glPushMatrix() @@ -8448,7 +8492,7 @@ def RenderSphere(x,y,z,radius,color): q = GLU.gluNewQuadric() GLU.gluSphere(q,radius,20,10) GL.glPopMatrix() - + def RenderBonds(x,y,z,Bonds,radius,color,slice=20): GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color) GL.glPushMatrix() @@ -8464,9 +8508,9 @@ def RenderBonds(x,y,z,Bonds,radius,color,slice=20): GL.glRotate(phi,1,0,0) q = GLU.gluNewQuadric() GLU.gluCylinder(q,radius,radius,Z,slice,2) - GL.glPopMatrix() + GL.glPopMatrix() GL.glPopMatrix() - + def RenderPolyhedra(x,y,z,Faces,color): GL.glShadeModel(GL.GL_FLAT) GL.glPushMatrix() @@ -8485,7 +8529,7 @@ def RenderPolyhedra(x,y,z,Faces,color): GL.glPopMatrix() GL.glShadeModel(GL.GL_SMOOTH) - def RenderLabel(x,y,z,label,matRot): + def RenderLabel(x,y,z,label,matRot): GL.glPushMatrix() GL.glTranslate(x,y,z) GL.glMultMatrixf(B4mat.T) @@ -8497,9 +8541,9 @@ def RenderLabel(x,y,z,label,matRot): text.draw_text(scale=0.025) GL.glEnable(GL.GL_LIGHTING) GL.glPopMatrix() - + def Draw(caller=''): -#useful debug? +#useful debug? # if caller: # print caller # end of useful debug @@ -8512,7 +8556,7 @@ def Draw(caller=''): SetBackground() GL.glInitNames() GL.glPushName(0) - + GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() if sys.platform == "darwin": @@ -8522,8 +8566,8 @@ def Draw(caller=''): GL.glViewport(0,0,VS[0],VS[1]) GLU.gluPerspective(20.,aspect,1.,500.) GLU.gluLookAt(0,0,cPos,0,0,0,0,1,0) - SetLights() - + SetLights() + GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() matRot = G2mth.Q2Mat(Q) @@ -8560,10 +8604,10 @@ def Draw(caller=''): def OnSize(event): Draw('size') - + def OnFocus(event): Draw('focus') - + # PlotLayers execution starts here cell = Layers['Cell'][1:7] Amat,Bmat = G2lat.cell2AB(cell) #Amat - crystal to cartesian, Bmat - inverse @@ -8577,15 +8621,15 @@ def OnFocus(event): Bc = np.array([0,0,0]) uBox = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0],[0,0,1],[1,0,1],[1,1,1],[0,1,1]]) uEdges = np.array([ - [uBox[0],uBox[1]],[uBox[0],uBox[3]],[uBox[0],uBox[4]],[uBox[1],uBox[2]], - [uBox[2],uBox[3]],[uBox[1],uBox[5]],[uBox[2],uBox[6]],[uBox[3],uBox[7]], + [uBox[0],uBox[1]],[uBox[0],uBox[3]],[uBox[0],uBox[4]],[uBox[1],uBox[2]], + [uBox[2],uBox[3]],[uBox[1],uBox[5]],[uBox[2],uBox[6]],[uBox[3],uBox[7]], [uBox[4],uBox[5]],[uBox[5],uBox[6]],[uBox[6],uBox[7]],[uBox[7],uBox[4]]]) uColors = [Rd,Gr,Bl,Wt-Bc, Wt-Bc,Wt-Bc,Wt-Bc,Wt-Bc, Wt-Bc,Wt-Bc,Wt-Bc,Wt-Bc] uEdges[2][1][2] = len(laySeq) AtInfo = Layers['AtInfo'] Names = [layer['Name'] for layer in Layers['Layers']] getAtoms() - + new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('Layer','ogl') if new: Page.views = False @@ -8615,8 +8659,10 @@ def OnFocus(event): Page.camera['backColor'] = np.array([0,0,0,0]) Page.context = wx.glcanvas.GLContext(Page.canvas) Page.canvas.SetCurrent(Page.context) + if firstCall: # on Mac need to draw twice, but needs to be delayed + wx.CallAfter(Draw,'main') wx.CallAfter(Draw,'main') - + #### Plot Cluster Analysis #################################################### def PlotClusterXYZ(G2frame,YM,XYZ,CLuDict,Title='',PlotName='cluster'): @@ -8629,16 +8675,16 @@ def PlotClusterXYZ(G2frame,YM,XYZ,CLuDict,Title='',PlotName='cluster'): :param str PlotName: plot tab name ''' import scipy.cluster.hierarchy as SCH - from mpl_toolkits.axes_grid1.inset_locator import inset_axes - + from mpl_toolkits.axes_grid1.inset_locator import inset_axes + global SetPick SetPick = True def OnMotion(event): global SetPick - if event.xdata and event.ydata: + if event.xdata and event.ydata: G2frame.G2plotNB.status.SetStatusText('x=%.3f y=%.3f'%(event.xdata,event.ydata),1) SetPick = True - + def OnPick(event): global SetPick if SetPick: @@ -8648,8 +8694,8 @@ def OnPick(event): G2frame.G2plotNB.status.SetStatusText(text,1) SetPick = False print(text) - - Colors = ['xkcd:blue','xkcd:red','xkcd:green','xkcd:cyan', + + Colors = ['xkcd:blue','xkcd:red','xkcd:green','xkcd:cyan', 'xkcd:magenta','xkcd:black','xkcd:pink','xkcd:brown', 'xkcd:teal','xkcd:orange','xkcd:grey','xkcd:violet', 'xkcd:aqua','xkcd:blueberry','xkcd:bordeaux'] #need 15 colors! @@ -8664,7 +8710,7 @@ def OnPick(event): Page.canvas.mpl_connect('pick_event', OnPick) Page.Choice = None np.seterr(all='ignore') - + if YM is not None: Imin = np.min(YM) Imax = np.max(YM) @@ -8724,7 +8770,7 @@ def OnPick(event): Plot.set_xlabel('PCA axis-1',fontsize=12) Plot.set_ylabel('PCA axis-2',fontsize=12) Plot.set_zlabel('PCA axis-3',fontsize=12) - else: + else: Plot.set_visible(False) #hide old plot frame, will get replaced below gs = mpl.gridspec.GridSpec(2,2,figure=Page.figure) ax1 = Page.figure.add_subplot(gs[0,0]) @@ -8760,4 +8806,3 @@ def OnPick(event): ax3.set_xlabel('PCA index',fontsize=12) ax3.set_ylabel('% of total',fontsize=12) Page.canvas.draw() - diff --git a/GSASII/GSASIIpwd.py b/GSASII/GSASIIpwd.py index 60775fd7d..f68855416 100644 --- a/GSASII/GSASIIpwd.py +++ b/GSASII/GSASIIpwd.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ''' -Classes and routines defined in :mod:`GSASIIpwd` follow. +Classes and routines defined in :mod:`GSASIIpwd` follow. ''' from __future__ import division, print_function @@ -24,28 +24,30 @@ import scipy.special as sp import scipy.signal as signal -import GSASIIpath +from . import GSASIIpath +GSASIIpath.SetBinaryPath() -filversion = "?" +filversion = str(GSASIIpath.GetVersionNumber()) +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIElem as G2elem +from . import GSASIImath as G2mth +from . import GSASIIfiles as G2fil try: - import git_verinfo - filversion = git_verinfo.git_tags[0] -except: - pass -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIElem as G2elem -import GSASIImath as G2mth -try: - import pypowder as pyd + if GSASIIpath.binaryPath: + import pypowder as pyd + else: + from . import pypowder as pyd except ImportError: print ('pypowder is not available - profile calcs. not allowed') try: - import pydiffax as pyx + if GSASIIpath.binaryPath: + import pydiffax as pyx + else: + from . import pydiffax as pyx except ImportError: print ('pydiffax is not available for this platform') -import GSASIIfiles as G2fil - + # trig functions in degrees tand = lambda x: math.tan(x*math.pi/180.) atand = lambda x: 180.*math.atan(x)/math.pi @@ -73,7 +75,7 @@ def PhaseWtSum(G2frame,histo): ''' Calculate sum of phase mass*phase fraction for PWDR data (exclude magnetic phases) - + :param G2frame: GSASII main frame structure :param str histo: histogram name :returns: sum(scale*mass) for phases in histo @@ -88,7 +90,7 @@ def PhaseWtSum(G2frame,histo): phFr = Phases[phase]['Histograms'][histo]['Scale'][0] wtSum += mass*phFr return wtSum - + #### GSASII pwdr & pdf calculation routines ################################################################################ def Transmission(Geometry,Abs,Diam): ''' @@ -122,19 +124,19 @@ def Transmission(Geometry,Abs,Diam): return math.exp(-MuR) elif 'Bragg' in Geometry: return 0.0 - + def SurfaceRough(SRA,SRB,Tth): ''' Suortti (J. Appl. Cryst, 5,325-331, 1972) surface roughness correction :param float SRA: Suortti surface roughness parameter :param float SRB: Suortti surface roughness parameter :param float Tth: 2-theta(deg) - can be numpy array - + ''' sth = npsind(Tth/2.) T1 = np.exp(-SRB/sth) T2 = SRA+(1.-SRA)*np.exp(-SRB) return (SRA+(1.-SRA)*T1)/T2 - + def SurfaceRoughDerv(SRA,SRB,Tth): ''' Suortti surface roughness correction derivatives :param float SRA: Suortti surface roughness parameter (dimensionless) @@ -158,7 +160,7 @@ def Absorb(Geometry,MuR,Tth,Phi=0,Psi=0): :param float Phi: flat plate tilt angle - future :param float Psi: flat plate tilt axis - future ''' - + def muRunder3(MuR,Sth2): T0 = 16.0/(3.*np.pi) T1 = (25.99978-0.01911*Sth2**0.25)*np.exp(-0.024551*Sth2)+ \ @@ -168,7 +170,7 @@ def muRunder3(MuR,Sth2): T3 = 0.003045+0.018167*Sth2-0.03305*Sth2**2 Trns = -T0*MuR-T1*MuR**2-T2*MuR**3-T3*MuR**4 return np.exp(Trns) - + def muRover3(MuR,Sth2): T1 = 1.433902+11.07504*Sth2-8.77629*Sth2*Sth2+ \ 10.02088*Sth2**3-3.36778*Sth2**4 @@ -179,7 +181,7 @@ def muRover3(MuR,Sth2): T4 = 0.044365-0.04259/(1.0+0.41051*Sth2)**148.4202 Trns = (T1-T4)/(1.0+T2*(MuR-3.0))**T3+T4 return Trns/100. - + Sth2 = npsind(Tth/2.0)**2 if 'Cylinder' in Geometry: #Lobanov & Alte da Veiga for 2-theta = 0; beam fully illuminates sample if 'array' in str(type(MuR)): @@ -204,7 +206,7 @@ def muRover3(MuR,Sth2): MuT = 2.*MuR cth = npcosd(Tth/2.0) return np.exp(-MuT/cth)/cth - + def AbsorbDerv(Geometry,MuR,Tth,Phi=0,Psi=0): 'needs a doc string' dA = 0.001 @@ -214,7 +216,7 @@ def AbsorbDerv(Geometry,MuR,Tth,Phi=0,Psi=0): return (AbsP-AbsM)/(2.0*dA) else: return (AbsP-1.)/dA - + def Polarization(Pola,Tth,Azm=0.0): """ Calculate angle dependent x-ray polarization correction (not scaled correctly!) @@ -233,7 +235,7 @@ def Polarization(Pola,Tth,Azm=0.0): pola = ((1.0-Pola)*cazm+Pola*sazm)*npcosd(Tth)**2+(1.0-Pola)*sazm+Pola*cazm dpdPola = -npsind(Tth)**2*(sazm-cazm) return pola,dpdPola - + def Oblique(ObCoeff,Tth): 'currently assumes detector is normal to beam' if ObCoeff: @@ -241,7 +243,7 @@ def Oblique(ObCoeff,Tth): return K else: return 1.0 - + def Ruland(RulCoff,wave,Q,Compton): 'needs a doc string' C = 2.9978e8 @@ -258,11 +260,11 @@ def KleinNishina(wave,Q): P = 1./(1.+(1.-npcosd(TTh)*(hmc/wave))) KN = (P**3-(P*npsind(TTh))**2+P)/(1.+npcosd(TTh)**2) return KN - + def LorchWeight(Q): 'needs a doc string' return np.sin(np.pi*(Q[-1]-Q)/(2.0*Q[-1])) - + def GetAsfMean(ElList,Sthl2): '''Calculate various scattering factor terms for PDF calcs @@ -284,14 +286,14 @@ def GetAsfMean(ElList,Sthl2): FF2 += ff2*el['FormulaNo']/sumNoAtoms CF += cf*el['FormulaNo']/sumNoAtoms return FF2,FF**2,CF - + def GetNumDensity(ElList,Vol): 'needs a doc string' sumNoAtoms = 0.0 for El in ElList: sumNoAtoms += ElList[El]['FormulaNo'] return sumNoAtoms/Vol - + def CalcPDF(data,inst,limits,xydata): '''Computes I(Q), S(Q) & G(r) from Sample, Bkg, etc. diffraction patterns loaded into dict xydata; results are placed in xydata. @@ -330,7 +332,7 @@ def CalcPDF(data,inst,limits,xydata): print("Interpolating Container background since points don't match") interpF = si.interp1d(xydata['Container'][1][0],xycontainer,fill_value='extrapolate') IofQ[1][1] += interpF(IofQ[1][0]) - + data['IofQmin'] = IofQ[1][1][-1] IofQ[1][1] -= data.get('Flat Bkg',0.) #get element data & absorption coeff. @@ -358,17 +360,17 @@ def CalcPDF(data,inst,limits,xydata): SA *= ElList[El]['FormulaNo']/data['Form Vol'] Abs += SA MuR = Abs*data['Diam']/2. - IofQ[1][1] /= Absorb(data['Geometry'],MuR,inst['2-theta'][1]*np.ones(len(wave))) + IofQ[1][1] /= Absorb(data['Geometry'],MuR,inst['2-theta'][1]*np.ones(len(wave))) # improves look of F(Q) but no impact on G(R) # bBut,aBut = signal.butter(8,.5,"lowpass") # IofQ[1][1] = signal.filtfilt(bBut,aBut,IofQ[1][1]) - XY = IofQ[1] + XY = IofQ[1] #convert to Q nQpoints = 5000 if 'C' in inst['Type'][0]: wave = G2mth.getWave(inst) minQ = npT2q(Tth[0],wave) - maxQ = npT2q(Tth[-1],wave) + maxQ = npT2q(Tth[-1],wave) Qpoints = np.linspace(0.,maxQ,nQpoints,endpoint=True) dq = Qpoints[1]-Qpoints[0] XY[0] = npT2q(XY[0],wave) @@ -382,7 +384,7 @@ def CalcPDF(data,inst,limits,xydata): XY[0] = 2.*np.pi*difC/XY[0] Qdata = si.griddata(XY[0],XY[1],Qpoints,method='linear',fill_value=XY[1][-1]) #interpolate I(Q) Qdata -= np.min(Qdata)*data['BackRatio'] - + qLimits = data['QScaleLim'] maxQ = np.searchsorted(Qpoints,min(Qpoints[-1],qLimits[1]))+1 minQ = np.searchsorted(Qpoints,min(qLimits[0],0.90*Qpoints[-1])) @@ -395,7 +397,7 @@ def CalcPDF(data,inst,limits,xydata): for item in xydata['IofQ'][1]: newdata.append(item[:maxQ]) xydata['IofQ'][1] = newdata - + xydata['SofQ'] = copy.deepcopy(xydata['IofQ']) if 'XC' in inst['Type'][0]: FFSq,SqFF,CF = GetAsfMean(ElList,(xydata['SofQ'][1][0]/(4.0*np.pi))**2) #these are ,^2,Cf @@ -408,7 +410,7 @@ def CalcPDF(data,inst,limits,xydata): if 'XC' in inst['Type'][0]: # CF *= KleinNishina(wave,Q) ruland = Ruland(data['Ruland'],wave,Q,CF) -# auxPlot.append([Q,ruland,'Ruland']) +# auxPlot.append([Q,ruland,'Ruland']) CF *= ruland # auxPlot.append([Q,CF,'CF-Corr']) scale = np.sum((FFSq+CF)[minQ:maxQ])/np.sum(xydata['SofQ'][1][1][minQ:maxQ]) @@ -421,7 +423,7 @@ def CalcPDF(data,inst,limits,xydata): xydata['FofQ'] = copy.deepcopy(xydata['SofQ']) xydata['FofQ'][1][1] = xydata['FofQ'][1][0]*(xydata['SofQ'][1][1]-1.0) if data['Lorch']: - xydata['FofQ'][1][1] *= LorchWeight(Q) + xydata['FofQ'][1][1] *= LorchWeight(Q) xydata['GofR'] = copy.deepcopy(xydata['FofQ']) xydata['gofr'] = copy.deepcopy(xydata['FofQ']) nR = len(xydata['GofR'][1][1]) @@ -445,10 +447,10 @@ def CalcPDF(data,inst,limits,xydata): xydata['gofr'][1][1] = np.where(R nmin :param int thresh: maximum prime factor allowed :Returns: list of data sizes where the maximum prime factor is < thresh - ''' + ''' plist = [] nmin = max(1,nmin) nmax = max(nmin+1,nmax) @@ -707,12 +709,12 @@ class norm_gen(st.rv_continuous): normal.pdf(x) = exp(-x**2/2)/sqrt(2*pi) ''' - + def pdf(self,x,*args,**kwds): loc,scale=kwds['loc'],kwds['scale'] x = (x-loc)/scale return np.exp(-x**2/2.0) * _norm_pdf_C / scale - + norm = norm_gen(name='norm') ## Cauchy @@ -732,9 +734,9 @@ def pdf(self,x,*args,**kwds): loc,scale=kwds['loc'],kwds['scale'] x = (x-loc)/scale return 1.0/np.pi/(1.0+x*x) / scale - + cauchy = cauchy_gen(name='cauchy') - + class fcjde_gen(st.rv_continuous): """ @@ -743,7 +745,7 @@ class fcjde_gen(st.rv_continuous): :param x: array -1 to 1 :param t: 2-theta position of peak - :param s: sum(S/L,H/L); S: sample height, H: detector opening, + :param s: sum(S/L,H/L); S: sample height, H: detector opening, L: sample to detector opening distance :param dx: 2-theta step size in deg @@ -753,10 +755,10 @@ class fcjde_gen(st.rv_continuous): * s = S/L+H/L * if x < 0:: - fcj.pdf = [1/sqrt({cos(T)**2/cos(t)**2}-1) - 1/s]/|cos(T)| + fcj.pdf = [1/sqrt({cos(T)**2/cos(t)**2}-1) - 1/s]/|cos(T)| + + * if x >= 0: fcj.pdf = 0 - * if x >= 0: fcj.pdf = 0 - """ def _pdf(self,x,t,s,dx): T = dx*x+t @@ -767,14 +769,14 @@ def _pdf(self,x,t,s,dx): fx = np.where(ax>bx,(np.sqrt(bx/(ax-bx))-1./s)/ax2,0.0) fx = np.where(fx > 0.,fx,0.0) return fx - + def pdf(self,x,*args,**kwds): loc=kwds['loc'] return self._pdf(x-loc,*args) - + fcjde = fcjde_gen(name='fcjde',shapes='t,s,dx') - -def getFCJVoigt(pos,intens,sig,gam,shl,xdata): + +def getFCJVoigt(pos,intens,sig,gam,shl,xdata): '''Compute the Finger-Cox-Jepcoat modified Voigt function for a CW powder peak by direct convolution. This version is not used. ''' @@ -797,21 +799,21 @@ def getFCJVoigt(pos,intens,sig,gam,shl,xdata): Df /= np.sum(Df) Df = si.interp1d(x,Df,bounds_error=False,fill_value=0.0) return intens*Df(xdata)*DX/dx - -#### GSASII peak fitting routine: Finger, Cox & Jephcoat model + +#### GSASII peak fitting routine: Finger, Cox & Jephcoat model def getWidthsCW(pos,sig,gam,shl): '''Compute the peak widths used for computing the range of a peak - for constant wavelength data. On low-angle side, 50 FWHM are used, + for constant wavelength data. On low-angle side, 50 FWHM are used, on high-angle side 75 are used, low angle side extended for axial divergence (for peaks above 90 deg, these are reversed.) - + :param pos: peak position; 2-theta in degrees :param sig: Gaussian peak variance in centideg^2 :param gam: Lorentzian peak width in centidegrees :param shl: axial divergence parameter (S+H)/L - - :returns: widths; [Gaussian sigma, Lorentzian gamma] in degrees, and + + :returns: widths; [Gaussian sigma, Lorentzian gamma] in degrees, and low angle, high angle ends of peak; 50 FWHM & 75 FWHM from position reversed for 2-theta > 90 deg. ''' @@ -820,19 +822,19 @@ def getWidthsCW(pos,sig,gam,shl): fmin = 50.*(fwhm+shl*abs(npcosd(pos))) fmax = 75.0*fwhm if pos > 90.: - fmin,fmax = [fmax,fmin] + fmin,fmax = [fmax,fmin] return widths,fmin,fmax - + def getWidthsED(pos,sig,gam): '''Compute the peak widths used for computing the range of a peak - for energy dispersive data. On low-energy side, 20 FWHM are used, + for energy dispersive data. On low-energy side, 20 FWHM are used, on high-energy side 20 are used - + :param pos: peak position; energy in keV (not used) :param sig: Gaussian peak variance in keV^2 :param gam: Lorentzian peak width in keV - - :returns: widths; [Gaussian sigma, Lorentzian gamma] in keV, and + + :returns: widths; [Gaussian sigma, Lorentzian gamma] in keV, and low angle, high angle ends of peak; 5 FWHM & 5 FWHM from position ''' widths = [np.sqrt(sig),gam] @@ -840,38 +842,38 @@ def getWidthsED(pos,sig,gam): fmin = 5.*fwhm fmax = 5.*fwhm return widths,fmin,fmax - + def getWidthsTOF(pos,alp,bet,sig,gam): '''Compute the peak widths used for computing the range of a peak - for TOF data. 50 FWHM are used on both sides each + for TOF data. 50 FWHM are used on both sides each extended by exponential coeff. - + param pos: peak position; TOF in musec (not used) param alp,bet: TOF peak exponential rise & decay parameters param sig: Gaussian peak variance in musec^2 param gam: Lorentzian peak width in musec - + returns: widths; [Gaussian sigma, Lornetzian gamma] in musec returns: low TOF, high TOF ends of peak; 50FWHM from position ''' widths = [np.sqrt(sig),gam] fwhm = 2.355*widths[0]+2.*widths[1] - fmin = 50.*fwhm*(1.+1./alp) + fmin = 50.*fwhm*(1.+1./alp) fmax = 50.*fwhm*(1.+1./bet) return widths,fmin,fmax - + def getWidthsCWA(pos,alp,bet,sig,gam,shl): '''Compute the peak widths used for computing the range of a peak - for constant wavelength data with axial divergence. 50 & 75 FWHM are used on + for constant wavelength data with axial divergence. 50 & 75 FWHM are used on each side each extended by exponential coeff. - + :param pos: peak position; 2-theta in degrees :param alp,bet: TOF peak exponential rise & decay parameters :param sig: Gaussian peak variance in centideg^2 :param gam: Lorentzian peak width in centidegrees :param shl: axial divergence parameter (S+H)/L - - :returns: widths; [Gaussian sigma, Lorentzian gamma] in degrees, and + + :returns: widths; [Gaussian sigma, Lorentzian gamma] in degrees, and low angle, high angle ends of peak; 50 FWHM & 75 FWHM from position reversed for 2-theta > 90 deg. ''' @@ -884,37 +886,37 @@ def getWidthsCWA(pos,alp,bet,sig,gam,shl): fmin = 75.0*fwhm*(1.+1./alp) fmax = 50.0*(fwhm*(1.+1./bet)+shl*abs(npcosd(pos))) return widths,fmin,fmax - + def getWidthsCWB(pos,alp,bet,sig,gam): '''Compute the peak widths used for computing the range of a peak - for constant wavelength data without axial divergence. 50 FWHM are used on + for constant wavelength data without axial divergence. 50 FWHM are used on both sides each extended by exponential coeff. - + :param pos: peak position; 2-theta in degrees :param alp,bet: TOF peak exponential rise & decay parameters :param sig: Gaussian peak variance in centideg^2 :param gam: Lorentzian peak width in centidegrees - + returns: widths; [Gaussian sigma, Lornetzian gamma] in degrees returns: low angle, high angle ends of peak; 50FWHM from position ''' widths = [np.sqrt(sig)/100.,gam/100.] fwhm = 2.355*widths[0]+2.*widths[1] - fmin = 50.*fwhm*(1.+1./alp) + fmin = 50.*fwhm*(1.+1./alp) fmax = 50.*fwhm*(1.+1./bet) return widths,fmin,fmax - + def getFWHM(pos,Inst,N=1): '''Compute total FWHM from Thompson, Cox & Hastings (1987) , J. Appl. Cryst. 20, 79-83 via getgamFW(g,s). - + :param pos: float peak position in deg 2-theta or tof in musec :param Inst: dict instrument parameters :param N: int Inst index (0 for input, 1 for fitted) - + :returns float: total FWHM of pseudoVoigt in deg or musec - ''' - + ''' + sig = lambda Th,U,V,W: np.sqrt(max(0.001,U*tand(Th)**2+V*tand(Th)+W)) sigED = lambda E,A,B,C: np.sqrt(max(0.001,A*E**2+B*E+C)) sigTOF = lambda dsp,S0,S1,S2,Sq: np.sqrt(S0+S1*dsp**2+S2*dsp**4+Sq*dsp) @@ -954,38 +956,38 @@ def getFWHM(pos,Inst,N=1): s = sig(pos/2.,Inst['U'][N],Inst['V'][N],Inst['W'][N]) g = gam(pos/2.,Inst['X'][N],Inst['Y'][N],Inst['Z'][N]) return getgamFW(g,s)/100.+np.log(2.0)*(alp+bet)/(alp*bet) #returns FWHM in deg - + def getgamFW(g,s): '''Compute total FWHM from Thompson, Cox & Hastings (1987), J. Appl. Cryst. 20, 79-83 lambda fxn needs FWHM for both Gaussian & Lorentzian components - + :param g: float Lorentzian gamma = FWHM(L) :param s: float Gaussian sig - + :returns float: total FWHM of pseudoVoigt - ''' + ''' gamFW = lambda s,g: np.exp(np.log(s**5+2.69269*s**4*g+2.42843*s**3*g**2+4.47163*s**2*g**3+0.07842*s*g**4+g**5)/5.) return gamFW(2.35482*s,g) #sqrt(8ln2)*sig = FWHM(G) - + def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): - '''Computes the background based on parameters that may be taken from + '''Computes the background based on parameters that may be taken from a gpx file or the data tree. :param str pfx: histogram prefix (:h:) :param dict parmDict: Refinement parameter values :param str bakType: defines background function to be used. Should be - one of these: 'chebyschev', 'cosine', 'chebyschev-1', - 'Q^2 power series', 'Q^-2 power series', 'lin interpolate', + one of these: 'chebyschev', 'cosine', 'chebyschev-1', + 'Q^2 power series', 'Q^-2 power series', 'lin interpolate', 'inv interpolate', 'log interpolate' :param str dataType: Code to indicate histogram type (PXC, PNC, PNT,...) - :param MaskedArray xdata: independent variable, 2theta (deg*100) or + :param MaskedArray xdata: independent variable, 2theta (deg*100) or TOF (microsec?) - :param numpy.array fixback: Array of fixed background points (length + :param numpy.array fixback: Array of fixed background points (length matching xdata) or None - :returns: yb,sumBK where yp is an array of background values (length + :returns: yb,sumBK where yp is an array of background values (length matching xdata) and sumBK is a list with three values. The sumBK[0] is - the sum of all yb values, sumBK[1] is the sum of Debye background terms + the sum of all yb values, sumBK[1] is the sum of Debye background terms and sumBK[2] is the sum of background peaks. ''' if 'T' in dataType: @@ -1007,7 +1009,7 @@ def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): break #empirical functions if bakType in ['chebyschev','cosine','chebyschev-1']: - dt = xdata[-1]-xdata[0] + dt = xdata[-1]-xdata[0] for iBak in range(nBak): key = pfx+'Back;'+str(iBak) if bakType == 'chebyschev': @@ -1058,7 +1060,7 @@ def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): #Debye function if pfx+'difC' in parmDict or 'E' in dataType: ff = 1. - else: + else: try: wave = parmDict[pfx+'Lam'] except KeyError: @@ -1066,7 +1068,7 @@ def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): SQ = (q/(4.*np.pi))**2 FF = G2elem.GetFormFactorCoeff('Si')[0] ff = np.array(G2elem.ScatFac(FF,SQ)[0])**2 - iD = 0 + iD = 0 while True: try: dbA = parmDict[pfx+'DebyeA;'+str(iD)] @@ -1075,7 +1077,7 @@ def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): ybi = ff*dbA*np.sin(q*dbR)*np.exp(-dbU*q**2)/(q*dbR) yb += ybi sumBk[1] += np.sum(ybi) - iD += 1 + iD += 1 except KeyError: break #peaks @@ -1094,7 +1096,7 @@ def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): alp = max(0.1,parmDict[pfx+'alpha-0']+parmDict[pfx+'alpha-1']*sinPos) bet = max(0.001,parmDict[pfx+'beta-0']+parmDict[pfx+'beta-1']*sinPos) Wd,fmin,fmax = getWidthsCWB(pkP,alp,bet,pkS,pkG) - elif 'A'' in dataType': + elif 'A'' in dataType': shl = parmDict[pfx+'SH/L'] sinPos = npsind(pkP/2.0) alp = max(0.1,parmDict[pfx+'alpha-0']+parmDict[pfx+'alpha-1']*sinPos) @@ -1127,26 +1129,26 @@ def getBackground(pfx,parmDict,bakType,dataType,xdata,fixback=None): raise Exception('dataType of {:} should not happen!'.format(dataType)) yb[iBeg:iFin] += ybi sumBk[2] += np.sum(ybi) - iD += 1 + iD += 1 except KeyError: break except ValueError: G2fil.G2Print ('**** WARNING - backround peak '+str(iD)+' sigma is negative; fix & try again ****') break - if fixback is not None: + if fixback is not None: yb += parmDict.get(pfx+'BF mult',1.0)*fixback sumBk[0] = sum(yb) return yb,sumBk - + def getBackgroundDerv(hfx,parmDict,bakType,dataType,xdata,fixback=None): - '''Computes the derivatives of the background + '''Computes the derivatives of the background Parameters passed to this may be pulled from gpx file or data tree. See :func:`getBackground` for parameter definitions. - :returns: dydb,dyddb,dydpk,dydfb where the first three are 2-D arrays - of derivatives with respect to the background terms, the Debye terms and - the background peak terms vs. the points in the diffracton pattern. The - final 1D array is the derivative with respect to the fixed-background + :returns: dydb,dyddb,dydpk,dydfb where the first three are 2-D arrays + of derivatives with respect to the background terms, the Debye terms and + the background peak terms vs. the points in the diffracton pattern. The + final 1D array is the derivative with respect to the fixed-background multiplier (= the fixed background values). ''' if 'T' in dataType: @@ -1170,8 +1172,8 @@ def getBackgroundDerv(hfx,parmDict,bakType,dataType,xdata,fixback=None): dydfb = [] if bakType in ['chebyschev','cosine','chebyschev-1']: - dt = xdata[-1]-xdata[0] - for iBak in range(nBak): + dt = xdata[-1]-xdata[0] + for iBak in range(nBak): if bakType == 'chebyschev': dydb[iBak] = (-1.+2.*(xdata-xdata[0])/dt)**iBak elif bakType == 'chebyschev-1': @@ -1224,7 +1226,7 @@ def getBackgroundDerv(hfx,parmDict,bakType,dataType,xdata,fixback=None): SQ = (q/(4*np.pi))**2 FF = G2elem.GetFormFactorCoeff('Si')[0] ff = np.array(G2elem.ScatFac(FF,SQ)[0])*np.pi**2 #needs pi^2~10. for cw data (why?) - iD = 0 + iD = 0 while True: try: if hfx+'difC' in parmDict: @@ -1256,7 +1258,7 @@ def getBackgroundDerv(hfx,parmDict,bakType,dataType,xdata,fixback=None): alp = max(0.1,parmDict[hfx+'alpha-0']+parmDict[hfx+'alpha-1']*sinPos) bet = max(0.001,parmDict[hfx+'beta-0']+parmDict[hfx+'beta-1']*sinPos) Wd,fmin,fmax = getWidthsCWB(pkP,alp,bet,pkS,pkG) - elif 'A'' in dataType': + elif 'A'' in dataType': shl = parmDict[hfx+'SH/L'] sinPos = npsind(pkP/2.0) alp = max(0.1,parmDict[hfx+'alpha-0']+parmDict[hfx+'alpha-1']*sinPos) @@ -1293,12 +1295,12 @@ def getBackgroundDerv(hfx,parmDict,bakType,dataType,xdata,fixback=None): dydpk[4*iD+1][iBeg:iFin] += Df dydpk[4*iD+2][iBeg:iFin] += pkI*dFds dydpk[4*iD+3][iBeg:iFin] += pkI*dFdg - iD += 1 + iD += 1 except KeyError: break except ValueError: G2fil.G2Print ('**** WARNING - backround peak '+str(iD)+' sigma is negative; fix & try again ****') - break + break # fixed background from file if fixback is not None: dydfb = fixback @@ -1308,13 +1310,13 @@ def getBackgroundDerv(hfx,parmDict,bakType,dataType,xdata,fixback=None): def getFCJVoigt3(pos,sig,gam,shl,xdata): '''Compute the Finger-Cox-Jepcoat modified Pseudo-Voigt function for a CW powder peak in external Fortran routine - + param pos: peak position in degrees param sig: Gaussian variance in centideg^2 param gam: Lorentzian width in centideg param shl: axial divergence parameter (S+H)/L param xdata: array; profile points for peak to be calculated; bounded by 20FWHM to 50FWHM (or vv) - + returns: array: calculated peak function at each xdata returns: integral of peak; nominally = 1.0 ''' @@ -1329,22 +1331,22 @@ def getFCJVoigt3(pos,sig,gam,shl,xdata): def getdFCJVoigt3(pos,sig,gam,shl,xdata): '''Compute analytic derivatives the Finger-Cox-Jepcoat modified Pseudo-Voigt function for a CW powder peak - + param pos: peak position in degrees param sig: Gaussian variance in centideg^2 param gam: Lorentzian width in centideg param shl: axial divergence parameter (S+H)/L param xdata: array; profile points for peak to be calculated; bounded by 20FWHM to 50FWHM (or vv) - + returns: arrays: function and derivatives of pos, sig, gam, & shl ''' Df,dFdp,dFds,dFdg,dFdsh = pyd.pydpsvfcjo(len(xdata),xdata-pos,pos,sig,gam,shl) return Df,dFdp,dFds,dFdg,dFdsh def getExpFCJVoigt3(pos,alp,bet,sig,gam,shl,xdata): - '''Compute the Finger-Cox-Jepcoat modified double exponential Pseudo-Voigt + '''Compute the Finger-Cox-Jepcoat modified double exponential Pseudo-Voigt convolution function for a CW powder peak in external Fortran routine - + param pos: peak position in degrees param sig: Gaussian variance in centideg^2 param alp: Rise exponential coefficient @@ -1352,7 +1354,7 @@ def getExpFCJVoigt3(pos,alp,bet,sig,gam,shl,xdata): param gam: Lorentzian width in centideg param shl: axial divergence parameter (S+H)/L param xdata: array; profile points for peak to be calculated; bounded by 20FWHM to 50FWHM (or vv) - + returns: array: calculated peak function at each xdata returns: integral of peak; nominally = 1.0 ''' @@ -1365,9 +1367,9 @@ def getExpFCJVoigt3(pos,alp,bet,sig,gam,shl,xdata): return 0.,1. def getdExpFCJVoigt3(pos,alp,bet,sig,gam,shl,xdata): - '''Compute analytic derivatives the Finger-Cox-Jepcoat modified double + '''Compute analytic derivatives the Finger-Cox-Jepcoat modified double exponential Pseudo-Voigt convolution function for a CW powder peak - + param pos: peak position in degrees param alp: Rise exponential coefficient param bet: Decay exponential coefficient @@ -1375,7 +1377,7 @@ def getdExpFCJVoigt3(pos,alp,bet,sig,gam,shl,xdata): param gam: Lorentzian width in centideg param shl: axial divergence parameter (S+H)/L param xdata: array; profile points for peak to be calculated; bounded by 20FWHM to 50FWHM (or vv) - + returns: arrays: function and derivatives of pos, alp, bet, sig, gam, & shl ''' Df,dFdp,dFda,dFdb,dFds,dFdg,dFdsh = pyd.pydpsvfcjexpo(len(xdata),xdata-pos,pos,alp,bet,sig,gam,shl) @@ -1384,16 +1386,16 @@ def getdExpFCJVoigt3(pos,alp,bet,sig,gam,shl,xdata): def getPsVoigt(pos,sig,gam,xdata): '''Compute the simple Pseudo-Voigt function for a small angle Bragg peak in external Fortran routine - + param pos: peak position in degrees param sig: Gaussian variance in centideg^2 param gam: Lorentzian width in centideg param xdata: array; profile points for peak to be calculated - + returns: array: calculated peak function at each xdata returns: integral of peak; nominally = 1.0 ''' - + cw = np.diff(xdata) cw = np.append(cw,cw[-1]) Df = pyd.pypsvoigt(len(xdata),xdata-pos,sig,gam) @@ -1402,16 +1404,16 @@ def getPsVoigt(pos,sig,gam,xdata): def getdPsVoigt(pos,sig,gam,xdata): '''Compute the simple Pseudo-Voigt function derivatives for a small angle Bragg peak peak in external Fortran routine - + param pos: peak position in degrees param sig: Gaussian variance in centideg^2 param gam: Lorentzian width in centideg param xdata: array; profile points for peak to be calculated returns: arrays: function and derivatives of pos, sig & gam - NB: the pos derivative has the opposite sign compared to that in other profile functions + NB: the pos derivative has the opposite sign compared to that in other profile functions ''' - + Df,dFdp,dFds,dFdg = pyd.pydpsvoigt(len(xdata),xdata-pos,sig,gam) return Df,dFdp,dFds,dFdg @@ -1419,27 +1421,27 @@ def getEpsVoigt(pos,alp,bet,sig,gam,xdata): '''Compute the double exponential Pseudo-Voigt convolution function for a neutron TOF & CW pink peak in external Fortran routine ''' - + cw = np.diff(xdata) cw = np.append(cw,cw[-1]) Df = pyd.pyepsvoigt(len(xdata),xdata-pos,alp,bet,sig,gam) - return Df,np.sum(Df*cw) - + return Df,np.sum(Df*cw) + def getdEpsVoigt(pos,alp,bet,sig,gam,xdata): '''Compute the double exponential Pseudo-Voigt convolution function derivatives for a neutron TOF & CW pink peak in external Fortran routine ''' - + Df,dFdp,dFda,dFdb,dFds,dFdg = pyd.pydepsvoigt(len(xdata),xdata-pos,alp,bet,sig,gam) - return Df,dFdp,dFda,dFdb,dFds,dFdg + return Df,dFdp,dFda,dFdb,dFds,dFdg def ellipseSize(H,Sij,GB): '''Implements r=1/sqrt(sum((1/S)*(q.v)^2) per note from Alexander Brady ''' - + HX = np.inner(H.T,GB) lenHX = np.sqrt(np.sum(HX**2)) - Esize,Rsize = nl.eigh(G2lat.U6toUij(Sij)) + Esize,Rsize = nl.eigh(G2lat.U6toUij(Sij)) R = np.inner(HX/lenHX,Rsize)**2*Esize #want column length for hkl in crystal lenR = 1./np.sqrt(np.sum(R)) return lenR @@ -1447,7 +1449,7 @@ def ellipseSize(H,Sij,GB): def ellipseSizeDerv(H,Sij,GB): '''Implements r=1/sqrt(sum((1/S)*(q.v)^2) derivative per note from Alexander Brady ''' - + lenR = ellipseSize(H,Sij,GB) delt = 0.001 dRdS = np.zeros(6) @@ -1476,7 +1478,7 @@ def getMustrain(HKL,G,SGData,muStrData): Strms = np.array(G2spc.MustrainCoeff(H,SGData)) Sum = np.sum(np.array(muStrData[4])[:,nxs]*Strms,axis=0) return np.sqrt(Sum)/rdsq - + def getCrSize(HKL,G,GB,sizeData): if sizeData[0] == 'isotropic': return np.ones(HKL.shape[1])*sizeData[1][0] @@ -1497,14 +1499,14 @@ def getHKLpeak(dmin,SGData,A,Inst=None,nodup=False): Generates allowed by symmetry reflections with d >= dmin NB: GenHKLf & checkMagextc return True for extinct reflections - :param dmin: minimum d-spacing + :param dmin: minimum d-spacing :param SGData: space group data obtained from SpcGroup :param A: lattice parameter terms A1-A6 :param Inst: instrument parameter info :returns: HKLs: np.array hkl, etc for allowed reflections ''' - HKL = G2lat.GenHLaue(dmin,SGData,A) + HKL = G2lat.GenHLaue(dmin,SGData,A) HKLs = [] ds = [] for h,k,l,d in HKL: @@ -1527,7 +1529,7 @@ def getHKLMpeak(dmin,Inst,SGData,SSGData,Vec,maxH,A): vec = np.array(Vec) vstar = np.sqrt(G2lat.calc_rDsq(vec,A)) #find extra needed for -n SS reflections dvec = 1./(maxH*vstar+1./dmin) - HKL = G2lat.GenHLaue(dvec,SGData,A) + HKL = G2lat.GenHLaue(dvec,SGData,A) SSdH = [vec*h for h in range(-maxH,maxH+1)] SSdH = dict(zip(range(-maxH,maxH+1),SSdH)) ifMag = False @@ -1545,16 +1547,16 @@ def getHKLMpeak(dmin,Inst,SGData,SSGData,Vec,maxH,A): if d >= dmin: HKLM = np.array([h,k,l,dH]) if G2spc.checkSSextc(HKLM,SSGData) or ifMag: - HKLs.append([h,k,l,dH,d,G2lat.Dsp2pos(Inst,d),-1]) + HKLs.append([h,k,l,dH,d,G2lat.Dsp2pos(Inst,d),-1]) return G2lat.sortHKLd(HKLs,True,True,True) peakInstPrmMode = True -'''Determines the mode used for peak fitting. When peakInstPrmMode=True peak -width parameters are computed from the instrument parameters (UVW,... or -alpha,... etc) unless the individual parameter is refined. This allows the +'''Determines the mode used for peak fitting. When peakInstPrmMode=True peak +width parameters are computed from the instrument parameters (UVW,... or +alpha,... etc) unless the individual parameter is refined. This allows the instrument parameters to be refined. When peakInstPrmMode=False, the instrument -parameters are not used and cannot be refined. -The default is peakFitMode=True. This is changed only in +parameters are not used and cannot be refined. +The default is peakFitMode=True. This is changed only in :func:`setPeakInstPrmMode`, which is called from :mod:`GSASIIscriptable` or GSASIIphsGUI.OnSetPeakWidMode ('Gen unvaried widths' menu item). ''' @@ -1562,13 +1564,13 @@ def getHKLMpeak(dmin,Inst,SGData,SSGData,Vec,maxH,A): def setPeakInstPrmMode(normal=True): '''Determines the mode used for peak fitting. If normal=True (default) peak width parameters are computed from the instrument parameters - unless the individual parameter is refined. If normal=False, - peak widths are used as supplied for each peak. + unless the individual parameter is refined. If normal=False, + peak widths are used as supplied for each peak. - Note that normal=True unless this routine is called. Also, - instrument parameters can only be refined with normal=True. + Note that normal=True unless this routine is called. Also, + instrument parameters can only be refined with normal=True. - :param bool normal: setting to apply to global variable + :param bool normal: setting to apply to global variable :data:`peakInstPrmMode` ''' global peakInstPrmMode @@ -1576,7 +1578,7 @@ def setPeakInstPrmMode(normal=True): def getPeakProfile(dataType,parmDict,xdata,fixback,varyList,bakType): '''Computes the profiles from multiple peaks for individual peak fitting - for powder patterns. + for powder patterns. NB: not used for Rietveld refinement ''' yb = getBackground('',parmDict,bakType,dataType,xdata,fixback)[0] @@ -1675,7 +1677,7 @@ def getPeakProfile(dataType,parmDict,xdata,fixback,varyList,bakType): Wd,fmin,fmax = getWidthsCW(pos,sig,gam,shl) elif 'B' in dataType: Wd,fmin,fmax = getWidthsCWB(pos,alp,bet,sig,gam) - else: #'A' + else: #'A' Wd,fmin,fmax = getWidthsCWA(pos,alp,bet,sig,gam,shl) iBeg = np.searchsorted(xdata,pos-fmin) iFin = np.searchsorted(xdata,pos+fmin) @@ -1739,14 +1741,14 @@ def getPeakProfile(dataType,parmDict,xdata,fixback,varyList,bakType): yc[iBeg:iFin] += intens*getPsVoigt(pos,sig*10.**4,gam*100.,xdata[iBeg:iFin])[0] iPeak += 1 except KeyError: #no more peaks to process - return yb+yc + return yb+yc else: Pdabc = parmDict['Pdabc'] difC = parmDict['difC'] iPeak = 0 while True: try: - pos = parmDict['pos'+str(iPeak)] + pos = parmDict['pos'+str(iPeak)] tof = pos-parmDict['Zero'] dsp = tof/difC intens = parmDict['int'+str(iPeak)] @@ -1798,12 +1800,12 @@ def getPeakProfile(dataType,parmDict,xdata,fixback,varyList,bakType): iPeak += 1 except KeyError: #no more peaks to process return yb+yc - + def getPeakProfileDerv(dataType,parmDict,xdata,fixback,varyList,bakType): '''Computes the profile derivatives for a powder pattern for single peak fitting - + return: np.array([dMdx1,dMdx2,...]) in same order as varylist = backVary,insVary,peakVary order - + NB: not used for Rietveld refinement ''' dMdv = np.zeros(shape=(len(varyList),len(xdata))) @@ -1885,7 +1887,7 @@ def getPeakProfileDerv(dataType,parmDict,xdata,fixback,varyList,bakType): Wd,fmin,fmax = getWidthsCW(pos,sig,gam,shl) elif 'B' in dataType: Wd,fmin,fmax = getWidthsCWB(pos,alp,bet,sig,gam) - else: #'A' + else: #'A' Wd,fmin,fmax = getWidthsCWA(pos,alp,bet,sig,gam,shl) iBeg = np.searchsorted(xdata,pos-fmin) iFin = max(iBeg+3,np.searchsorted(xdata,pos+fmin)) @@ -2030,15 +2032,15 @@ def getPeakProfileDerv(dataType,parmDict,xdata,fixback,varyList,bakType): dMdv[varyList.index('Z')] += dgdZ*dervDict['gam'] iPeak += 1 except KeyError: #no more peaks to process - break - + break + else: Pdabc = parmDict['Pdabc'] difC = parmDict['difC'] iPeak = 0 while True: try: - pos = parmDict['pos'+str(iPeak)] + pos = parmDict['pos'+str(iPeak)] tof = pos-parmDict['Zero'] dsp = tof/difC intens = parmDict['int'+str(iPeak)] @@ -2130,19 +2132,19 @@ def getPeakProfileDerv(dataType,parmDict,xdata,fixback,varyList,bakType): break if 'BF mult' in varyList: dMdv[varyList.index('BF mult')] = fixback - + return dMdv - + def Dict2Values(parmdict, varylist): - '''Use before call to leastsq to setup list of values for the parameters + '''Use before call to leastsq to setup list of values for the parameters in parmdict, as selected by key in varylist''' - return [parmdict[key] for key in varylist] - + return [parmdict[key] for key in varylist] + def Values2Dict(parmdict, varylist, values): - ''' Use after call to leastsq to update the parameter dictionary with + ''' Use after call to leastsq to update the parameter dictionary with values corresponding to keys in varylist''' parmdict.update(zip(varylist,values)) - + def SetBackgroundParms(Background): 'Loads background parameters into dicts/lists to create varylist & parmdict' if len(Background) == 1: # fix up old backgrounds @@ -2168,7 +2170,7 @@ def SetBackgroundParms(Background): if item[1]: debyeVary.append(item[0]) backDict.update(debyeDict) - backVary += debyeVary + backVary += debyeVary backDict['nPeaks'] = Debye['nPeaks'] peaksDict = {} @@ -2193,21 +2195,24 @@ def SetBackgroundParms(Background): def autoBkgCalc(bkgdict,ydata): '''Compute the autobackground using the selected pybaselines function - + :param dict bkgdict: background parameters :param np.array ydata: array of Y values :returns: points for background intensity at each Y position ''' - import pybaselines.whittaker + try: + import pybaselines.whittaker + except: + raise Exception("import of pybaselines failed. autobackground requires this") lamb = int(10**bkgdict['autoPrms']['logLam']) if bkgdict['autoPrms']['opt'] == 0: func = pybaselines.whittaker.arpls else: func = pybaselines.whittaker.iarpls return func(ydata, lam=lamb, max_iter=10)[0] - + def DoCalibInst(IndexPeaks,Inst,Sample): - + def SetInstParms(): dataType = Inst['Type'][0] insVary = [] @@ -2232,7 +2237,7 @@ def SetInstParms(): insVary.append('DisplaceY') instDict = dict(zip(insNames,insVals)) return dataType,instDict,insVary - + def GetInstParms(parmDict,varyList): for name in Inst: Inst[name][1] = parmDict[name] @@ -2241,8 +2246,8 @@ def GetInstParms(parmDict,varyList): try: Sample[name][0] = parmDict[name] except: - pass - + pass + def InstPrint(sigDict): print ('Instrument/Sample Parameters:') if 'C' in Inst['Type'][0] or 'B' in Inst['Type'][0]: @@ -2271,7 +2276,7 @@ def InstPrint(sigDict): print (ptlbls) print (ptstr) print (sigstr) - + def errPeakPos(values,peakDsp,peakPos,peakWt,dataType,parmDict,varyList): parmDict.update(zip(varyList,values)) calcPos = G2lat.getPeakPos(dataType,parmDict,peakDsp) @@ -2309,7 +2314,7 @@ def errPeakPos(values,peakDsp,peakPos,peakWt,dataType,parmDict,varyList): result = so.leastsq(errPeakPos,values,full_output=True,ftol=0.000001, args=(peakDsp,peakPos,peakWt,dataType,parmDict,varyList)) ncyc = int(result[2]['nfev']/2) - runtime = time.time()-begin + runtime = time.time()-begin chisq = np.sum(result[2]['fvec']**2) Values2Dict(parmDict, varyList, result[0]) GOF = chisq/(len(peakPos)-len(varyList)) #reduced chi^2 @@ -2324,14 +2329,14 @@ def errPeakPos(values,peakDsp,peakPos,peakWt,dataType,parmDict,varyList): except ValueError: #result[1] is None on singular matrix G2fil.G2Print ('**** Refinement failed - singular matrix ****') return False - + sigDict = dict(zip(varyList,sig)) GetInstParms(parmDict,varyList) InstPrint(sigDict) return True def getHeaderInfo(dataType): - '''Provide parameter name, label name and formatting information for the + '''Provide parameter name, label name and formatting information for the contents of the Peak Table and where used in DoPeakFit ''' names = ['pos','int'] @@ -2369,12 +2374,12 @@ def DoPeakFit(FitPgm,Peaks,Background,Limits,Inst,Inst2,data,fixback=None,prevVa :param str FitPgm: type of fit to perform. At present this is ignored. :param list Peaks: a list of peaks. Each peak entry is a list with paired values: - The number of pairs depends on the data type (see :func:`getHeaderInfo`). - For CW data there are + The number of pairs depends on the data type (see :func:`getHeaderInfo`). + For CW data there are four values each followed by a refine flag where the values are: position, intensity, sigma (Gaussian width) and gamma (Lorentzian width). From the Histogram/"Peak List" tree entry, dict item "peaks". For some types of fits, overall parameters are placed - in a dict entry. + in a dict entry. :param list Background: describes the background. List with two items. Item 0 specifies a background model and coefficients. Item 1 is a dict. From the Histogram/Background tree entry. @@ -2383,13 +2388,13 @@ def DoPeakFit(FitPgm,Peaks,Background,Limits,Inst,Inst2,data,fixback=None,prevVa :param dict Inst2: more Instrument parameters :param numpy.array data: a 5xn array. data[0] is the x-values, data[1] is the y-values, data[2] are weight values, data[3], [4] and [5] are - calc, background and difference intensities, respectively. + calc, background and difference intensities, respectively. :param array fixback: fixed background array; same size as data[0-5] :param list prevVaryList: Used in sequential refinements to override the variable list. Defaults as an empty list. :param bool oneCycle: True if only one cycle of fitting should be performed :param dict controls: a dict specifying two values, Ftol = controls['min dM/M'] - and derivType = controls['deriv type']. If None default values are used. + and derivType = controls['deriv type']. If None default values are used. :param float wtFactor: weight multiplier; = 1.0 by default :param wx.Dialog dlg: A dialog box that is updated with progress from the fit. Defaults to None, which means no updates are done. @@ -2428,7 +2433,7 @@ def GetBackgroundParms(parmList,Background): break if 'BF mult' in parmList: Background[1]['background PWDR'][1] = parmList['BF mult'] - + def BackgroundPrint(Background,sigDict): print ('Background coefficients for '+Background[0][0]+' function') ptfmt = "%12.5f" @@ -2486,7 +2491,7 @@ def BackgroundPrint(Background,sigDict): print (sigstr) if 'BF mult' in sigDict: print('Background file mult: %.3f(%d)'%(Background[1]['background PWDR'][1],int(1000*sigDict['BF mult']))) - + def SetInstParms(Inst): dataType = Inst['Type'][0] insVary = [] @@ -2502,7 +2507,7 @@ def SetInstParms(Inst): if 'SH/L' in instDict: instDict['SH/L'] = max(instDict['SH/L'],0.002) return dataType,instDict,insVary - + def GetPkInstParms(parmDict,Inst,varyList): for name in Inst: Inst[name][1] = parmDict[name] @@ -2531,7 +2536,7 @@ def GetPkInstParms(parmDict,Inst,varyList): iPeak += 1 except KeyError: break - + def InstPrint(Inst,sigDict): print ('Instrument Parameters:') ptfmt = "%12.6f" @@ -2573,19 +2578,19 @@ def SetPeaksParms(dataType,Peaks): if peak[off+2*j+1]: peakVary.append(parName) return peakDict,peakVary - + def GetPeaksParms(Inst,parmDict,Peaks,varyList): '''Put values into the Peaks list from the refinement results from inside the parmDict array ''' names,_,_ = getHeaderInfo(Inst['Type'][0]) off = 0 - if 'LF' in Inst['Type'][0]: + if 'LF' in Inst['Type'][0]: off = 2 if 'clat' in varyList: Peaks[-1]['clat'] = parmDict['clat'] names = names[:-1] # drop 2nd 2theta value - for i,peak in enumerate(Peaks): + for i,peak in enumerate(Peaks): if type(peak) is dict: continue parmDict['ttheta'+str(i)] = peak[-1] for i,peak in enumerate(Peaks): @@ -2629,7 +2634,7 @@ def GetPeaksParms(Inst,parmDict,Peaks,varyList): peak[2*j+off] = G2mth.getEDgam(parmDict,pos) else: #'C' & 'B' peak[2*j+off] = G2mth.getCWgam(parmDict,pos) - + def PeaksPrint(dataType,parmDict,sigDict,varyList,ptsperFW): if 'clat' in varyList: print('c = {:.6f} esd {:.6f}'.format(parmDict['clat'],sigDict['clat'])) @@ -2668,17 +2673,17 @@ def PeaksPrint(dataType,parmDict,sigDict,varyList,ptsperFW): ptstr += 10*' ' ptstr += '%8.1f'%(ptsperFW[i]) print ('%s'%(('Peak'+str(i+1)).center(8)),ptstr) - + def devPeakProfile(values,xdata,ydata,fixback, weights,dataType,parmdict,varylist,bakType,dlg): - '''Computes a matrix where each row is the derivative of the calc-obs + '''Computes a matrix where each row is the derivative of the calc-obs values (see :func:`errPeakProfile`) with respect to each parameter in backVary,insVary,peakVary. Used for peak fitting. ''' parmdict.update(zip(varylist,values)) return np.sqrt(weights)*getPeakProfileDerv(dataType,parmdict,xdata,fixback,varylist,bakType) - + def errPeakProfile(values,xdata,ydata,fixback,weights,dataType,parmdict,varylist,bakType,dlg): - '''Computes a vector with the weighted calc-obs values differences + '''Computes a vector with the weighted calc-obs values differences for peak fitting ''' parmdict.update(zip(varylist,values)) @@ -2762,7 +2767,7 @@ def errPeakProfile(values,xdata,ydata,fixback,weights,dataType,parmdict,varylist print('peak fit failure') return ncyc = int(result[2]['nfev']/2) - runtime = time.time()-begin + runtime = time.time()-begin chisq = np.sum(result[2]['fvec']**2) Values2Dict(parmDict, varyList, result[0]) Rvals['Rwp'] = np.sqrt(chisq/np.sum(wtFactor*w[xBeg:xFin]*y[xBeg:xFin]**2))*100. #to % @@ -2826,7 +2831,7 @@ def errPeakProfile(values,xdata,ydata,fixback,weights,dataType,parmdict,varylist G2fil.G2Print ('*** Warning: data binning yields too few data points across peak FWHM for reliable Rietveld refinement ***') G2fil.G2Print ('*** recommended is 6-10; you have %.2f ***'%(min(binsperFWHM))) return sigDict,result,sig,Rvals,varyList,parmDict,fullvaryList,badVary - + def calcIncident(Iparm,xdata): 'needs a doc string' @@ -2835,14 +2840,14 @@ def IfunAdv(Iparm,xdata): Icoef = Iparm['Icoeff'] DYI = np.ones((12,xdata.shape[0])) YI = np.ones_like(xdata)*Icoef[0] - + x = xdata/1000. #expressions are in ms if Itype == 'Exponential': for i in [1,3,5,7,9]: Eterm = np.exp(-Icoef[i+1]*x**((i+1)/2)) YI += Icoef[i]*Eterm DYI[i] *= Eterm - DYI[i+1] *= -Icoef[i]*Eterm*x**((i+1)/2) + DYI[i+1] *= -Icoef[i]*Eterm*x**((i+1)/2) elif 'Maxwell'in Itype: Eterm = np.exp(-Icoef[2]/x**2) DYI[1] = Eterm/x**5 @@ -2864,7 +2869,7 @@ def IfunAdv(Iparm,xdata): YI += Ccof[i]*Icoef[i+2] DYI[i+2] =Ccof[i] return YI,DYI - + Iesd = np.array(Iparm['Iesd']) Icovar = Iparm['Icovar'] YI,DYI = IfunAdv(Iparm,xdata) @@ -2907,7 +2912,7 @@ def MakeInst(PWDdata,Name,Size,Mustrain,useSamBrd): fl.write('%d\n'%int(inst[prms[0]][1])) fl.write('%19.11f%19.11f%19.11f%19.11f%19.11f\n'%(inst[prms[1]][1],inst[prms[2]][1],inst[prms[3]][1],inst[prms[4]][1],inst[prms[5]][1],)) fl.write('%12.6e%14.6e%14.6e%14.6e\n'%(inst[prms[6]][1],inst[prms[7]][1],inst[prms[8]][1],inst[prms[9]][1])) - fl.write('%12.6e%14.6e%14.6e%14.6e\n'%(inst[prms[10]][1],inst[prms[11]][1],inst[prms[12]][1],inst[prms[13]][1])) + fl.write('%12.6e%14.6e%14.6e%14.6e\n'%(inst[prms[10]][1],inst[prms[11]][1],inst[prms[12]][1],inst[prms[13]][1])) fl.write('%12.6e%14.6e%14.6e%14.6e%14.6e\n'%(inst[prms[14]][1],inst[prms[15]][1]+Ysb,inst[prms[16]][1]+Xsb,0.0,0.0)) fl.write('%12.6e\n\n\n'%(sample['Absorption'][0])) fl.close() @@ -2929,12 +2934,12 @@ def MakeInst(PWDdata,Name,Size,Mustrain,useSamBrd): fl.write('%d\n'%int(inst[prms[0]][1])) fl.write('%10.5f%10.5f%10.4f%10d\n'%(inst[prms[1]][1],100.*inst[prms[2]][1],inst[prms[3]][1],0)) fl.write('%10.3f%10.3f%10.3f\n'%(inst[prms[4]][1],inst[prms[5]][1],inst[prms[6]][1])) - fl.write('%10.3f%10.3f%10.3f\n'%(inst[prms[7]][1]+Xsb,inst[prms[8]][1]+Ysb,0.0)) + fl.write('%10.3f%10.3f%10.3f\n'%(inst[prms[7]][1]+Xsb,inst[prms[8]][1]+Ysb,0.0)) fl.write('%10.3f%10.3f%10.3f\n'%(0.0,0.0,0.0)) fl.write('%12.6e\n\n\n'%(sample['Absorption'][0])) fl.close() return fname - + def MakeBack(PWDdata,Name): Back = PWDdata['Background'][0] inst = PWDdata['Instrument Parameters'][0] @@ -2968,8 +2973,8 @@ def findDup(Atoms): Fracs[-1]+= [at2[6],] return Dup,Fracs -def MakeRMC6f(PWDdata,Name,Phase,RMCPdict): - +def MakeRMC6f(PWDdata,Name,Phase,RMCPdict): + Meta = RMCPdict['metadata'] Atseq = RMCPdict['atSeq'] Supercell = RMCPdict['SuperCell'] @@ -2992,7 +2997,7 @@ def MakeRMC6f(PWDdata,Name,Phase,RMCPdict): Natm = np.count_nonzero(Natm-1) Atoms = newPhase['Atoms'] reset = False - + if ifSfracs: Natm = np.core.defchararray.count(np.array(Atcodes),'+') #no. atoms in original unit cell Natm = np.count_nonzero(Natm-1) @@ -3025,7 +3030,7 @@ def MakeRMC6f(PWDdata,Name,Phase,RMCPdict): i += 1 else: Natoms = Atoms - + NAtype = np.zeros(len(Atseq)) for atom in Natoms: NAtype[Atseq.index(atom[1])] += 1 @@ -3047,7 +3052,7 @@ def MakeRMC6f(PWDdata,Name,Phase,RMCPdict): fl.write('Cell (Ang/deg): %12.6f%12.6f%12.6f%12.6f%12.6f%12.6f\n'%( Cell[0],Cell[1],Cell[2],Cell[3],Cell[4],Cell[5])) A,B = G2lat.cell2AB(Cell,True) - fl.write('Lattice vectors (Ang):\n') + fl.write('Lattice vectors (Ang):\n') for i in [0,1,2]: fl.write('%12.6f%12.6f%12.6f\n'%(A[i,0],A[i,1],A[i,2])) fl.write('Atoms (fractional coordinates):\n') @@ -3060,7 +3065,7 @@ def MakeRMC6f(PWDdata,Name,Phase,RMCPdict): cell = [0,0,0] if '+' in atcode[1]: cell = eval(atcode[1].split('+')[1]) - fl.write('%6d%4s [%s]%19.15f%19.15f%19.15f%6d%4d%4d%4d\n'%( + fl.write('%6d%4s [%s]%19.15f%19.15f%19.15f%6d%4d%4d%4d\n'%( nat,atom[1].strip(),atcode[0],atom[3],atom[4],atom[5],(iat)%Natm+1,cell[0],cell[1],cell[2])) fl.close() return fname,reset @@ -3086,7 +3091,7 @@ def MakeBragg(PWDdata,Name,Phase): else: fl.write('%12s%12s\n'%(' 2-theta, deg',' I(obs)')) for i in range(Ibeg,Ifin-1): - fl.write('%11.6f%15.2f\n'%(Data[0][i],Data[1][i])) + fl.write('%11.6f%15.2f\n'%(Data[0][i],Data[1][i])) fl.close() return fname @@ -3189,7 +3194,7 @@ def MakeRMCPdat(PWDdata,Name,Phase,RMCPdict): if len(RMCPdict['Potentials']['Stretch']): fl.write(' > STRETCH_SEARCH :: %.1f%%\n'%RMCPdict['Potentials']['Stretch search']) for bond in RMCPdict['Potentials']['Stretch']: - fl.write(' > STRETCH :: %s %s %.2f eV %.2f Ang\n'%(bond[0],bond[1],bond[3],bond[2])) + fl.write(' > STRETCH :: %s %s %.2f eV %.2f Ang\n'%(bond[0],bond[1],bond[3],bond[2])) if len(RMCPdict['Potentials']['Angles']): fl.write(' > ANGLE_SEARCH :: %.1f%%\n'%RMCPdict['Potentials']['Angle search']) for angle in RMCPdict['Potentials']['Angles']: @@ -3208,7 +3213,7 @@ def MakeRMCPdat(PWDdata,Name,Phase,RMCPdict): fl.write(' > OXID :: %s\n'%' '.join(oxid)) fl.write(' > RIJ :: %s\n'%' '.join(['%6.3f'%RMCPdict['BVS'][bvs][0] for bvs in RMCPdict['BVS']])) fl.write(' > BVAL :: %s\n'%' '.join(['%6.3f'%RMCPdict['BVS'][bvs][1] for bvs in RMCPdict['BVS']])) - fl.write(' > CUTOFF :: %s\n'%' '.join(['%6.3f'%RMCPdict['BVS'][bvs][2] for bvs in RMCPdict['BVS']])) + fl.write(' > CUTOFF :: %s\n'%' '.join(['%6.3f'%RMCPdict['BVS'][bvs][2] for bvs in RMCPdict['BVS']])) fl.write(' > SAVE :: 100000\n') fl.write(' > UPDATE :: 100000\n') if len(RMCPdict['Swaps']): @@ -3221,9 +3226,9 @@ def MakeRMCPdat(PWDdata,Name,Phase,RMCPdict): except ValueError: break fl.write(' > SWAP_ATOMS :: %d %d %.2f\n'%(at1,at2,swap[2])) - + if len(RMCPdict['FxCN']): - fl.write('FIXED_COORDINATION_CONSTRAINTS :: %d\n'%len(RMCPdict['FxCN'])) + fl.write('FIXED_COORDINATION_CONSTRAINTS :: %d\n'%len(RMCPdict['FxCN'])) for ifx,fxcn in enumerate(RMCPdict['FxCN']): try: at1 = Atseq.index(fxcn[0]) @@ -3362,7 +3367,7 @@ def MakeRMCPdat(PWDdata,Name,Phase,RMCPdict): # q = XY[0] # dq[1:] = np.diff(q) # dq[0] = dq[1] - + # for j in range(n): # for i in range(n): # b = abs(q[i]-q[j]) @@ -3372,7 +3377,7 @@ def MakeRMCPdat(PWDdata,Name,Phase,RMCPdict): # else: # snew[j] += q[i]*sold[i]*(np.sin(b*d)/b-np.sin(t*d)/t)*dq[i] # snew[j] /= np.pi*q[j] - + # snew[0] = snew[1] # return snew @@ -3395,13 +3400,13 @@ def MakeRMCPdat(PWDdata,Name,Phase,RMCPdict): def findfullrmc(): '''Find where fullrmc is installed. Tries the following: - + 1. Returns the Config var 'fullrmc_exec', if defined. If an executable is found at that location it is assumed to run and supply fullrmc 5.0+ 2. The path is checked for a fullrmc image as named by Bachir - :returns: the full path to a python executable that is assumed to + :returns: the full path to a python executable that is assumed to have fullrmc installed or None, if it was not found. ''' fullrmc_exe = GSASIIpath.GetConfigValue('fullrmc_exec') @@ -3425,10 +3430,10 @@ def findfullrmc(): return fullrmc_exe def fullrmcDownload(): - '''Downloads the fullrmc executable from Bachir's site to the current - GSAS-II binary directory. + '''Downloads the fullrmc executable from Bachir's site to the current + GSAS-II binary directory. - Does some error checking. + Does some error checking. ''' import os import requests @@ -3445,16 +3450,17 @@ def fullrmcDownload(): if 'aarch' in platform.machine() or 'arm' in platform.machine(): return "Sorry, fullrmc is only available for Intel-compatible machines." URL = "https://github.com/bachiraoun/fullrmc/raw/master/standalones/fullrmc500_3p8p5_Linux-4p19p121-linuxkit-x86_64-with-glibc2p29" - + GSASIIpath.SetBinaryPath() fil = os.path.join(GSASIIpath.binaryPath,os.path.split(URL)[1]) print('Starting installation of fullrmc\nDownloading from', 'https://github.com/bachiraoun/fullrmc/tree/master/standalones', '\nCreating '+fil, '\nThis may take a while...') - open(fil, "wb").write(requests.get(URL).content) + with open(fil, "wb") as fp: + fp.write(requests.get(URL).content) print('...Download completed') - if setXbit: + if setXbit: import stat os.chmod(fil, os.stat(fil).st_mode | stat.S_IEXEC) return '' @@ -3469,11 +3475,13 @@ def findPDFfit(): try: from diffpy.pdffit2 import PdfFit import diffpy + PdfFit + diffpy return sys.executable except Exception as msg: print('Error importing PDFfit2:\n',msg) return None - + def GetPDFfitAtomVar(Phase,RMCPdict): ''' Find dict of independent "@n" variables for PDFfit in atom constraints ''' @@ -3501,7 +3509,7 @@ def GetPDFfitAtomVar(Phase,RMCPdict): for name in list(AtomVar.keys()): #clear out unused parameters if name not in varnames: del AtomVar[name] - + def MakePDFfitAtomsFile(Phase,RMCPdict): '''Make the PDFfit atoms file ''' @@ -3529,7 +3537,7 @@ def MakePDFfitAtomsFile(Phase,RMCPdict): cell = General['Cell'][1:7] fatm.write('cell %10.6f,%10.6f,%10.6f,%10.6f,%10.6f,%10.6f\n'%( cell[0],cell[1],cell[2],cell[3],cell[4],cell[5])) - fatm.write('dcell '+5*' 0.000000,'+' 0.000000\n') + fatm.write('dcell '+5*' 0.000000,'+' 0.000000\n') Atoms = Phase['Atoms'] fatm.write('ncell %8d,%8d,%8d,%10d\n'%(1,1,1,len(Atoms))) fatm.write('atoms\n') @@ -3542,11 +3550,11 @@ def MakePDFfitAtomsFile(Phase,RMCPdict): fatm.write(' '+'%18.8f%18.8f%18.8f\n'%(atom[cia+5],atom[cia+6],atom[cia+7])) fatm.write(' '+'%18.8f%18.8f%18.8f\n'%(0.,0.,0.)) fatm.close() - + def MakePDFfitRunFile(Phase,RMCPdict): '''Make the PDFfit python run file ''' - + def GetCellConstr(SGData): if SGData['SGLaue'] in ['m3', 'm3m']: return [1,1,1,0,0,0] @@ -3565,7 +3573,7 @@ def GetCellConstr(SGData): return [1,2,3,0,0,4] else: return [1,2,3,4,5,6] - + General = Phase['General'] Cell = General['Cell'][1:7] rundata = '''#!/usr/bin/env python @@ -3623,7 +3631,7 @@ def GetCellConstr(SGData): rundata += 'pf.constrain(pf.spdiameter,"@%d")\n'%Np parms[Np] = RMCPdict['spdiameter'][0] parmNames[Np] = 'spdiameter' - + if RMCPdict['cellref']: cellconst = GetCellConstr(RMCPdict['SGData']) used = [] @@ -3635,7 +3643,7 @@ def GetCellConstr(SGData): parms[Np+cellconst[ic]] = Cell[ic] parmNames[Np+cellconst[ic]] = cellNames[ic] used.append(cellconst[ic]) -#Atom constraints here ------------------------------------------------------- +#Atom constraints here ------------------------------------------------------- AtomVar = RMCPdict['AtomVar'] used = [] for iat,atom in enumerate(RMCPdict['AtomConstr']): @@ -3659,24 +3667,24 @@ def GetCellConstr(SGData): parmNames[itnum] = names[it-2].split('.')[1] used.append(itnum) else: - uijs = ['pf.u11(%d)'%(iat+1),'pf.u22(%d)'%(iat+1),'pf.u33(%d)'%(iat+1)] + uijs = ['pf.u11(%d)'%(iat+1),'pf.u22(%d)'%(iat+1),'pf.u33(%d)'%(iat+1)] for i in range(3): rundata += 'pf.constrain(%s,"%s")\n'%(uijs[i],item) if itnum not in used: parms[itnum] = AtomVar['@%d'%itnum] parmNames[itnum] = uijs[i].split('.')[1] used.append(itnum) - + if 'sequential' in RMCPdict['refinement']: rundata += '#parameters here\n' RMCPdict['Parms'] = parms #{'n':val,...} RMCPdict['ParmNames'] = parmNames #{'n':name,...} - else: + else: # set parameter values for iprm in parms: rundata += 'pf.setpar(%d,%.6f)\n'%(iprm,parms[iprm]) - -# Save results --------------------------------------------------------------- + +# Save results --------------------------------------------------------------- rundata += 'pf.refine()\n' if 'sequential' in RMCPdict['refinement']: fName = 'Sequential_PDFfit' @@ -3685,19 +3693,19 @@ def GetCellConstr(SGData): else: fName = General['Name'].replace(' ','_')+'-PDFfit' rfile = open(fName+'.py','w') - Nd = 0 + Nd = 0 for file in RMCPdict['files']: if 'Select' in RMCPdict['files'][file][0]: #skip unselected continue Nd += 1 rundata += 'pf.save_pdf(%d, pathWrap("%s"))\n'%(Nd,fName+file[0]+'.fgr') - + rundata += 'pf.save_struct(1, pathWrap("%s"))\n'%(fName+'.rstr') rundata += 'pf.save_res(pathWrap("%s"))\n'%(fName+'.res') - + rfile.writelines(rundata) rfile.close() - + return fName+'.py' def GetSeqCell(SGData,parmDict): @@ -3726,11 +3734,11 @@ def GetSeqCell(SGData,parmDict): return G2lat.cell2A(cell) except KeyError: return None - + def UpdatePDFfit(Phase,RMCPdict): ''' Updates various PDFfit parameters held in GSAS-II ''' - + General = Phase['General'] if RMCPdict['refinement'] == 'normal': fName = General['Name']+'-PDFfit.rstr' @@ -3752,7 +3760,7 @@ def UpdatePDFfit(Phase,RMCPdict): RMCPdict['spdiameter'][0] = float(resdict['shape'].split()[-1]) else: RMCPdict['stepcut'][0] = float(resdict['shape'][-1]) - cx,ct,cs,ci = G2mth.getAtomPtrs(Phase) + cx,ct,cs,ci = G2mth.getAtomPtrs(Phase) Atoms = Phase['Atoms'] atmBeg = 0 for line in lines: @@ -3769,7 +3777,7 @@ def UpdatePDFfit(Phase,RMCPdict): atom[ci+5:ci+8] = [float(Uijstr[0]),float(Uijstr[1]),float(Uijstr[2])] atmBeg += 6 fName = General['Name']+'-PDFfit.res' - else: + else: fName = 'Sequential_PDFfit.res' try: res = open(fName.replace(' ','_'),'r') @@ -3791,7 +3799,7 @@ def UpdatePDFfit(Phase,RMCPdict): XNdata[dkey]['qbroad'][0] = float(line.split()[4]) if 'Scale' in line and '(' in line: XNdata[dkey]['dscale'][0] = float(line.split()[3]) - + for iline,line in enumerate(lines): if 'Refinement parameters' in line: Ibeg = True @@ -3838,13 +3846,13 @@ def MakefullrmcSupercell(Phase,RMCPdict): :param dict Phase: phase information from data tree :param dict RMCPdict: fullrmc parameters from GUI - :param list grpDict: a list of lists where the inner list - contains the atom numbers contained in each group. e.g. - [[0,1,2,3,4],[5,6],[4,6]] creates three groups with + :param list grpDict: a list of lists where the inner list + contains the atom numbers contained in each group. e.g. + [[0,1,2,3,4],[5,6],[4,6]] creates three groups with atoms 0-4 in the first atoms 5 & 6 in the second and - atoms 4 & 6 in the third. Note that it is fine that - atom 4 appears in two groups. + atoms 4 & 6 in the third. Note that it is fine that + atom 4 appears in two groups. ''' #for i in (0,1): grpDict[i].append(1) # debug: 1st & 2nd atoms in 2nd group cell = Phase['General']['Cell'][1:7] @@ -3872,8 +3880,8 @@ def MakefullrmcSupercell(Phase,RMCPdict): return atomlist,coordlist def MakefullrmcRun(pName,Phase,RMCPdict): - '''Creates a script to run fullrmc. Returns the name of the file that was - created. + '''Creates a script to run fullrmc. Returns the name of the file that was + created. ''' BondList = {} for k in RMCPdict['Pairs']: @@ -3938,7 +3946,7 @@ def writeHeader(ENGINE,statFP): statFP.write(', ') statFP.write('\\n') statFP.flush() - + def writeCurrentStatus(ENGINE,statFP,plotF): """line in stats file & current constraint plots""" statFP.write(str(ENGINE.generated)) @@ -4022,7 +4030,7 @@ def makepdb(atoms, coords, bbox=None): project = prefix + "-fullrmc" time0 = time.time() '''.format(RMCPdict['ReStart'][0],projDir,projName) - + rundata += '# setup structure\n' rundata += 'cell = ' + str(cell) + '\n' rundata += 'supercell = ' + str(RMCPdict['SuperCell']) + '\n' @@ -4060,7 +4068,7 @@ def makepdb(atoms, coords, bbox=None): projectPlots = os.path.join(dirName, project + '.plots') projectXYZ = os.path.join(dirName, project + '.atoms') pdbFile = os.path.join(dirName, project + '_restart.pdb') -# check Engine exists if so (and not FRESH_START) load it otherwise build it +# check Engine exists if so (and not FRESH_START) load it otherwise build it ENGINE = Engine(path=None) if not ENGINE.is_engine(engineFileName) or FRESH_START: ENGINE = Engine(path=engineFileName, freshStart=True) @@ -4102,7 +4110,7 @@ def makepdb(atoms, coords, bbox=None): unitcellBC = cell, supercell = supercell) ENGINE.set_groups_as_atoms() -''' +''' rundata += ' rho0 = len(ENGINE.allNames)/ENGINE.volume\n' rundata += '\n # "Constraints" (includes experimental data) setup\n' # settings that require a new Engine @@ -4180,7 +4188,7 @@ def makepdb(atoms, coords, bbox=None): if minDists: rundata += " D_CONSTRAINT.set_pairs_definition( {'inter':[" + minDists + "]})\n" rundata += ' ENGINE.add_constraints(D_CONSTRAINT)\n' - + if AngleList: rundata += ''' A_CONSTRAINT = BondsAngleConstraint() ENGINE.add_constraints(A_CONSTRAINT) @@ -4284,7 +4292,7 @@ def _constraint_copy_needs_lut(self, *args, **kwargs): # rundata += ' c.create_angles_by_definition(anglesDefinition={"%s":[\n'%Res # for torsion in RMCPdict['Torsions']: # rundata += ' %s\n'%str(tuple(torsion)) - # rundata += ' ]})\n' + # rundata += ' ]})\n' rundata += ''' if FRESH_START: # initialize engine with one step to get starting config energetics @@ -4300,7 +4308,7 @@ def _constraint_copy_needs_lut(self, *args, **kwargs): rundata += 'steps = {}\n'.format(RMCPdict['Steps/cycle']) rundata += 'for _ in range({}):\n'.format(RMCPdict['Cycles']) rundata += ' expected = ENGINE.generated+steps\n' - + rundata += ' ENGINE.run(restartPdb=pdbFile,numberOfSteps=steps, saveFrequency=steps)\n' rundata += ' writeCurrentStatus(ENGINE,statFP,projectPlots)\n' rundata += ' if ENGINE.generated != expected: break # run was stopped' @@ -4321,7 +4329,7 @@ def _constraint_copy_needs_lut(self, *args, **kwargs): rfile.writelines(rundata) rfile.close() return scrname - + def GetRMCBonds(general,RMCPdict,Atoms,bondList): bondDist = [] Cell = general['Cell'][1:7] @@ -4333,13 +4341,13 @@ def GetRMCBonds(general,RMCPdict,Atoms,bondList): Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) for bonds in bondList: Oxyz = np.array(Atoms[bonds[0]][1:]) - Txyz = np.array([Atoms[tgt-1][1:] for tgt in bonds[1]]) + Txyz = np.array([Atoms[tgt-1][1:] for tgt in bonds[1]]) Dx = np.array([Txyz-Oxyz+unit for unit in Units]) Dx = np.sqrt(np.sum(np.inner(Dx,Amat)**2,axis=2)) for dx in Dx.T: bondDist.append(np.min(dx)) return np.array(bondDist) - + def GetRMCAngles(general,RMCPdict,Atoms,angleList): bondAngles = [] Cell = general['Cell'][1:7] @@ -4351,7 +4359,7 @@ def GetRMCAngles(general,RMCPdict,Atoms,angleList): Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) for angle in angleList: Oxyz = np.array(Atoms[angle[0]][1:]) - TAxyz = np.array([Atoms[tgt-1][1:] for tgt in angle[1].T[0]]) + TAxyz = np.array([Atoms[tgt-1][1:] for tgt in angle[1].T[0]]) TBxyz = np.array([Atoms[tgt-1][1:] for tgt in angle[1].T[1]]) DAxV = np.inner(np.array([TAxyz-Oxyz+unit for unit in Units]),Amat) DAx = np.sqrt(np.sum(DAxV**2,axis=2)) @@ -4367,14 +4375,14 @@ def GetRMCAngles(general,RMCPdict,Atoms,angleList): def ISO2PDFfit(Phase): ''' Creates new phase structure to be used for PDFfit from an ISODISTORT mode displacement phase. - It builds the distortion mode parameters to be used as PDFfit variables for atom displacements from + It builds the distortion mode parameters to be used as PDFfit variables for atom displacements from the original parent positions as transformed to the child cell wiht symmetry defined from ISODISTORT. - + :param Phase: dict GSAS-II Phase structure; must contain ISODISTORT dict. NB: not accessed otherwise - + :returns: dict: GSAS-II Phase structure; will contain ['RMC']['PDFfit'] dict ''' - + Trans = np.eye(3) Uvec = np.zeros(3) Vvec = np.zeros(3) @@ -4412,7 +4420,7 @@ def ISO2PDFfit(Phase): pid = ISOdict['IsoVarList'].index(pname) consVec = ModeMatrix[pid] for ic,citm in enumerate(consVec): #NB: this assumes orthorhombic or lower symmetry - if opid < 0: + if opid < 0: citm *= -SGOps[100-opid%100-1][0][ip][ip] #remove centering, if any else: citm *= SGOps[opid%100-1][0][ip][ip] @@ -4442,14 +4450,14 @@ def GetAtmDispList(ISOdata): #### Reflectometry calculations ################################################################################ def REFDRefine(Profile,ProfDict,Inst,Limits,Substances,data): G2fil.G2Print ('fit REFD data by '+data['Minimizer']+' using %.2f%% data resolution'%(data['Resolution'][0])) - + class RandomDisplacementBounds(object): """random displacement with bounds""" def __init__(self, xmin, xmax, stepsize=0.5): self.xmin = xmin self.xmax = xmax self.stepsize = stepsize - + def __call__(self, x): """take a random step but ensure the new position is within the bounds""" while True: @@ -4459,7 +4467,7 @@ def __call__(self, x): if np.all(xnew < self.xmax) and np.all(xnew > self.xmin): break return xnew - + def GetModelParms(): parmDict = {} varyList = [] @@ -4494,12 +4502,12 @@ def GetModelParms(): parmDict[cid+'rho'] = Substances[name]['Scatt density'] parmDict[cid+'irho'] = Substances[name].get('XImag density',0.) return parmDict,varyList,values,bounds - + def SetModelParms(): line = ' Refined parameters: Histogram scale: %.4g'%(parmDict['Scale']) if 'Scale' in varyList: data['Scale'][0] = parmDict['Scale'] - line += ' esd: %.4g'%(sigDict['Scale']) + line += ' esd: %.4g'%(sigDict['Scale']) G2fil.G2Print (line) line = ' Flat background: %15.4g'%(parmDict['FltBack']) if 'FltBack' in varyList: @@ -4524,17 +4532,17 @@ def SetModelParms(): line += ' esd: %.3g'%(sigDict[cid+parm]) G2fil.G2Print (line) G2fil.G2Print (line2) - + def calcREFD(values,Q,Io,wt,Qsig,parmDict,varyList): parmDict.update(zip(varyList,values)) M = np.sqrt(wt)*(getREFD(Q,Qsig,parmDict)-Io) return M - + def sumREFD(values,Q,Io,wt,Qsig,parmDict,varyList): parmDict.update(zip(varyList,values)) M = np.sqrt(wt)*(getREFD(Q,Qsig,parmDict)-Io) return np.sum(M**2) - + def getREFD(Q,Qsig,parmDict): Ic = np.ones_like(Q)*parmDict['FltBack'] Scale = parmDict['Scale'] @@ -4564,7 +4572,7 @@ def getREFD(Q,Qsig,parmDict): AB = SmearAbeles(0.5*Q,Qsig,depth,rho,irho,sigma[1:]) Ic += AB*Scale return Ic - + def estimateT0(takestep): Mmax = -1.e-10 Mmin = 1.e10 @@ -4590,7 +4598,7 @@ def estimateT0(takestep): parmDict,varyList,values,bounds = GetModelParms() Msg = 'Failed to converge' if varyList: - if data['Minimizer'] == 'LMLS': + if data['Minimizer'] == 'LMLS': result = so.leastsq(calcREFD,values,full_output=True,epsfcn=1.e-8,ftol=1.e-6, args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],Qsig[Ibeg:Ifin],parmDict,varyList)) parmDict.update(zip(varyList,result[0])) @@ -4612,7 +4620,7 @@ def estimateT0(takestep): covM = [] elif data['Minimizer'] == 'MC/SA Anneal': xyrng = np.array(bounds).T - result = G2mth.anneal(sumREFD, values, + result = G2mth.anneal(sumREFD, values, args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],Qsig[Ibeg:Ifin],parmDict,varyList), schedule='log', full_output=True,maxeval=None, maxaccept=None, maxiter=10,dwell=1000, boltzmann=10.0, feps=1e-6,lower=xyrng[0], upper=xyrng[1], slope=0.9,ranStart=True, @@ -4675,9 +4683,9 @@ def estimateT0(takestep): except (ValueError,TypeError): #when bad LS refinement; covM missing or with nans G2fil.G2Print (Msg) return False,0,0,0,0,0,0,Msg - + def makeSLDprofile(data,Substances): - + sq2 = np.sqrt(2.) laySeq = ['0',]+data['Layer Seq'].split()+[str(len(data['Layers'])-1),] Nlayers = len(laySeq) @@ -4715,10 +4723,10 @@ def makeSLDprofile(data,Substances): iPos = np.searchsorted(x,interfaces[ilayer]) y[iBeg:] += (delt/2.)*sp.erfc((interfaces[ilayer]-x[iBeg:])/(sq2*sigma[ilayer+1])) iBeg = iPos - return x,xr,y + return x,xr,y def REFDModelFxn(Profile,Inst,Limits,Substances,data): - + Q,Io,wt,Ic,Ib,Qsig = Profile[:6] Qmin = Limits[1][0] Qmax = Limits[1][1] @@ -4763,11 +4771,11 @@ def abeles(kz, depth, rho, irho=0, sigma=0): """ Optical matrix form of the reflectivity calculation. O.S. Heavens, Optical Properties of Thin Solid Films - + Reflectometry as a function of kz for a set of slabs. :param kz: float[n] (1/Ang). Scattering vector, :math:`2\\pi\\sin(\\theta)/\\lambda`. - This is :math:`\\tfrac12 Q_z`. + This is :math:`\\tfrac12 Q_z`. :param depth: float[m] (Ang). thickness of each layer. The thickness of the incident medium and substrate are ignored. @@ -4785,13 +4793,13 @@ def abeles(kz, depth, rho, irho=0, sigma=0): """ def calc(kz, depth, rho, irho, sigma): if len(kz) == 0: return kz - + # Complex index of refraction is relative to the incident medium. # We can get the same effect using kz_rel^2 = kz^2 + 4*pi*rho_o # in place of kz^2, and ignoring rho_o kz_sq = kz**2 + 4e-6*np.pi*rho[:,0] k = kz - + # According to Heavens, the initial matrix should be [ 1 F; F 1], # which we do by setting B=I and M0 to [1 F; F 1]. An extra matrix # multiply versus some coding convenience. @@ -4824,7 +4832,7 @@ def calc(kz, depth, rho, irho, sigma): B12 = C1 B22 = C2 k = k_next - + r = B12/B11 return np.absolute(r)**2 @@ -4844,19 +4852,19 @@ def calc(kz, depth, rho, irho, sigma): irho = irho[None,:] return calc(kz, depth, rho, irho, sigma) - + def SmearAbeles(kz,dq, depth, rho, irho=0, sigma=0): y = abeles(kz, depth, rho, irho, sigma) s = dq/2. y += 0.1354*(abeles(kz+2*s, depth, rho, irho, sigma)+abeles(kz-2*s, depth, rho, irho, sigma)) - y += 0.24935*(abeles(kz-5*s/3., depth, rho, irho, sigma)+abeles(kz+5*s/3., depth, rho, irho, sigma)) - y += 0.4111*(abeles(kz-4*s/3., depth, rho, irho, sigma)+abeles(kz+4*s/3., depth, rho, irho, sigma)) + y += 0.24935*(abeles(kz-5*s/3., depth, rho, irho, sigma)+abeles(kz+5*s/3., depth, rho, irho, sigma)) + y += 0.4111*(abeles(kz-4*s/3., depth, rho, irho, sigma)+abeles(kz+4*s/3., depth, rho, irho, sigma)) y += 0.60653*(abeles(kz-s, depth, rho, irho, sigma) +abeles(kz+s, depth, rho, irho, sigma)) y += 0.80074*(abeles(kz-2*s/3., depth, rho, irho, sigma)+abeles(kz-2*s/3., depth, rho, irho, sigma)) y += 0.94596*(abeles(kz-s/3., depth, rho, irho, sigma)+abeles(kz-s/3., depth, rho, irho, sigma)) y *= 0.137023 return y - + def makeRefdFFT(Limits,Profile): G2fil.G2Print ('make fft') Q,Io = Profile[:2] @@ -4871,10 +4879,10 @@ def makeRefdFFT(Limits,Profile): F = fft.rfft(If) return R,F - + #### Stacking fault simulation codes ################################################################################ def GetStackParms(Layers): - + Parms = [] #cell parms if Layers['Laue'] in ['-3','-3m','4/m','4/mmm','6/m','6/mmm']: @@ -4897,7 +4905,7 @@ def GetStackParms(Layers): def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): '''Simulate powder or selected area diffraction pattern from stacking faults using DIFFaX - + :param dict Layers: dict with following items :: @@ -4905,17 +4913,17 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): {'Laue':'-1','Cell':[False,1.,1.,1.,90.,90.,90,1.], 'Width':[[10.,10.],[False,False]],'Toler':0.01,'AtInfo':{}, 'Layers':[],'Stacking':[],'Transitions':[]} - + :param str ctrls: controls string to be written on DIFFaX controls.dif file :param float scale: scale factor :param dict background: background parameters :param list limits: min/max 2-theta to be calculated :param dict inst: instrument parameters dictionary :param list profile: powder pattern data - - Note that parameters all updated in place + + Note that parameters all updated in place ''' - import atmdata + from . import atmdata path = sys.path for name in path: if 'bin' in name: @@ -4929,7 +4937,7 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): if 'H' not in atTypes: atTypes.insert(0,'H') for atType in atTypes: - if atType == 'H': + if atType == 'H': blen = -.3741 else: blen = Layers['AtInfo'][atType]['Isotopes']['Nat. Abund.']['SL'][0] @@ -4943,7 +4951,7 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): sf.close() #make DIFFaX control.dif file - future use GUI to set some of these flags cf = open('control.dif','w') - if ctrls == '0\n0\n3\n' or ctrls == '0\n1\n3\n': + if ctrls == '0\n0\n3\n' or ctrls == '0\n1\n3\n': x0 = profile[0] iBeg = np.searchsorted(x0,limits[0]) iFin = np.searchsorted(x0,limits[1])+1 @@ -4963,7 +4971,7 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): df.write('X-RAY\n') elif 'N' in inst['Type'][0]: df.write('NEUTRON\n') - if ctrls == '0\n0\n3\n' or ctrls == '0\n1\n3\n': + if ctrls == '0\n0\n3\n' or ctrls == '0\n1\n3\n': df.write('%.4f\n'%(G2mth.getMeanWave(inst))) U = ateln2*inst['U'][1]/10000. V = ateln2*inst['V'][1]/10000. @@ -4990,7 +4998,7 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): laue = '2/m(2)' if 'unknown' in Layers['Laue']: df.write('%s %.3f\n'%(laue,Layers['Toler'])) - else: + else: df.write('%s\n'%(laue)) df.write('%d\n'%(len(Layers['Layers']))) if Layers['Width'][0][0] < 1. or Layers['Width'][0][1] < 1.: @@ -5029,7 +5037,7 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): df.write('%s\n'%(Layers['Stacking'][2][iB:iF])) iB = iF else: - df.write('%s\n'%Layers['Stacking'][1]) + df.write('%s\n'%Layers['Stacking'][1]) df.write('TRANSITIONS\n') for iY in range(len(Layers['Layers'])): sumPx = 0. @@ -5044,8 +5052,8 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): os.remove('data.sfc') os.remove('control.dif') os.remove('GSASII-DIFFaX.dat') - return - df.close() + return + df.close() time0 = time.time() try: subp.call(DIFFaX) @@ -5057,7 +5065,7 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): iFin = iBeg+Xpat.shape[1] bakType,backDict,backVary = SetBackgroundParms(background) backDict['Lam1'] = G2mth.getWave(inst) - profile[4][iBeg:iFin] = getBackground('',backDict,bakType,inst['Type'][0],profile[0][iBeg:iFin])[0] + profile[4][iBeg:iFin] = getBackground('',backDict,bakType,inst['Type'][0],profile[0][iBeg:iFin])[0] profile[3][iBeg:iFin] = Xpat[-1]*scale+profile[4][iBeg:iFin] if not np.any(profile[1]): #fill dummy data x,y,w,yc,yb,yd rv = st.poisson(profile[3][iBeg:iFin]) @@ -5077,9 +5085,9 @@ def StackSim(Layers,ctrls,scale=0.,background={},limits=[],inst={},profile=[]): os.remove('data.sfc') os.remove('control.dif') os.remove('GSASII-DIFFaX.dat') - + def SetPWDRscan(inst,limits,profile): - + wave = G2mth.getMeanWave(inst) x0 = profile[0] iBeg = np.searchsorted(x0,limits[0]) @@ -5089,10 +5097,10 @@ def SetPWDRscan(inst,limits,profile): Dx = (x0[iFin]-x0[iBeg])/(iFin-iBeg) pyx.pygetinst(wave,x0[iBeg],x0[iFin],Dx) return iFin-iBeg - + def SetStackingSF(Layers,debug): # Load scattering factors into DIFFaX arrays - import atmdata + from . import atmdata atTypes = Layers['AtInfo'].keys() aTypes = [] for atype in atTypes: @@ -5107,12 +5115,12 @@ def SetStackingSF(Layers,debug): SFdat.append(SF) SFdat = np.array(SFdat) pyx.pyloadscf(len(atTypes),aTypes,SFdat.T,debug) - + def SetStackingClay(Layers,Type): # Controls rand.seed() ranSeed = rand.randint(1,2**16-1) - try: + try: laueId = ['-1','2/m(ab)','2/m(c)','mmm','-3','-3m','4/m','4/mmm', '6/m','6/mmm'].index(Layers['Laue'])+1 except ValueError: #for 'unknown' @@ -5143,7 +5151,7 @@ def SetStackingClay(Layers,Type): LaueSym = Layers['Laue'].ljust(12) pyx.pygetclay(controls,LaueSym,Wdth,Nstk,StkSeq) return laueId,controls - + def SetCellAtoms(Layers): Cell = Layers['Cell'][1:4]+Layers['Cell'][6:7] # atoms in layers @@ -5178,7 +5186,7 @@ def SetCellAtoms(Layers): Nlayers = len(layerNames) pyx.pycellayer(Cell,Natm,AtomTp,AtomXOU.T,Nuniq,LayerSymm,Nlayers,LayerNum) return Nlayers - + def SetStackingTrans(Layers,Nlayers): # Transitions TransX = [] @@ -5190,7 +5198,7 @@ def SetStackingTrans(Layers,Nlayers): TransX = np.array(TransX,dtype='float') # GSASIIpath.IPyBreak() pyx.pygettrans(Nlayers,TransP,TransX) - + def CalcStackingPWDR(Layers,scale,background,limits,inst,profile,debug): # Scattering factors SetStackingSF(Layers,debug) @@ -5198,7 +5206,7 @@ def CalcStackingPWDR(Layers,scale,background,limits,inst,profile,debug): laueId,controls = SetStackingClay(Layers,'PWDR') # cell & atoms Nlayers = SetCellAtoms(Layers) - Volume = Layers['Cell'][7] + Volume = Layers['Cell'][7] # Transitions SetStackingTrans(Layers,Nlayers) # PWDR scan @@ -5212,8 +5220,8 @@ def CalcStackingPWDR(Layers,scale,background,limits,inst,profile,debug): iFin = np.searchsorted(x0,limits[1])+1 if iFin-iBeg > 20000: iFin = iBeg+20000 - Nspec = 20001 - spec = np.zeros(Nspec,dtype='double') + Nspec = 20001 + spec = np.zeros(Nspec,dtype='double') time0 = time.time() pyx.pygetspc(controls,Nspec,spec) G2fil.G2Print (' GETSPC time = %.2fs'%(time.time()-time0)) @@ -5234,7 +5242,7 @@ def CalcStackingPWDR(Layers,scale,background,limits,inst,profile,debug): iFin = iBeg+Nsteps bakType,backDict,backVary = SetBackgroundParms(background) backDict['Lam1'] = G2mth.getWave(inst) - profile[4][iBeg:iFin] = getBackground('',backDict,bakType,inst['Type'][0],profile[0][iBeg:iFin])[0] + profile[4][iBeg:iFin] = getBackground('',backDict,bakType,inst['Type'][0],profile[0][iBeg:iFin])[0] profile[3][iBeg:iFin] = BrdSpec*scale+profile[4][iBeg:iFin] if not np.any(profile[1]): #fill dummy data x,y,w,yc,yb,yd try: @@ -5248,20 +5256,20 @@ def CalcStackingPWDR(Layers,scale,background,limits,inst,profile,debug): profile[2][iBeg:iFin] = np.where(profile[1][iBeg:iFin]>0.,1./profile[1][iBeg:iFin],1.0) profile[5][iBeg:iFin] = profile[1][iBeg:iFin]-profile[3][iBeg:iFin] G2fil.G2Print (' Broadening time = %.2fs'%(time.time()-time0)) - + def CalcStackingSADP(Layers,debug): - + # Scattering factors SetStackingSF(Layers,debug) # Controls & sequences laueId,controls = SetStackingClay(Layers,'SADP') # cell & atoms - Nlayers = SetCellAtoms(Layers) + Nlayers = SetCellAtoms(Layers) # Transitions SetStackingTrans(Layers,Nlayers) # result as Sadp - Nspec = 20001 - spec = np.zeros(Nspec,dtype='double') + Nspec = 20001 + spec = np.zeros(Nspec,dtype='double') time0 = time.time() hkLim,Incr,Nblk = pyx.pygetsadp(controls,Nspec,spec) Sapd = np.zeros((256,256)) @@ -5283,11 +5291,11 @@ def CalcStackingSADP(Layers,debug): iB += Nblk Layers['Sadp']['Img'] = Sapd G2fil.G2Print (' GETSAD time = %.2fs'%(time.time()-time0)) - + #### Maximum Entropy Method - Dysnomia ############################################################################### def makePRFfile(data,MEMtype): ''' makes Dysnomia .prf control file from Dysnomia GUI controls - + :param dict data: GSAS-II phase data :param int MEMtype: 1 for neutron data with negative scattering lengths 0 otherwise @@ -5343,7 +5351,7 @@ def makeMEMfile(data,reflData,MEMtype,DYSNOMIA): 0 otherwise :param str DYSNOMIA: path to dysnomia.exe ''' - + DysData = data['Dysnomia'] generalData = data['General'] cell = generalData['Cell'][1:7] @@ -5388,12 +5396,12 @@ def makeMEMfile(data,reflData,MEMtype,DYSNOMIA): mem.write('%10.3f%10.3f 0.001\n'%(sumpos,sumneg)) else: mem.write('%10.3f 0.001\n'%sumpos) - + dmin = DysData['MEMdmin'] TOFlam = 2.0*dmin*npsind(80.0) refSet = G2lat.GenHLaue(dmin,SGData,A) #list of h,k,l,d refDict = {'%d %d %d'%(ref[0],ref[1],ref[2]):ref for ref in refSet} - + refs = [] prevpos = 0. for ref in reflData: @@ -5416,7 +5424,7 @@ def makeMEMfile(data,reflData,MEMtype,DYSNOMIA): delt = pos-prevpos refs.append([h,k,l,mult,pos,FWHM,Fobs,phase,delt]) prevpos = pos - + ovlp = DysData['overlap'] refs1 = [] refs2 = [] @@ -5444,7 +5452,7 @@ def makeMEMfile(data,reflData,MEMtype,DYSNOMIA): refs2[nref2].append(refs[iref]) else: refs1.append(refs[iref]) - + mem.write('%5d\n'%len(refs1)) for ref in refs1: h,k,l = ref[:3] @@ -5495,7 +5503,7 @@ def MEMupdateReflData(prfName,data,reflData): :param str prfName: phase.mem file name :param list reflData: GSAS-II reflection data ''' - + generalData = data['General'] Map = generalData['Map'] Type = Map['Type'] @@ -5540,14 +5548,14 @@ def MEMupdateReflData(prfName,data,reflData): return True,newRefs #===Laue Fringe code =================================================================== -import NIST_profile as FP +from . import NIST_profile as FP class profileObj(FP.FP_profile): def conv_Lauefringe(self): """Compute the FT of the Laue Fringe function""" - + me=self.get_function_name() #the name of this convolver,as a string - wave = self.param_dicts['conv_global']['dominant_wavelength']*1.e10 # in A + wave = self.param_dicts['conv_global']['dominant_wavelength']*1.e10 # in A pos = np.rad2deg(self.param_dicts["conv_global"]["twotheta0"]) # peak position as 2theta in deg posQ = np.pi * 4 * np.sin(self.param_dicts["conv_global"]["twotheta0"]/2) / wave # peak position as Q ttwid = self.twotheta_window_fullwidth_deg @@ -5555,8 +5563,8 @@ def conv_Lauefringe(self): co2 = self.param_dicts[me]['clat'] / 2. dampM = self.param_dicts[me]['dampM'] dampP = self.param_dicts[me]['dampP'] - fpowM = self.param_dicts[me]['fitPowerM'] - fpowP = self.param_dicts[me]['fitPowerP'] + fpowM = self.param_dicts[me]['fitPowerM'] + fpowP = self.param_dicts[me]['fitPowerP'] ttlist = np.linspace(pos-ttwid/2,pos+ttwid/2,len(self._epsb2)) Qs = np.pi * 4 * np.sin(np.deg2rad(ttlist/2)) / wave w = np.exp(-1*10**((dampM) * np.abs(Qs - posQ)**fpowM)) @@ -5568,7 +5576,7 @@ def conv_Lauefringe(self): conv = FP.best_rfft(weqdiv) conv[1::2] *= -1 #flip center return conv - + def conv_Lorentzian(self): """Compute the FT of a Lorentz function where gamma is the FWHM""" ttwid = self.twotheta_window_fullwidth_deg @@ -5586,15 +5594,15 @@ def conv_Gaussian(self): ttwid = self.twotheta_window_fullwidth_deg me=self.get_function_name() #the name of this convolver,as a string g2sig2 = self.param_dicts[me]['g2sig2'] # gsas-ii sigma**2 in centidegr**2 - sigma = math.sqrt(g2sig2)/100. + sigma = math.sqrt(g2sig2)/100. ttlist = np.linspace(-ttwid/2,ttwid/2,len(self._epsb2)) eqdiv = np.exp(-0.5*ttlist**2/sigma**2) / math.sqrt(2*np.pi*sigma**2) conv = FP.best_rfft(eqdiv) conv[1::2] *= -1 #flip center return conv - + def LaueFringePeakCalc(ttArr,intArr,lam,peakpos,intens,sigma2,gamma,shol,ncells,clat,dampM,dampP,calcwid,fitPowerM=2,fitPowerP=2,plot=False): - '''Compute the peakshape for a Laue Fringe peak convoluted with a Gaussian, Lorentzian & + '''Compute the peakshape for a Laue Fringe peak convoluted with a Gaussian, Lorentzian & an axial divergence asymmetry correction. :param np.array ttArr: Array of two-theta values (in degrees) @@ -5609,7 +5617,7 @@ def LaueFringePeakCalc(ttArr,intArr,lam,peakpos,intens,sigma2,gamma,shol,ncells, :param float clat: c lattice parameter ** :param float dampM: :param float dampP: - :param float calcwid: two-theta (deg.) width for cutoff of peak computation. + :param float calcwid: two-theta (deg.) width for cutoff of peak computation. Defaults to 5 :param float fitPowerM: exponent used for damping fall-off on minus side of peak :param float fitPowerP: exponent used for damping fall-off on plus side of peak @@ -5666,9 +5674,9 @@ def LaueFringePeakCalc(ttArr,intArr,lam,peakpos,intens,sigma2,gamma,shol,ncells, }, "axial": { 'axDiv':"full", - 'slit_length_source' : 1e-3 * diffRadius * shol * axial_factor, + 'slit_length_source' : 1e-3 * diffRadius * shol * axial_factor, 'slit_length_target' : 1e-3 * diffRadius * shol * 1.00001 * axial_factor, # != 'slit_length_source' - 'length_sample' : 1e-3 * diffRadius * shol * axial_factor, + 'length_sample' : 1e-3 * diffRadius * shol * axial_factor, 'n_integral_points' : 10, 'angI_deg' : 2.5, 'angD_deg': 2.5, @@ -5681,7 +5689,7 @@ def LaueFringePeakCalc(ttArr,intArr,lam,peakpos,intens,sigma2,gamma,shol,ncells, NISTpk=profileObj(anglemode="twotheta", output_gaussian_smoother_bins_sigma=1.0, oversampling=NISTparms.get('oversampling',10)) - NISTpk.debug_cache=False + NISTpk.debug_cache=False for key in NISTparms: #set parameters for each convolver if key: NISTpk.set_parameters(convolver=key,**NISTparms[key]) @@ -5707,7 +5715,7 @@ def LaueFringePeakCalc(ttArr,intArr,lam,peakpos,intens,sigma2,gamma,shol,ncells, # else: # peakObj = NISTpk.compute_line_profile(convolver_names=convList) peakObj = NISTpk.compute_line_profile(convolver_names=convList) - + pkPts = len(peakObj.peak) pkMax = peakObj.peak.max() startInd = center_bin_idx-(pkPts//2) @@ -5734,14 +5742,14 @@ def LaueFringePeakCalc(ttArr,intArr,lam,peakpos,intens,sigma2,gamma,shol,ncells, intArr[istart:iend] += intens * peakObj.peak[pstart:pend]/pkMax # if plot: # LaueFringePeakPlot(ttArr[istart:iend], (intens * peakObj.peak[pstart:pend]/pkMax)) - + def LaueSatellite(peakpos,wave,c,ncell,j=[-4,-3,-2,-1,0,1,2,3,4]): '''Returns the locations of the Laue satellite positions relative to the peak position - + :param float peakpos: the peak position in degrees 2theta - :param float ncell: Laue fringe parameter, number of unit cells in layer - :param list j: the satellite order, where j=-1 is the first satellite + :param float ncell: Laue fringe parameter, number of unit cells in layer + :param list j: the satellite order, where j=-1 is the first satellite on the lower 2theta side and j=1 is the first satellite on the high 2theta side. j=0 gives the peak position ''' @@ -5755,7 +5763,7 @@ def SetDefaultSubstances(): 'unit scatter':{'Elements':None,'Volume':None,'Density':None,'Scatt density':1.0,'XImag density':1.0}}} def SetDefaultSASDModel(): - 'Fills in default items for the SASD Models dictionary' + 'Fills in default items for the SASD Models dictionary' return {'Back':[0.0,False], 'Size':{'MinDiam':50,'MaxDiam':10000,'Nbins':100,'logBins':True,'Method':'MaxEnt', 'Distribution':[],'Shape':['Spheroid',1.0], @@ -5763,25 +5771,25 @@ def SetDefaultSASDModel(): 'IPG':{'Niter':100,'Approach':0.8,'Power':-1},'Reg':{},}, 'Pair':{'Method':'Moore','MaxRadius':100.,'NBins':100,'Errors':'User', 'Percent error':2.5,'Background':[0,False],'Distribution':[], - 'Moore':10,'Dist G':100.,'Result':[],}, + 'Moore':10,'Dist G':100.,'Result':[],}, 'Particle':{'Matrix':{'Name':'vacuum','VolFrac':[0.0,False]},'Levels':[],}, 'Shapes':{'outName':'run','NumAA':100,'Niter':1,'AAscale':1.0,'Symm':1,'bias-z':0.0, 'inflateV':1.0,'AAglue':0.0,'pdbOut':False,'boxStep':4.0}, 'Current':'Size dist.','BackFile':'', } - + def SetDefaultREFDModel(): - '''Fills in default items for the REFD Models dictionary which are + '''Fills in default items for the REFD Models dictionary which are defined as follows for each layer: - + * Name: name of substance * Thick: thickness of layer in Angstroms (not present for top & bottom layers) * Rough: upper surface roughness for layer (not present for toplayer) * Penetration: mixing of layer substance into layer above-is this needed? * DenMul: multiplier for layer scattering density (default = 1.0) - + Top layer defaults to vacuum (or air/any gas); can be substituted for some other substance. - + Bottom layer default: infinitely thisck Silicon; can be substituted for some other substance. ''' return {'Layers':[{'Name':'vacuum','DenMul':[1.0,False],}, #top layer @@ -5833,19 +5841,19 @@ def TestData(): def test0(): if NeedTestData: TestData() gplot = plotter.add('FCJ-Voigt, 11BM').gca() - gplot.plot(xdata,getBackground('',parmDict0,bakType,'PXC',xdata)[0]) + gplot.plot(xdata,getBackground('',parmDict0,bakType,'PXC',xdata)[0]) gplot.plot(xdata,getPeakProfile(parmDict0,xdata,np.zeros_like(xdata),varyList,bakType)) fplot = plotter.add('FCJ-Voigt, Ka1+2').gca() - fplot.plot(xdata,getBackground('',parmDict1,bakType,'PXC',xdata)[0]) + fplot.plot(xdata,getBackground('',parmDict1,bakType,'PXC',xdata)[0]) fplot.plot(xdata,getPeakProfile(parmDict1,xdata,np.zeros_like(xdata),varyList,bakType)) - + def test1(): if NeedTestData: TestData() time0 = time.time() for i in range(100): getPeakProfile(parmDict1,xdata,np.zeros_like(xdata),varyList,bakType) G2fil.G2Print ('100+6*Ka1-2 peaks=1200 peaks %.2f'%time.time()-time0) - + def test2(name,delt): if NeedTestData: TestData() varyList = [name,] @@ -5856,7 +5864,7 @@ def test2(name,delt): parmDict2[name] += delt y1 = getPeakProfile(parmDict2,xdata,np.zeros_like(xdata),varyList,bakType) hplot.plot(xdata,(y1-y0)/delt,'r+') - + def test3(name,delt): if NeedTestData: TestData() names = ['pos','sig','gam','shl'] @@ -5872,7 +5880,7 @@ def test3(name,delt): hplot.plot(xdata,(y1-y0)/delt,'r+') if __name__ == '__main__': - import GSASIItestplot as plot + from . import GSASIItestplot as plot global plotter plotter = plot.PlotNotebook() # test0() @@ -5884,6 +5892,6 @@ def test3(name,delt): test3(name,shft) G2fil.G2Print ("OK") plotter.StartEventLoop() - + # GSASIIpath.SetBinaryPath(True,False) # print('found',findfullrmc()) diff --git a/GSASII/GSASIIpwdGUI.py b/GSASII/GSASIIpwdGUI.py index 5a3c7088d..9859e11e4 100644 --- a/GSASII/GSASIIpwdGUI.py +++ b/GSASII/GSASIIpwdGUI.py @@ -18,30 +18,30 @@ import math import copy import random as ran -if '2' in platform.python_version_tuple()[0]: - import cPickle -else: - import pickle as cPickle +import pickle import scipy.interpolate as si -import GSASIIpath -import GSASIImath as G2mth -import GSASIIpwd as G2pwd -import GSASIIfiles as G2fil -import GSASIIobj as G2obj -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIindex as G2indx -import GSASIIplot as G2plt -import GSASIIpwdplot as G2pwpl -import GSASIIdataGUI as G2gd -import GSASIIphsGUI as G2phsG -import GSASIIctrlGUI as G2G -import GSASIIElemGUI as G2elemGUI -import GSASIIElem as G2elem -import GSASIIsasd as G2sasd -import G2shapes -import SUBGROUPS as kSUB -import k_vector_search as kvs +from . import GSASIIpath +from . import GSASIImath as G2mth +from . import GSASIIpwd as G2pwd +from . import GSASIIfiles as G2fil +from . import GSASIIobj as G2obj +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIindex as G2indx +from . import GSASIIplot as G2plt +from . import GSASIIpwdplot as G2pwpl +from . import GSASIIdataGUI as G2gd +from . import GSASIIphsGUI as G2phsG +from . import GSASIIctrlGUI as G2G +from . import GSASIIElemGUI as G2elemGUI +from . import GSASIIElem as G2elem +from . import GSASIIsasd as G2sasd +from . import G2shapes +from . import SUBGROUPS as kSUB +from . import k_vector_search as kvs +from GSASII.imports.G2phase_CIF import CIFPhaseReader as CIFpr +from . import GSASIIscriptable as G2sc +from . import GSASIImiscGUI as G2IO try: VERY_LIGHT_GREY = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) WACV = wx.ALIGN_CENTER_VERTICAL @@ -87,7 +87,7 @@ 'P2/m','P1','C1'] def SetLattice(controls): - '''impose constraints on lattice constaints and determine the + '''impose constraints on lattice constaints and determine the Bravias lattice index (ibrav) as used in cellGUIlist ''' ibrav = bravaisSymb.index(controls[5]) @@ -122,11 +122,11 @@ def __init__(self,parent,title,controls,SGData,items,phaseDict,ifPick=False): self.phaseDict = phaseDict self.Pick = 0 self.ifPick = ifPick - + self.Draw() - + def Draw(self): - + def RefreshGrid(event): r,c = event.GetRow(),event.GetCol() br = self.items[r] @@ -182,7 +182,7 @@ def RefreshGrid(event): if c == 3: title = 'Conjugacy list for '+pname items = phase['altList'] - + elif c == 4: title = 'Super groups list list for '+pname items = phase['supList'] @@ -190,7 +190,7 @@ def RefreshGrid(event): wx.MessageBox(pname+' is a maximal subgroup',caption='Super group is parent',style=wx.ICON_INFORMATION) return SubCellsDialog(self.panel,title,self.controls,self.SGData,items,self.phaseDict).Show() - + if self.panel: self.panel.Destroy() self.panel = wx.Panel(self) rowLabels = [str(i+1) for i in range(len(self.items))] @@ -230,14 +230,14 @@ def RefreshGrid(event): magDisplay.Bind(wg.EVT_GRID_CELL_LEFT_CLICK,RefreshGrid) magDisplay.AutoSizeColumns(False) mainSizer.Add(magDisplay,0) - + OkBtn = wx.Button(self.panel,-1,"Ok") OkBtn.Bind(wx.EVT_BUTTON, self.OnOk) btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add((20,20),1) btnSizer.Add(OkBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() @@ -245,7 +245,7 @@ def RefreshGrid(event): def GetSelection(self): return self.Pick - + def OnOk(self,event): parent = self.GetParent() parent.Raise() @@ -258,17 +258,17 @@ def __init__(self,parent): pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE) self.panel = None self.result = {'UseObsCalc':'obs-calc','maxR':20.0,'Smooth':'linear'} - + self.Draw() - + def Draw(self): - + def OnUseOC(event): self.result['UseObsCalc'] = useOC.GetValue() - + def OnSmCombo(event): self.result['Smooth'] = smCombo.GetValue() - + if self.panel: self.panel.Destroy() self.panel = wx.Panel(self) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -305,12 +305,12 @@ def OnSmCombo(event): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) self.panel.SetSizer(mainSizer) self.panel.Fit() self.Fit() - + def GetSelection(self): return self.result @@ -323,8 +323,8 @@ def OnCancel(self,event): parent = self.GetParent() parent.Raise() self.EndModal(wx.ID_CANCEL) - - + + ################################################################################ ##### Setup routines ################################################################################ @@ -351,7 +351,7 @@ def GetFileBackground(G2frame,xye,background,scale=True): return bxye*mult else: return bxye - + def IsHistogramInAnyPhase(G2frame,histoName): '''Tests a Histogram to see if it is linked to any phases. Returns the name of the first phase where the histogram is used. @@ -370,7 +370,7 @@ def IsHistogramInAnyPhase(G2frame,histoName): return False def GetPhasesforHistogram(G2frame,histoName): - '''Returns phases (if any) associated with provided Histogram + '''Returns phases (if any) associated with provided Histogram Returns a list of phase dicts ''' phList = [] @@ -384,7 +384,7 @@ def SetupSampleLabels(histName,dataType,histType): '''Setup a list of labels and number formatting for use in labeling sample parameters. :param str histName: Name of histogram, ("PWDR ...") - :param str dataType: + :param str dataType: ''' parms = [] parms.append(['Scale','Histogram scale factor: ',[10,7]]) @@ -415,7 +415,7 @@ def SetupSampleLabels(histName,dataType,histType): parms.append(['Temperature','Sample temperature (K): ',[10,3]]) parms.append(['Pressure','Sample pressure (MPa): ',[10,3]]) return parms - + def GetFileList(G2frame,fileType): ''' Get list of file names containing a particular string param: fileType str: any string within a file name @@ -430,12 +430,12 @@ def GetFileList(G2frame,fileType): fileList.append(name) Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie) return fileList - + def GetHistsLikeSelected(G2frame): '''Get the histograms that match the current selected one: The histogram prefix and data type (PXC etc.), the number of - wavelengths and the instrument geometry (Debye-Scherrer etc.) - must all match. The current histogram is not included in the list. + wavelengths and the instrument geometry (Debye-Scherrer etc.) + must all match. The current histogram is not included in the list. :param wx.Frame G2frame: pointer to main GSAS-II data tree ''' @@ -453,7 +453,7 @@ def GetHistsLikeSelected(G2frame): hstName = G2frame.GPXtree.GetItemText(G2frame.PatternId) hPrefix = hstName.split()[0]+' ' # cycle through tree looking for items that match the above - item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) + item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) while item: name = G2frame.GPXtree.GetItemText(item) if name.startswith(hPrefix) and name != hstName: @@ -498,7 +498,7 @@ def SetCopyNames(histName,dataType,addNames=[]): if len(addNames): copyNames += addNames return histType,copyNames - + def CopyPlotCtrls(G2frame): '''Global copy: Copy plot controls from current histogram to others. ''' @@ -508,12 +508,12 @@ def CopyPlotCtrls(G2frame): G2frame.ErrorDialog('No match','No other histograms match '+hst,G2frame) return sourceData = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) - + if 'Offset' not in sourceData[0]: #patch for old data sourceData[0].update({'Offset':[0.0,0.0],'delOffset':0.02,'refOffset':-1.0, 'refDelt':0.01,}) G2frame.GPXtree.SetItemPyData(G2frame.PatternId,sourceData) - + dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy plot controls from\n'+str(hst[5:])+' to...', 'Copy plot controls', histList) results = [] @@ -523,7 +523,7 @@ def CopyPlotCtrls(G2frame): finally: dlg.Destroy() copyList = [] - for i in results: + for i in results: copyList.append(histList[i]) keys = ['Offset','delOffset','refOffset','refDelt'] @@ -551,7 +551,7 @@ def CopySelectedHistItems(G2frame): if dlg.ShowModal() == wx.ID_OK: choiceList = [choices[i] for i in dlg.GetSelections()] if not choiceList: return - + dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy parameters from\n'+str(hst[5:])+' to...', 'Copy parameters', histList) results = [] @@ -561,7 +561,7 @@ def CopySelectedHistItems(G2frame): finally: dlg.Destroy() copyList = [] - for i in results: + for i in results: copyList.append(histList[i]) if 'Limits' in choiceList: # Limits @@ -606,7 +606,7 @@ def CopySelectedHistItems(G2frame): G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,Id,'Sample Parameters') ).update(copy.deepcopy(copyDict)) - + def TestMagAtoms(phase,magAtms,SGData,Uvec,Trans,allmom,maxequiv=100,maximal=False): ''' Tests substructure magnetic atoms for magnetic site symmetry param: phase GSAS-II phase object @@ -638,7 +638,7 @@ def TestMagAtoms(phase,magAtms,SGData,Uvec,Trans,allmom,maxequiv=100,maximal=Fal Phase['Atoms'].append(matm[:2]+list(xyz)) SytSym,Mul,Nop,dupDir = G2spc.SytSym(xyz,phase['SGData']) CSI = G2spc.GetCSpqinel(phase['SGData']['SpnFlp'],dupDir) - if any(CSI[0]): + if any(CSI[0]): anymom = True if allmom: if not any(CSI[0]): @@ -699,10 +699,10 @@ def cellPrint(ibrav,A): else: print (" %s%10.6f %s%10.6f %s%10.6f" % ('a =',cell[0],'b =',cell[1],'c =',cell[2])) print (" %s%8.3f %s%8.3f %s%8.3f %s%12.3f" % ('alpha =',cell[3],'beta =',cell[4],'gamma =',cell[5],' volume =',Vol)) - + def vecPrint(Vec): print (' %s %10.5f %10.5f %10.5f'%('Modulation vector:',Vec[0],Vec[1],Vec[2])) - + Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Instrument Parameters'))[0] Limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Limits'))[1] if 'T' in Inst['Type'][0]: @@ -738,7 +738,7 @@ def vecPrint(Vec): peaks = [G2indx.IndexPeaks(peaks[0],G2frame.HKL)[1],peaks[1]] #put peak fit esds back in peaks Lhkl,M20,X20,Aref = G2indx.refinePeaksE(peaks[0],TTh,ibrav,A) Zero = 0.0 - else: + else: if ssopt.get('Use',False): vecFlags = [True if x in ssopt['ssSymb'] else False for x in ['a','b','g']] SSGData = G2spc.SSpcGroup(SGData,ssopt['ssSymb'])[1] @@ -809,7 +809,7 @@ def OnAutoSearch(event): mags = ymask[indx] poss = x[indx] refs = list(zip(poss,mags)) - if 'T' in Inst['Type'][0]: + if 'T' in Inst['Type'][0]: refs = G2mth.sortArray(refs,0,reverse=True) #big TOFs first else: #'C', 'E' or 'B' refs = G2mth.sortArray(refs,0,reverse=False) #small 2-Thetas or energies first @@ -817,7 +817,7 @@ def OnAutoSearch(event): for ref2 in refs[i+1:]: if abs(ref2[0]-ref1[0]) < 2.*G2pwd.getFWHM(ref1[0],inst): del(refs[i]) - if 'T' in Inst['Type'][0]: + if 'T' in Inst['Type'][0]: refs = G2mth.sortArray(refs,1,reverse=False) else: #'C', 'E' or 'B' refs = G2mth.sortArray(refs,1,reverse=True) @@ -825,7 +825,7 @@ def OnAutoSearch(event): data['peaks'].append(G2mth.setPeakparms(inst,inst2,pos,mag)) UpdatePeakGrid(G2frame,data) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnCopyPeaks(event): 'Copy peaks to other histograms' hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) @@ -846,11 +846,11 @@ def OnCopyPeaks(event): Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) G2frame.GPXtree.SetItemPyData( G2gd.GetGPXtreeItemId(G2frame,Id,'Peak List'),copy.deepcopy(data)) - + def OnLoadPeaks(event): 'Load peak list from file' pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II PWDR peaks list file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II PWDR peaks list file', pth, '', 'PWDR peak list files (*.pkslst)|*.pkslst',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -873,11 +873,11 @@ def OnLoadPeaks(event): data = {'peaks':peaks,'sigDict':{}} UpdatePeakGrid(G2frame,data) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnSavePeaks(event): 'Save peak to file suitable for OnLoadPeaks' pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II PWDR peaks list file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II PWDR peaks list file', pth, '', 'PWDR peak list files (*.pkslst)|*.pkslst',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -894,14 +894,14 @@ def OnSavePeaks(event): print ('PWDR peaks list saved to: '+filename) finally: dlg.Destroy() - + def OnUnDo(event): 'Undo a peak fit - reads a saved file from PeakFit' file = open(G2frame.undofile,'rb') PatternId = G2frame.PatternId for item in ['Background','Instrument Parameters','Peak List']: Id = G2gd.GetGPXtreeItemId(G2frame,PatternId, item) - oldvals = cPickle.load(file) + oldvals = pickle.load(file) G2frame.GPXtree.SetItemPyData(Id,oldvals) if item == 'Peak List': data.update(G2frame.GPXtree.GetItemPyData(Id)) @@ -910,17 +910,17 @@ def OnUnDo(event): G2frame.dataWindow.UnDo.Enable(False) wx.CallAfter(UpdatePeakGrid,G2frame,data) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def SaveState(): 'Saves result of a peaak fit for possible UnDo' G2frame.undofile = os.path.join(G2frame.dirname,'GSASII.save') file = open(G2frame.undofile,'wb') PatternId = G2frame.PatternId for item in ['Background','Instrument Parameters','Peak List']: - cPickle.dump(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId,item)),file,1) + pickle.dump(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId,item)),file,1) file.close() G2frame.dataWindow.UnDo.Enable(True) - + def OnLSQPeakFit(event): 'Do a peak fit' if reflGrid.IsCellEditControlEnabled(): # complete any grid edits in progress @@ -929,14 +929,14 @@ def OnLSQPeakFit(event): if not G2frame.GSASprojectfile: #force a save of the gpx file so SaveState can write in the same directory G2frame.OnFileSaveas(event) wx.CallAfter(OnPeakFit) - + def OnOneCycle(event): 'Do a single cycle of peak fit' if reflGrid.IsCellEditControlEnabled(): # complete any grid edits in progress reflGrid.HideCellEditControl() reflGrid.DisableCellEditControl() wx.CallAfter(OnPeakFit,oneCycle=True) - + def OnSeqPeakFit(event): '''Do a sequential peak fit across multiple histograms - peaks must be present in all. results saved in Sequential peak fit results''' @@ -959,7 +959,7 @@ def OnSeqPeakFit(event): Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential peak fit results') SeqResult = {'SeqPseudoVars':{},'SeqParFitEqList':[]} SeqResult['histNames'] = names - dlg = wx.ProgressDialog('Sequential peak fit','Data set name = '+names[0],len(names), + dlg = wx.ProgressDialog('Sequential peak fit','Data set name = '+names[0],len(names), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT) controls = {'deriv type':'analytic','min dM/M':0.001,} print ('Peak Fitting with '+controls['deriv type']+' derivatives:') @@ -1006,7 +1006,7 @@ def OnSeqPeakFit(event): G2frame.GPXtree.SetItemPyData(Id,SeqResult) G2frame.G2plotNB.Delete('Sequential refinement') #clear away probably invalid plot G2frame.GPXtree.SelectItem(Id) - + def OnDelPeaks(event): 'Delete selected peaks from the Peak fit table' if G2frame.dataWindow.XtraPeakMode.IsChecked(): # which table? @@ -1027,7 +1027,7 @@ def OnDelPeaks(event): del tbl[i] UpdatePeakGrid(G2frame,data) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnClearPeaks(event): 'Clear the Peak fit table' dlg = wx.MessageDialog(G2frame,'Delete all peaks?','Clear peak list',wx.OK|wx.CANCEL) @@ -1038,7 +1038,7 @@ def OnClearPeaks(event): dlg.Destroy() UpdatePeakGrid(G2frame,peaks) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnPeakFit(oneCycle=False,noFit=False): 'Do peak fitting by least squares' SaveState() @@ -1075,11 +1075,11 @@ def OnPeakFit(oneCycle=False,noFit=False): pks = list(range(-lines,0)) + list(range(1,lines+1)) peaks['LaueFringe']['satellites'].extend( G2pwd.LaueSatellite(i[0],wave,peaks['LaueFringe']['clat'],peaks['LaueFringe']['ncell'],pks)) - + if G2frame.dataWindow.XtraPeakMode.IsChecked(): # adding peaks to computed pattern histoName = G2frame.GPXtree.GetItemText(G2frame.PatternId) # do zero cycle refinement - import GSASIIstrMain as G2stMn + from . import GSASIIstrMain as G2stMn # recompute current pattern for current histogram, set as fixed background bxye = G2stMn.DoNoFit(G2frame.GSASprojectfile,histoName) peaksplus = peaks['xtraPeaks'] + [{}] @@ -1113,7 +1113,7 @@ def OnPeakFit(oneCycle=False,noFit=False): G2frame.AddToNotebook(text,'PF') G2pwpl.PlotPatterns(G2frame,plotType='PWDR') wx.CallAfter(UpdatePeakGrid,G2frame,newpeaks) - + def OnResetSigGam(event): 'Reset sig & gam values to instrument parameter values' PatternId = G2frame.PatternId @@ -1127,7 +1127,7 @@ def OnResetSigGam(event): newpeaks['peaks'].append(G2mth.setPeakparms(Inst,Inst2,peak[0],peak[2])) G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Peak List'),newpeaks) UpdatePeakGrid(G2frame,newpeaks) - + def setBackgroundColors(): 'Set background colors in peak list table; red if negative (nonsense), white if ok' for r in range(reflGrid.GetNumberRows()): @@ -1140,7 +1140,7 @@ def setBackgroundColors(): reflGrid.SetCellBackgroundColour(r,c,wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) except: pass - + def KeyEditPeakGrid(event): '''Respond to pressing a key to act on selection of a row, column or cell in the Peak List table @@ -1166,7 +1166,7 @@ def KeyEditPeakGrid(event): G2frame.PeakTable.DeleteRow(row) nDel += 1 if nDel: - msg = wg.GridTableMessage(G2frame.PeakTable, + msg = wg.GridTableMessage(G2frame.PeakTable, wg.GRIDTABLE_NOTIFY_ROWS_DELETED,0,nDel) reflGrid.ProcessTableMessage(msg) data['peaks'] = G2frame.PeakTable.GetData()[:-nDel] @@ -1195,7 +1195,7 @@ def KeyEditPeakGrid(event): return G2pwpl.PlotPatterns(G2frame,plotType='PWDR') wx.CallAfter(UpdatePeakGrid,G2frame,data) - + def SelectVars(rows): '''Set or clear peak refinement variables for peaks listed in rows ''' @@ -1216,7 +1216,7 @@ def SelectVars(rows): for lbl,c in refOpts.items(): tbl[r][c] = lbl in sels UpdatePeakGrid(G2frame,data) - + def OnRefineSelected(event): '''set refinement flags for the selected peaks ''' @@ -1245,9 +1245,9 @@ def onCellListDClick(event): r,c = event.GetRow(),event.GetCol() if r < 0 and c < 0: for row in range(reflGrid.GetNumberRows()): - reflGrid.SelectRow(row,True) + reflGrid.SelectRow(row,True) for col in range(reflGrid.GetNumberCols()): - reflGrid.SelectCol(col,True) + reflGrid.SelectCol(col,True) elif r >= 0 and c < 0: #row label: select it and replot! reflGrid.ClearSelection() reflGrid.SelectRow(r,True) @@ -1284,7 +1284,7 @@ def ToggleXtraMode(event): G2frame.dataWindow.XtraPeakMode.Check( not G2frame.dataWindow.XtraPeakMode.IsChecked()) OnXtraMode(event) - + def OnXtraMode(event): '''Respond to change in "Extra Peak" mode from menu command or button via :func:`ToggleXtraMode`. @@ -1292,9 +1292,9 @@ def OnXtraMode(event): data['xtraMode'] = G2frame.dataWindow.XtraPeakMode.IsChecked() wx.CallAfter(UpdatePeakGrid,G2frame,data) wx.CallAfter(G2pwpl.PlotPatterns,G2frame,plotType='PWDR') - + def OnSetPeakWidMode(event): - '''Toggle G2pwd.peakInstPrmMode mode; determines if unvaried + '''Toggle G2pwd.peakInstPrmMode mode; determines if unvaried sigma and gamma values are set from UVW & XY ''' state = G2frame.dataWindow.setPeakMode.IsChecked() @@ -1403,7 +1403,7 @@ def ShiftLFc(event): reflGrid = G2G.GSGrid(parent=G2frame.dataWindow) reflGrid.SetRowLabelSize(45) reflGrid.SetTable(kPeakTable, True) - setBackgroundColors() + setBackgroundColors() reflGrid.Bind(wg.EVT_GRID_CELL_CHANGED, RefreshPeakGrid) reflGrid.Bind(wx.EVT_KEY_DOWN, KeyEditPeakGrid) reflGrid.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, onCellListDClick) @@ -1428,7 +1428,7 @@ def ShiftLFc(event): colLabels.append('refine') Types.append(wg.GRID_VALUE_BOOL) # sort peaks & save into tree - T = [] + T = [] for peak in data['peaks']: T.append(peak[0]) D = dict(zip(T,data['peaks'])) @@ -1444,14 +1444,14 @@ def ShiftLFc(event): if 'LF' in Inst['Type'][0]: data['LFpeaks'] = [] for i in range(len(data['peaks'])): - if len(data['peaks'][i]) == 8: # need to extend the entry + if len(data['peaks'][i]) == 8: # need to extend the entry if 'Lam' in Inst: lam = Inst['Lam'][0] else: - lam = (Inst['Lam1'][0] + + lam = (Inst['Lam1'][0] + Inst['I(L2)/I(L1)'][0] * Inst['Lam2'][0]) / ( 1 + Inst['I(L2)/I(L1)'][0]) - if i == 0: + if i == 0: pos = data['peaks'][i][0] data['LaueFringe']['clat'] = data['LaueFringe']['lmin'] * 0.5 * lam / np.sin(pos*np.pi/360) l = data['LaueFringe']['lmin'] @@ -1472,7 +1472,7 @@ def ShiftLFc(event): reflGrid = G2G.GSGrid(parent=G2frame.dataWindow) reflGrid.SetRowLabelSize(45) reflGrid.SetTable(G2frame.PeakTable, True) - setBackgroundColors() + setBackgroundColors() reflGrid.Bind(wg.EVT_GRID_CELL_CHANGED, RefreshPeakGrid) reflGrid.Bind(wx.EVT_KEY_DOWN, KeyEditPeakGrid) reflGrid.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, onCellListDClick) @@ -1562,7 +1562,7 @@ def ShiftLFc(event): mainSizer.Add(reflGrid,1,wx.EXPAND,1) G2frame.dataWindow.SetDataSize() #RefreshPeakGrid(None) - + ################################################################################ ##### Background ################################################################################ @@ -1593,7 +1593,7 @@ def OnBackFlagCopy(event): copyList = [] try: if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): copyList.append(histList[i]) finally: dlg.Destroy() @@ -1612,7 +1612,7 @@ def OnBackFlagCopy(event): backData[1]['background PWDR'][2] = FBflag except: backData[1]['background PWDR'] = ['',-1.,False] - + def OnBackCopy(event): 'Copy background functions/values to other similar histograms' hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) @@ -1634,11 +1634,11 @@ def OnBackCopy(event): G2frame.GPXtree.SetItemPyData( G2gd.GetGPXtreeItemId(G2frame,Id,'Background'),copy.deepcopy(data)) CalcBack(Id) - + def OnBackSave(event): 'Save background values to file' pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Set name to save GSAS-II background parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Set name to save GSAS-II background parameters file', pth, '', 'background parameter files (*.pwdrbck)|*.pwdrbck',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -1647,7 +1647,13 @@ def OnBackSave(event): filename = os.path.splitext(filename)[0]+'.pwdrbck' File = open(filename,'w') File.write("#GSAS-II background parameter file; do not add/delete items!\n") - File.write(str(data[0])+'\n') + out_tmp = list() + for item in data[0]: + if isinstance(item, np.float64) or isinstance(item, float): + out_tmp.append(float(item)) + else: + out_tmp.append(item) + File.write(str(out_tmp)+'\n') for item in data[1]: if item in ['nPeaks','background PWDR','nDebye'] or not len(data[1][item]): File.write(item+':'+str(data[1][item])+'\n') @@ -1659,12 +1665,12 @@ def OnBackSave(event): print ('Background parameters saved to: '+filename) finally: dlg.Destroy() - + def OnBackLoad(event): 'load background values from file' pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II background parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II background parameters file', pth, '', 'background parameter files (*.pwdrbck)|*.pwdrbck',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -1675,7 +1681,7 @@ def OnBackLoad(event): if S[0] == '#': #skip the heading S = File.readline() #should contain the std. bck fxn newback[0] = eval(S.strip()) - S = File.readline() + S = File.readline() while S and ':' in S: item,vals = S.strip().split(':') if item in ['nPeaks','nDebye']: @@ -1690,7 +1696,7 @@ def OnBackLoad(event): S = File.readline() else: continue - S = File.readline() + S = File.readline() File.close() G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId,'Background'),newback) finally: @@ -1701,7 +1707,7 @@ def OnBackLoad(event): def OnBkgFit(event): 'Fit background functions to fixed set of background points' - + def SetInstParms(Inst): dataType = Inst['Type'][0] insVary = [] @@ -1721,8 +1727,8 @@ def SetInstParms(Inst): if 'SH/L' in instDict: instDict['SH/L'] = max(instDict['SH/L'],0.002) return dataType,instDict,insVary - - PatternId = G2frame.PatternId + + PatternId = G2frame.PatternId controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) background = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Background')) limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Limits'))[1] @@ -1735,7 +1741,7 @@ def SetInstParms(Inst): print (msg) G2frame.ErrorDialog('No points',msg) return - background[1]['FixedPoints'] = sorted(background[1]['FixedPoints'],key=lambda pair:pair[0]) + background[1]['FixedPoints'] = sorted(background[1]['FixedPoints'],key=lambda pair:pair[0]) X = [x for x,y in background[1]['FixedPoints']] Y = [y for x,y in background[1]['FixedPoints']] if X[0] > limits[0]: @@ -1768,7 +1774,7 @@ def SetInstParms(Inst): print (msg) G2frame.ErrorDialog('Too few points',msg) return - + wx.BeginBusyCursor() try: G2pwd.DoPeakFit('LSQ',[],background,limits,inst,inst2, @@ -1787,7 +1793,7 @@ def SetInstParms(Inst): G2pwpl.PlotPatterns(G2frame,plotType='PWDR') # show the updated background values wx.CallLater(100,UpdateBackground,G2frame,data) - + def OnBkgClear(event): 'Clear fixed points from background' if 'FixedPoints' not in data[1]: @@ -1795,7 +1801,7 @@ def OnBkgClear(event): else: data[1]['FixedPoints'] = [] G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnPeaksMove(event): 'Move a background peak' if not data[1]['nPeaks']: @@ -1805,7 +1811,7 @@ def OnPeaksMove(event): for peak in data[1]['peaksList']: Peaks['peaks'].append([peak[0],0,peak[2],0,peak[4],0,peak[6],0]) G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Peak List'),Peaks) - + def OnMakeRDF(event): 'Make a Radial Distribution Function from the background - useful for selecting Debye background positions' dlg = RDFDialog(G2frame) @@ -1816,7 +1822,7 @@ def OnMakeRDF(event): return finally: dlg.Destroy() - PatternId = G2frame.PatternId + PatternId = G2frame.PatternId background = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Background')) inst,inst2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Instrument Parameters')) pwddata = G2frame.GPXtree.GetItemPyData(PatternId)[1] @@ -1829,35 +1835,35 @@ def OnMakeRDF(event): else: xlabel = r'$Q,\AA$'+superMinusOne ylabel = r'$I(Q)$' - G2plt.PlotXY(G2frame,[XY,],Title=plot[2],labelX=xlabel,labelY=ylabel,lines=True) - + G2plt.PlotXY(G2frame,[XY,],Title=plot[2],labelX=xlabel,labelY=ylabel,lines=True) + def BackSizer(): - + def OnNewType(event): data[0][0] = bakType.GetValue() - + def OnBakRef(event): data[0][1] = bakRef.GetValue() - + def OnBakTerms(event): data[0][2] = int(bakTerms.GetValue()) M = len(data[0]) N = data[0][2]+3 item = data[0] if N > M: #add terms - for i in range(M,N): + for i in range(M,N): item.append(0.0) elif N < M: #delete terms for i in range(N,M): del(item[-1]) G2frame.GPXtree.SetItemPyData(BackId,data) wx.CallLater(100,UpdateBackground,G2frame,data) - + def AfterChange(invalid,value,tc): if invalid: return CalcBack(G2frame.PatternId) G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + backSizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Background function: '),0,WACV) @@ -1886,22 +1892,22 @@ def AfterChange(invalid,value,tc): bakSizer.Add(bakVal,0,WACV) backSizer.Add(bakSizer) return backSizer - + def DebyeSizer(): - + def OnDebTerms(event): data[1]['nDebye'] = int(debTerms.GetValue()) M = len(data[1]['debyeTerms']) N = data[1]['nDebye'] if N > M: #add terms - for i in range(M,N): + for i in range(M,N): data[1]['debyeTerms'].append([1.0,False,1.0,False,0.010,False]) elif N < M: #delete terms for i in range(N,M): del(data[1]['debyeTerms'][-1]) if N == 0: CalcBack(G2frame.PatternId) - G2pwpl.PlotPatterns(G2frame,plotType='PWDR') + G2pwpl.PlotPatterns(G2frame,plotType='PWDR') wx.CallAfter(UpdateBackground,G2frame,data) def KeyEditPeakGrid(event): @@ -1921,10 +1927,10 @@ def KeyEditPeakGrid(event): for row in range(debyeGrid.GetNumberRows()): data[1]['debyeTerms'][row][col]=True elif key == 78: #'N' for row in range(debyeGrid.GetNumberRows()): data[1]['debyeTerms'][row][col]=False - + def OnCellChange(event): CalcBack(G2frame.PatternId) - G2pwpl.PlotPatterns(G2frame,plotType='PWDR') + G2pwpl.PlotPatterns(G2frame,plotType='PWDR') debSizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -1937,7 +1943,7 @@ def OnCellChange(event): topSizer.Add((5,0),0) debSizer.Add(topSizer) if data[1]['nDebye']: - debSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Debye diffuse terms:'),0) + debSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Debye diffuse terms:'),0) rowLabels = [] for i in range(len(data[1]['debyeTerms'])): rowLabels.append(str(i)) colLabels = ['A','refine','R','refine','U','refine'] @@ -1950,9 +1956,9 @@ def OnCellChange(event): debyeGrid.Bind(wx.EVT_KEY_DOWN, KeyEditPeakGrid) debyeGrid.Bind(wg.EVT_GRID_CELL_CHANGED,OnCellChange) debyeGrid.AutoSizeColumns(False) - debSizer.Add(debyeGrid) + debSizer.Add(debyeGrid) return debSizer - + def PeaksSizer(): def OnPeaks(event): @@ -1961,7 +1967,7 @@ def OnPeaks(event): M = len(data[1]['peaksList']) N = data[1]['nPeaks'] if N > M: #add terms - for i in range(M,N): + for i in range(M,N): data[1]['peaksList'].append([1.0,False,1.0,False,0.10,False,0.10,False]) elif N < M: #delete terms for i in range(N,M): @@ -1980,7 +1986,7 @@ def OnPeaks(event): peaksGrid.DisableCellEditControl() #wx.CallLater(100,peaksGrid.Destroy) # crashes python wx.CallAfter(UpdateBackground,G2frame,data) - + def KeyEditPeakGrid(event): colList = peaksGrid.GetSelectedCols() if event.GetKeyCode() == wx.WXK_RETURN: @@ -1998,10 +2004,10 @@ def KeyEditPeakGrid(event): for row in range(peaksGrid.GetNumberRows()): data[1]['peaksList'][row][col]=True elif key == 78: #'N' for row in range(peaksGrid.GetNumberRows()): data[1]['peaksList'][row][col]=False - + def OnCellChange(event): CalcBack(G2frame.PatternId) - G2pwpl.PlotPatterns(G2frame,plotType='PWDR') + G2pwpl.PlotPatterns(G2frame,plotType='PWDR') peaksSizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -2016,7 +2022,7 @@ def OnCellChange(event): G2frame.dataWindow.currentGrids = [] peaksGrid = None if data[1]['nPeaks']: - peaksSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Peak list:'),0) + peaksSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Peak list:'),0) rowLabels = [] for i in range(len(data[1]['peaksList'])): rowLabels.append(str(i)) colLabels = ['pos','refine','int','refine','sig','refine','gam','refine'] @@ -2031,11 +2037,11 @@ def OnCellChange(event): peaksGrid.Bind(wx.EVT_KEY_DOWN, KeyEditPeakGrid) peaksGrid.Bind(wg.EVT_GRID_CELL_CHANGED,OnCellChange) peaksGrid.AutoSizeColumns(False) - peaksSizer.Add(peaksGrid) + peaksSizer.Add(peaksGrid) return peaksSizer - + def BackFileSizer(): - + def OnBackPWDR(event): data[1]['background PWDR'][0] = back.GetValue() if len(data[1]['background PWDR'][0]): @@ -2057,14 +2063,14 @@ def OnBackPWDR(event): CalcBack() G2pwpl.PlotPatterns(G2frame,plotType='PWDR') wx.CallLater(100,UpdateBackground,G2frame,data) - + def AfterChange(invalid,value,tc): if invalid: return CalcBack() G2pwpl.PlotPatterns(G2frame,plotType='PWDR') - + def OnBackFit(event): - data[1]['background PWDR'][2] = not data[1]['background PWDR'][2] + data[1]['background PWDR'][2] = not data[1]['background PWDR'][2] fileSizer = wx.BoxSizer(wx.VERTICAL) fileSizer.Add((-1,5)) @@ -2073,7 +2079,7 @@ def OnBackFit(event): backSizer.Add(btn,0,wx.RIGHT,3) btn.Enable(len(data[1].get('FixedPoints',[])) > 5) btn.Bind(wx.EVT_BUTTON,OnBkgFit) - + btn = wx.Button(G2frame.dataWindow, wx.ID_ANY,'Compute auto background') backSizer.Add(btn,0,wx.RIGHT,3) btn.Bind(wx.EVT_BUTTON,onAutoBack) @@ -2085,7 +2091,7 @@ def OnBackFit(event): btn.Bind(wx.EVT_BUTTON,copyAutoBack) fileSizer.Add(backSizer) fileSizer.Add((-1,5)) - + fileSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Fixed background histogram (for point-by-point subtraction):'),0) if 'background PWDR' not in data[1]: data[1]['background PWDR'] = ['',-1.,False] @@ -2131,7 +2137,7 @@ def onAutoBack(event): G2pwpl.PlotPatterns(G2frame,plotType='PWDR') def copyAutoBack(event): - '''reproduce the auto background computation on selected + '''reproduce the auto background computation on selected other histograms ''' savePatternId = G2frame.PatternId @@ -2155,7 +2161,7 @@ def copyAutoBack(event): try: copyList = [] if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): copyList.append(histList[i]) finally: dlg.Destroy() @@ -2196,7 +2202,7 @@ def CalcBack(PatternId=G2frame.PatternId): fixBack = GetFileBackground(G2frame,pwddata[1],backData,scale=False) pwddata[1][4][xBeg:xFin] = G2pwd.getBackground('',parmDict,bakType,dataType,pwddata[1][0][xBeg:xFin],fixBack[xBeg:xFin])[0] - # UpdateBackground execution starts here + # UpdateBackground execution starts here G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.PeakMenu) # needed below G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.BackMenu) if len(data) < 2: #add Debye diffuse & peaks scattering here @@ -2215,7 +2221,7 @@ def CalcBack(PatternId=G2frame.PatternId): G2frame.Bind(wx.EVT_MENU,OnPeaksMove,id=G2G.wxID_BACKPEAKSMOVE) G2frame.Bind(wx.EVT_MENU,OnMakeRDF,id=G2G.wxID_MAKEBACKRDF) G2frame.Bind(wx.EVT_MENU,OnBkgFit,id=G2frame.dataWindow.wxID_BackPts['Fit']) - G2frame.Bind(wx.EVT_MENU,OnBkgClear,id=G2frame.dataWindow.wxID_BackPts['Clear']) + G2frame.Bind(wx.EVT_MENU,OnBkgClear,id=G2frame.dataWindow.wxID_BackPts['Clear']) BackId = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Background') Choices = ['chebyschev','chebyschev-1','cosine','Q^2 power series','Q^-2 power series','lin interpolate','inv interpolate','log interpolate'] G2frame.dataWindow.ClearData() @@ -2240,7 +2246,7 @@ def CalcBack(PatternId=G2frame.PatternId): def addAutoBack(G2frame,data,xydata): '''Create a new histogram for the computed auto background and place as the fixed background histogram - ''' + ''' bkgHistName = 'PWDR Autobkg for '+G2frame.GPXtree.GetItemText(G2frame.PatternId)[5:] # if histogram exists we should probably reuse it, but for now, just create a new one bkgHistName = G2obj.MakeUniqueLabel(bkgHistName,G2frame.GetHistogramNames('PWDR')) @@ -2260,12 +2266,12 @@ def addAutoBack(G2frame,data,xydata): d[1][5] = np.zeros_like(xydata[4]) NewId = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=bkgHistName) G2frame.GPXtree.SetItemPyData(NewId,d) - + item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.PatternId) while item: nam = G2frame.GPXtree.GetItemText(item) if nam == 'Comments': - d = [' # background generated with Autobkg'] + d = [' # background generated with Autobkg'] elif nam == 'Background': d = [['chebyschev-1',True,3,1.0,0.0,0.0], {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[], @@ -2277,7 +2283,7 @@ def addAutoBack(G2frame,data,xydata): elif nam == 'Unit Cells List': d = [] elif nam == 'Reflection Lists': - d = {} + d = {} else: d = copy.deepcopy(G2frame.GPXtree.GetItemPyData(item)) G2frame.GPXtree.SetItemPyData( @@ -2301,8 +2307,8 @@ def addAutoBack(G2frame,data,xydata): # Autobackground Dialog class autoBackground(wx.Dialog): - '''Create a file selection widget for setting background with - pybaselines, as requested by James Feng. + '''Create a file selection widget for setting background with + pybaselines, as requested by James Feng. :param wx.Frame G2frame: reference to the main GSAS-II frame. @@ -2314,7 +2320,7 @@ def __init__(self,G2frame,*args,**kwargs): self.bkgdict = data[1] self.xydata = G2frame.GPXtree.GetItemPyData(G2frame.PatternId)[1] npts = len(self.xydata[0]) - # add auto bkg to background prms dict + # add auto bkg to background prms dict self.bkgdict['autoPrms'] = self.bkgdict.get('autoPrms',{}) self.bkgdict['autoPrms']['opt'] = self.bkgdict['autoPrms'].get('opt',0) logLam = min(10,float(int(10*np.log10(npts)**1.5)-9.5)/10.) @@ -2324,10 +2330,10 @@ def __init__(self,G2frame,*args,**kwargs): # save starting point info self.startingBackground = copy.deepcopy(self.xydata[4]) # start process - wx.Dialog.__init__(self, parent=G2frame, + wx.Dialog.__init__(self, parent=G2frame, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) self.CenterOnParent() - + mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(wx.StaticText(self,label=' Compute autobackground'),0) choices = ['arpls','iarpls'] @@ -2341,7 +2347,7 @@ def __init__(self,G2frame,*args,**kwargs): 'logLam','log(Lambda)', 1.,maxLam,100,self._calcBkg) mainSizer.Add(siz) - + subSiz = wx.BoxSizer(wx.HORIZONTAL) subSiz.Add((-1,-1),1,wx.EXPAND,1) btn = wx.Button(self, wx.ID_CLOSE, label='Set Fixed\nPoints && Fit') @@ -2370,14 +2376,14 @@ def __init__(self,G2frame,*args,**kwargs): # restore the background to the starting values self.xydata[4] = self.startingBackground self.bkgdict['autoPrms']['Mode'] = None - + def _calcBkg(self,event=None): - '''respond to a change in the background parameters by recomputing + '''respond to a change in the background parameters by recomputing the auto background ''' self.xydata[4] = G2pwd.autoBkgCalc(self.bkgdict,self.xydata[1].data) G2pwpl.PlotPatterns(self.G2frame,plotType='PWDR') - + ################################################################################ ##### Limits ################################################################################ @@ -2401,16 +2407,16 @@ def LimitSizer(): limits.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data[1],i, \ xmin=data[0][0],xmax=data[0][1],nDig=(10,4),typeHint=float,OnLeave=AfterChange)) return limits - + def ExclSizer(): - + def OnDelExcl(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] del(data[item+2]) G2pwpl.PlotPatterns(G2frame,newPlot=False,plotType=datatype) wx.CallAfter(UpdateLimitsGrid,G2frame,data,datatype) - + Indx = {} excl = wx.FlexGridSizer(0,3,0,5) excl.Add(wx.StaticText(G2frame.dataWindow,label=' From: '),0,WACV) @@ -2442,7 +2448,7 @@ def onSetLimExcl(event): G2frame.CancelSetLimitsMode.Enable(False) G2frame.plotFrame.Raise() G2pwpl.PlotPatterns(G2frame,newPlot=False,plotType=datatype) - + def OnLimitCopy(event): hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) histList = GetHistsLikeSelected(G2frame) @@ -2453,14 +2459,14 @@ def OnLimitCopy(event): 'Copy limits', histList) try: if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): item = histList[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) G2frame.GPXtree.SetItemPyData( G2gd.GetGPXtreeItemId(G2frame,Id,'Limits'),copy.deepcopy(data)) finally: dlg.Destroy() - + def Draw(): G2frame.dataWindow.ClearData() topSizer = G2frame.dataWindow.topBox @@ -2494,8 +2500,8 @@ def Draw(): G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.SASDLimitMenu) G2frame.CancelSetLimitsMode.Enable(False) G2frame.Bind(wx.EVT_MENU,OnLimitCopy,id=G2G.wxID_SASDLIMITCOPY) - Draw() - + Draw() + ################################################################################ ##### Instrument parameters ################################################################################ @@ -2504,7 +2510,7 @@ def UpdateInstrumentGrid(G2frame,data): '''respond to selection of PWDR/SASD/REFD Instrument Parameters data tree item. ''' - if 'Bank' not in data: #get it from name; absent for default parms selection + if 'Bank' not in data: #get it from name; absent for default parms selection hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) if 'Bank' in hst: bank = int(hst.split('Bank')[1].split('_')[0]) @@ -2520,7 +2526,7 @@ def keycheck(keys): 'Lam','Azimuth','2-theta','fltPath','difC','difA','difB','Zero','Lam1','Lam2','XE','YE','ZE','WE']: good.append(key) return good - + def updateData(inst,ref): Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, G2frame.PatternId,'Instrument Parameters'))[0] @@ -2532,7 +2538,7 @@ def updateData(inst,ref): Data[item] = [Data[item][0],inst[item]] except KeyError: pass #skip 'Polariz.' for N-data - + def RefreshInstrumentGrid(event,doAnyway=False): if doAnyway or event.GetRow() == 1: peaks = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Peak List')) @@ -2541,7 +2547,7 @@ def RefreshInstrumentGrid(event,doAnyway=False): newpeaks.append(G2mth.setPeakparms(data,Inst2,peak[0],peak[2])) peaks['peaks'] = newpeaks G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Peak List'),peaks) - + def OnCalibrate(event): Pattern = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) xye = ma.array(ma.getdata(Pattern[1])) @@ -2563,7 +2569,7 @@ def OnCalibrate(event): Ok = True if not Ok: G2frame.ErrorDialog('Cannot calibrate','Index Peak List not indexed') - return + return if G2pwd.DoCalibInst(IndexPeaks,data,Sample): UpdateInstrumentGrid(G2frame,data) const = 0.0 @@ -2589,24 +2595,24 @@ def OnCalibrate(event): def OnLoad(event): '''Loads instrument parameters from a G2 .instprm file in response to the Instrument Parameters-Operations/Load Profile menu - If instprm file has multiple banks each with header #Bank n: ..., this + If instprm file has multiple banks each with header #Bank n: ..., this finds matching bank no. to load - rejects nonmatches. - + Note uses ReadPowderInstprm (GSASIIdataGUI.py) to read .instprm fil ''' - + def GetDefaultParms(rd): '''Solicits from user a default set of parameters & returns Inst parm dict param: rd: importer data structure returns: dict: Instrument parameter dictionary - ''' + ''' import defaultIparms as dI sind = lambda x: math.sin(x*math.pi/180.) tand = lambda x: math.tan(x*math.pi/180.) while True: # loop until we get a choice choices = [] head = 'Select from default instrument parameters' - + for l in dI.defaultIparm_lbl: choices.append('Defaults for '+l) res = G2G.BlockSelector(choices,ParentFrame=G2frame,title=head, @@ -2636,13 +2642,13 @@ def GetDefaultParms(rd): else: rd.Sample.update({'Type':'Debye-Scherrer','Absorption':[0.,False],'DisplaceX':[0.,False], 'DisplaceY':[0.,False]}) - + data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, G2frame.PatternId,'Instrument Parameters'))[0] bank = data['Bank'][0] pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II instrument parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II instrument parameters file', pth, '', 'instrument parameter files (*.instprm)|*.instprm',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -2676,20 +2682,20 @@ def GetDefaultParms(rd): rd.Sample = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId,'Sample Parameters')) try: data = GetDefaultParms(rd)[0] - except TypeError: #Cancel - got None + except TypeError: #Cancel - got None pass UpdateInstrumentGrid(G2frame,data) G2plt.PlotPeakWidths(G2frame) finally: dlg.Destroy() - + def OnSave(event): '''Respond to the Instrument Parameters Operations/Save Profile menu item: writes current parameters to a .instprm file It does not write Bank n: on # line & thus can be used any time w/o clash of bank nos. ''' pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Set name to save GSAS-II instrument parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Set name to save GSAS-II instrument parameters file', pth, '', 'instrument parameter files (*.instprm)|*.instprm',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -2705,9 +2711,9 @@ def OnSave(event): print ('Instrument parameters saved to: '+filename) finally: dlg.Destroy() - + def OnSaveAll(event): - '''Respond to the Instrument Parameters Operations/Save all Profile menu & writes + '''Respond to the Instrument Parameters Operations/Save all Profile menu & writes selected inst parms. across multiple banks into a single file Each block starts with #Bank n: GSAS-II instrument... where n is bank no. item: writes parameters from selected PWDR entries to a .instprm file @@ -2724,7 +2730,7 @@ def OnSaveAll(event): finally: dlg.Destroy() pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II instrument parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II instrument parameters file', pth, '', 'instrument parameter files (*.instprm)|*.instprm',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -2745,14 +2751,14 @@ def OnSaveAll(event): File.close() finally: dlg.Destroy() - + def OnReset(event): insVal.update(insDef) updateData(insVal,insRef) RefreshInstrumentGrid(event,doAnyway=True) #to get peaks updated UpdateInstrumentGrid(G2frame,data) G2plt.PlotPeakWidths(G2frame) - + def OnInstFlagCopy(event): hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) histList = GetHistsLikeSelected(G2frame) @@ -2786,7 +2792,7 @@ def OnInstFlagCopy(event): instData[item][2] = copy.copy(flags[item]) else: print (item+' not copied - instrument parameters not commensurate') - + def OnInstCopy(event): #need fix for dictionary hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) @@ -2803,7 +2809,7 @@ def OnInstCopy(event): 'Copy parameters', histList) try: if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): copyList.append(histList[i]) finally: dlg.Destroy() @@ -2827,13 +2833,13 @@ def OnInstCopy(event): print (item+' not copied - %d instrument parameters do not match source # %d'%(len(instData),len(data))) else: print (item+' not copied - instrument type %s does not match source type %s'%(instData['Type'][0],instType)) - + def AfterChange(invalid,value,tc): if invalid: return updateData(insVal,insRef) G2plt.PlotPeakWidths(G2frame) - - def AfterChangeEC(invalid,value,tc): + + def AfterChangeEC(invalid,value,tc): '''for SEC data only; converts electrn energy in keV to wavelength ''' if invalid: return @@ -2843,12 +2849,12 @@ def AfterChangeEC(invalid,value,tc): tc.SetValue(value) insVal.update({'Lam':value}) updateData(insVal,insRef) - + def NewProfile(invalid,value,tc): if invalid: return G2plt.PlotPeakWidths(G2frame) updateData(insVal,insRef) - + def OnItemRef(event): Obj = event.GetEventObject() item = RefObj[Obj.GetId()] @@ -2868,7 +2874,7 @@ def OnCopy1Val(event): def OnInstMult(event): 'If checked or unchecked, redisplay window' wx.CallAfter(UpdateInstrumentGrid,G2frame,data) - + def lblWdef(lbl,dec,val): 'Label parameter showing the default value' fmt = "%15."+str(dec)+"f" @@ -2888,7 +2894,7 @@ def OnLamPick(event): if 'P' in insVal['Type']: insVal['Lam1'] = G2elem.waves[lamType][0] insVal['Lam2'] = G2elem.waves[lamType][1] - elif 'S' in insVal['Type']: #and + elif 'S' in insVal['Type']: #and try: insVal['Lam'] = G2elem.meanwaves[lamType] data['Type'][0] = 'SXC' @@ -2907,7 +2913,7 @@ def OnLamPick(event): def MakeParameterWindow(): 'Displays the Instrument parameters in the dataWindow frame' - + def MakeLamSizer(): if 'Lam1' in insVal: subSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -2925,7 +2931,7 @@ def MakeLamSizer(): # ['TiKa','CrKa','FeKa','CoKa','CuKa','GaKa','MoKa','AgKa','InKa'] # patch if Lam1/2 & only element is specified if 'Lam1' in data and data['Source'][1] not in choice: - indxs = [i for i,t in enumerate(choice) if + indxs = [i for i,t in enumerate(choice) if t.lower().startswith(data['Source'][1].lower())] if len(indxs) == 1: data['Source'][1] = choice[indxs[0]] lamPick = wx.ComboBox(G2frame.dataWindow,value=data['Source'][1],choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN) @@ -2938,7 +2944,7 @@ def MakeLamSizer(): labelLst.append(key) elemKeysLst.append([key,1]) dspLst.append([10,4]) - refFlgElem.append([key,2]) + refFlgElem.append([key,2]) ratVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,insVal,key,nDig=(10,4),typeHint=float,OnLeave=AfterChange) instSizer.Add(ratVal,0) instSizer.Add(RefineBox(key),0,WACV) @@ -2954,9 +2960,9 @@ def MakeLamSizer(): elemKeysLst.append([key,1]) dspLst.append([10,6]) instSizer.Add(waveVal,0,WACV) - refFlgElem.append([key,2]) + refFlgElem.append([key,2]) instSizer.Add(RefineBox(key),0,WACV) - + G2frame.dataWindow.ClearData() topSizer = G2frame.dataWindow.topBox parent = G2frame.dataWindow.topPanel @@ -2982,7 +2988,7 @@ def MakeLamSizer(): elemKeysLst.append(['Azimuth',1]) dspLst.append([10,2]) refFlgElem.append(None) - MakeLamSizer() + MakeLamSizer() for item in ['Zero','Polariz.']: if item in insDef: labelLst.append(item) @@ -3006,7 +3012,7 @@ def MakeLamSizer(): else: Reference = "Reference: R.B. Von Dreele (2024). J. Appl. Cryst. 57, 1588-1597." else: #'A' - itemList = ['U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1','SH/L'] + itemList = ['U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1','SH/L'] Reference = """References: Thompson, P., Cox, D.E. & Hastings, J.B. (1987). J. Appl. Cryst. 20,79-83. Finger, L. W., Cox, D. E. & Jephcoat, A. P. (1994). J. Appl. Cryst. 27, 892-900. @@ -3035,7 +3041,7 @@ def MakeLamSizer(): elemKeysLst.append([key,1]) dspLst.append([10,3]) instSizer.Add(tthVal,0,WACV) - refFlgElem.append([key,2]) + refFlgElem.append([key,2]) instSizer.Add(RefineBox(key),0,WACV) for item in ['XE','YE','ZE','WE','A','B','C','X','Y','Z']: nDig = (10,6,'g') @@ -3046,14 +3052,14 @@ def MakeLamSizer(): instSizer.Add(wx.StaticText(G2frame.dataWindow,-1,lblWdef(item,nDig[1],insDef[item])),0,WACV) if item in ['XE','YE','ZE','WE']: instSizer.Add(wx.StaticText(G2frame.dataWindow,label='%10.6g'%insVal[item])) - instSizer.Add((5,5),0) - else: + instSizer.Add((5,5),0) + else: itemVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,insVal,item,nDig=nDig,typeHint=float,OnLeave=NewProfile) instSizer.Add(itemVal,0,WACV) instSizer.Add(RefineBox(item),0,WACV) elif 'T' in insVal['Type']: #time of flight (neutrons) Reference = """References: - Von Dreele, R., Jorgensen, J. D. & Windsor, C. G. (1982) J. Appl. Cryst. 15, 581-589. + Von Dreele, R., Jorgensen, J. D. & Windsor, C. G. (1982) J. Appl. Cryst. 15, 581-589. Huq, A., Kirkham, M., Peterson, P.F., Hodges, J.P. Whitfield, P.S., Page, K., Hugle, T., Iverson, E.B., Parizzia, A. & Rennich, G. (2019). J. Appl. Cryst. 52, 1189–1201. """ @@ -3064,14 +3070,14 @@ def MakeLamSizer(): labelLst.append('flight path') elemKeysLst.append(['fltPath',1]) dspLst.append([10,2]) - refFlgElem.append(None) + refFlgElem.append(None) subSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' 2-theta: '),0,WACV) txt = '%7.2f'%(insVal['2-theta']) subSizer.Add(wx.StaticText(G2frame.dataWindow,-1,txt.strip()),0,WACV) labelLst.append('2-theta') elemKeysLst.append(['2-theta',1]) dspLst.append([10,2]) - refFlgElem.append(None) + refFlgElem.append(None) if 'Pdabc' in Inst2: Items = ['sig-0','sig-1','sig-2','sig-q','X','Y','Z'] subSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' difC: '),0,WACV) @@ -3113,8 +3119,8 @@ def MakeLamSizer(): elemKeysLst.append([key,1]) dspLst.append([10,6]) instSizer.Add(waveVal,0,WACV) - refFlgElem.append([key,2]) - instSizer.Add((5,5),0) + refFlgElem.append([key,2]) + instSizer.Add((5,5),0) for item in ['Zero',]: if item in insDef: labelLst.append(item) @@ -3126,8 +3132,8 @@ def MakeLamSizer(): itemVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,insVal,item,nDig=(10,4),typeHint=float,OnLeave=AfterChange) instSizer.Add(itemVal,0,WACV) refFlgElem.append([item,2]) - instSizer.Add((5,5),0) - + instSizer.Add((5,5),0) + elif 'S' in insVal['Type']: #single crystal data Reference = '' if 'C' in insVal['Type']: #constant wavelength @@ -3154,7 +3160,7 @@ def MakeLamSizer(): pass #for now elif insVal['Type'][0] in ['L','R',]: Reference = '' - if 'C' in insVal['Type']: + if 'C' in insVal['Type']: instSizer.Add(wx.StaticText(G2frame.dataWindow,-1,u' Lam (\xc5): (%10.6f)'%(insDef['Lam'])),0,WACV) waveVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,insVal,'Lam',nDig=(10,6),typeHint=float,OnLeave=AfterChange) instSizer.Add(waveVal,0,WACV) @@ -3166,7 +3172,7 @@ def MakeLamSizer(): labelLst.append('Azimuth angle') elemKeysLst.append(['Azimuth',1]) dspLst.append([10,2]) - refFlgElem.append(None) + refFlgElem.append(None) else: #time of flight (neutrons) pass #for now @@ -3174,10 +3180,10 @@ def MakeLamSizer(): mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=Reference)) G2frame.dataWindow.SetDataSize() # end of MakeParameterWindow - + plotYsel = {} # selected Y items def MakeMultiParameterWindow(selected=None): - '''Displays the Instrument parameters for multiple histograms + '''Displays the Instrument parameters for multiple histograms in the dataWindow panel ''' plotIndex = {'plotX':0} # index for param name => plotTbl index @@ -3203,7 +3209,7 @@ def onSelectX(event): 'respond to change in plotting x axis; save and plot (if y selected)' plotIndex['plotX'] = event.GetEventObject().rbindex onPrmPlot(event) - + def onPrmPlot(event): '''Callback after a change to X or Y plot contents plots multiple instrument param values vs selected X value. @@ -3293,7 +3299,7 @@ def onPrmPlot(event): fgs.Add(rb,0,wx.ALIGN_CENTER|WACV) if firstRadio: rb.SetValue(True) - firstRadio = 0 + firstRadio = 0 fgs.Add((-1,-1)) # skip y checkbutton fgs.Add(wx.StaticText(sdlg,wx.ID_ANY,lbl),0,WACV|wx.LEFT,14) plotvals = [] @@ -3350,13 +3356,13 @@ def onPrmPlot(event): miniSizer.Add(G2G.G2CheckBox(sdlg,'',histdict[h][k],2),0,WACV|wx.RIGHT,15) fgs.Add(miniSizer,0,wx.ALIGN_CENTER) plotTbl.append(plotvals) - + mainSizer.Add(fgs) G2frame.dataWindow.SetDataSize() wx.EndBusyCursor() # end of MakeMultiParameterWindow - - #### beginning of UpdateInstrumentGrid code + + #### beginning of UpdateInstrumentGrid code #patch: make sure all parameter items are lists patched = 0 for key in data: @@ -3391,7 +3397,7 @@ def onPrmPlot(event): insRef = {} RefObj = {} Inst2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, - G2frame.PatternId,'Instrument Parameters'))[1] + G2frame.PatternId,'Instrument Parameters'))[1] G2gd.SetDataMenuBar(G2frame) #patch if 'P' in insVal['Type']: #powder data @@ -3422,7 +3428,7 @@ def onPrmPlot(event): G2frame.Bind(wx.EVT_MENU,OnInstCopy,id=G2G.wxID_INSTCOPY) G2frame.Bind(wx.EVT_MENU,OnInstFlagCopy,id=G2G.wxID_INSTFLAGCOPY) G2frame.Bind(wx.EVT_MENU,OnCopy1Val,id=G2G.wxID_INST1VAL) - G2frame.Bind(wx.EVT_MENU,OnInstMult,id=G2G.wxID_INSTSHOWMULT) + G2frame.Bind(wx.EVT_MENU,OnInstMult,id=G2G.wxID_INSTSHOWMULT) menuitem = G2frame.dataWindow.InstMenu.FindItemById(G2G.wxID_INSTSHOWMULT) if menuitem.IsChecked(): MakeMultiParameterWindow() @@ -3433,7 +3439,7 @@ def onPrmPlot(event): G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.SASDInstMenu) G2frame.Bind(wx.EVT_MENU,OnInstCopy,id=G2G.wxID_SASDINSTCOPY) G2frame.dataWindow.SetDataSize() - + ################################################################################ ##### Sample parameters ################################################################################ @@ -3448,7 +3454,7 @@ def OnSampleSave(event): item: writes current parameters to a .samprm file ''' pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II sample parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II sample parameters file', pth, '', 'sample parameter files (*.samprm)|*.samprm',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -3464,14 +3470,14 @@ def OnSampleSave(event): File.close() finally: dlg.Destroy() - + def OnSampleLoad(event): '''Loads sample parameters from a G2 .samprm file - in response to the Sample Parameters-Operations/Load menu + in response to the Sample Parameters-Operations/Load menu ''' pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose GSAS-II sample parameters file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose GSAS-II sample parameters file', pth, '', 'sample parameter files (*.samprm)|*.samprm',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -3485,19 +3491,19 @@ def OnSampleLoad(event): continue [item,val] = S[:-1].split(':') newItems[item.strip("'")] = eval(val) - S = File.readline() + S = File.readline() File.close() data.update(newItems) G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId,'Sample Parameters'),data) UpdateSampleGrid(G2frame,data) finally: dlg.Destroy() - + def OnAllSampleLoad(event): filename = '' pth = G2G.GetImportPath(G2frame) if not pth: pth = '.' - dlg = wx.FileDialog(G2frame, 'Choose multihistogram metadata text file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose multihistogram metadata text file', pth, '', 'metadata file (*.*)|*.*',wx.FD_OPEN) try: if dlg.ShowModal() == wx.ID_OK: @@ -3516,7 +3522,7 @@ def OnAllSampleLoad(event): Stuff = S[:-1].split() itemNames.append(Stuff[0]) newItems.append(Stuff[1:]) - S = File.readline() + S = File.readline() File.close() finally: dlg.Destroy() @@ -3565,9 +3571,9 @@ def OnAllSampleLoad(event): break Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist) sampleData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Sample Parameters')) - sampleData.update(newItems) - UpdateSampleGrid(G2frame,data) - + sampleData.update(newItems) + UpdateSampleGrid(G2frame,data) + def OnSetScale(event): if histName[:4] in ['REFD','PWDR']: Scale = data['Scale'][0] @@ -3584,7 +3590,7 @@ def OnSetScale(event): G2pwpl.PlotPatterns(G2frame,plotType=histName[:4],newPlot=True) UpdateSampleGrid(G2frame,data) return - #SASD rescaliing + #SASD rescaliing histList = [] item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root) while item: @@ -3613,7 +3619,7 @@ def OnSetScale(event): G2sasd.SetScale(Data,refData) G2pwpl.PlotPatterns(G2frame,plotType='SASD',newPlot=True) UpdateSampleGrid(G2frame,data) - + def OnRescaleAll(event): hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) histList = GetHistsLikeSelected(G2frame) @@ -3635,7 +3641,7 @@ def OnRescaleAll(event): else: sum0 = np.sum(y0[iBeg:iFin]) result = dlg.GetSelections() - for i in result: + for i in result: item = histList[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) xi,yi,wi = G2frame.GPXtree.GetItemPyData(Id)[1][:3] @@ -3647,7 +3653,7 @@ def OnRescaleAll(event): finally: dlg.Destroy() G2pwpl.PlotPatterns(G2frame,plotType=histName[:4],newPlot=True) - + def OnSampleCopy(event): histType,copyNames = SetCopyNames(histName,data['Type'], addNames = ['Omega','Chi','Phi','Gonio. radius','InstrName']) @@ -3664,7 +3670,7 @@ def OnSampleCopy(event): try: if dlg.ShowModal() == wx.ID_OK: result = dlg.GetSelections() - for i in result: + for i in result: item = histList[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) sampleData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Sample Parameters')) @@ -3694,7 +3700,7 @@ def OnSampleCopySelected(event): keyList = [i for i in data.keys() if i in TextTable] keyText = [TextTable[i] for i in keyList] # sort both lists together, ordered by keyText - keyText, keyList = zip(*sorted(list(zip(keyText,keyList)))) # sort lists + keyText, keyList = zip(*sorted(list(zip(keyText,keyList)))) # sort lists selectedKeys = [] dlg = G2G.G2MultiChoiceDialog(G2frame,'Select which sample parameters\nto copy', 'Select sample parameters', keyText) @@ -3712,13 +3718,13 @@ def OnSampleCopySelected(event): try: if dlg.ShowModal() == wx.ID_OK: result = dlg.GetSelections() - for i in result: + for i in result: item = histList[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) sampleData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Sample Parameters')) sampleData.update(copyDict) finally: - dlg.Destroy() + dlg.Destroy() G2pwpl.PlotPatterns(G2frame,plotType=hst[:4],newPlot=False) def OnSampleFlagCopy(event): @@ -3736,13 +3742,13 @@ def OnSampleFlagCopy(event): try: if dlg.ShowModal() == wx.ID_OK: result = dlg.GetSelections() - for i in result: + for i in result: item = histList[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) sampleData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Sample Parameters')) for name in copyNames: if name not in sampleData: - sampleData[name] = [0.0,False] + sampleData[name] = [0.0,False] sampleData[name][1] = copy.copy(flagDict[name]) finally: dlg.Destroy() @@ -3752,7 +3758,7 @@ def OnHistoChange(): ''' #wx.CallAfter(UpdateSampleGrid,G2frame,data) wx.CallLater(100,UpdateSampleGrid,G2frame,data) - + def SetNameVal(): inst = instNameVal.GetValue() data['InstrName'] = inst.strip() @@ -3760,21 +3766,21 @@ def SetNameVal(): def OnNameVal(event): event.Skip() wx.CallAfter(SetNameVal) - + def AfterChange(invalid,value,tc): if invalid: return if tc.key == 0 and 'SASD' in histName: #a kluge for Scale! G2pwpl.PlotPatterns(G2frame,plotType='SASD',newPlot=True) elif tc.key == 'Thick': - wx.CallAfter(UpdateSampleGrid,G2frame,data) - + wx.CallAfter(UpdateSampleGrid,G2frame,data) + def OnMaterial(event): Obj = event.GetEventObject() Id = Info[Obj.GetId()] data['Materials'][Id]['Name'] = Obj.GetValue() wx.CallAfter(UpdateSampleGrid,G2frame,data) - + def OnVolFrac(invalid,value,tc): Id = Info[tc.GetId()] data['Materials'][not Id]['VolFrac'] = 1.-value @@ -3784,7 +3790,7 @@ def OnCopy1Val(event): 'Select one value to copy to many histograms and optionally allow values to be edited in a table' G2G.SelectEdit1Var(G2frame,data,labelLst,elemKeysLst,dspLst,refFlgElem) wx.CallAfter(UpdateSampleGrid,G2frame,data) - + def SearchAllComments(value,tc,*args,**kwargs): '''Called when the label for a FreePrm is changed: the comments for all PWDR histograms are searched for a "label=value" pair that matches the label (case @@ -3881,7 +3887,7 @@ def SearchAllComments(value,tc,*args,**kwargs): nameSizer.Add(wx.StaticText(G2frame.dataWindow,wx.ID_ANY,' Instrument Name '),0,WACV) nameSizer.Add((-1,-1),1,WACV) instNameVal = wx.TextCtrl(G2frame.dataWindow,wx.ID_ANY,data['InstrName'], - size=(200,-1),style=wx.TE_PROCESS_ENTER) + size=(200,-1),style=wx.TE_PROCESS_ENTER) nameSizer.Add(instNameVal) instNameVal.Bind(wx.EVT_CHAR,OnNameVal) mainSizer.Add(nameSizer,0) @@ -3926,7 +3932,7 @@ def SearchAllComments(value,tc,*args,**kwargs): refFlgElem.append(None) parmSizer.Add(parmVal,0,WACV) Info = {} - + for key in ('FreePrm1','FreePrm2','FreePrm3'): parmVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Controls,key,typeHint=str, notBlank=False,OnLeave=SearchAllComments) @@ -3937,9 +3943,9 @@ def SearchAllComments(value,tc,*args,**kwargs): dspLst.append(None) elemKeysLst.append([key]) refFlgElem.append(None) - + mainSizer.Add(parmSizer,0) - mainSizer.Add((0,5),0) + mainSizer.Add((0,5),0) if histName[:4] in ['SASD',]: rho = [0.,0.] anomrho = [0.,0.] @@ -3952,7 +3958,7 @@ def SearchAllComments(value,tc,*args,**kwargs): matsel = wx.ComboBox(G2frame.dataWindow,value=item['Name'],choices=list(Substances['Substances'].keys()), style=wx.CB_READONLY|wx.CB_DROPDOWN) Info[matsel.GetId()] = Id - matsel.Bind(wx.EVT_COMBOBOX,OnMaterial) + matsel.Bind(wx.EVT_COMBOBOX,OnMaterial) subSizer.Add(matsel,0,WACV) subSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Volume fraction: '),0,WACV) volfrac = G2G.ValidatedTxtCtrl(G2frame.dataWindow,item,'VolFrac', @@ -3976,7 +3982,7 @@ def SearchAllComments(value,tc,*args,**kwargs): conSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Transmission (calc): %10.3f '%(np.exp(-mut))),0,WACV) mainSizer.Add(conSizer,0) G2frame.dataWindow.SetDataSize() - + ################################################################################ ##### Indexing Peaks ################################################################################ @@ -3989,7 +3995,7 @@ def UpdateIndexPeaksGrid(G2frame, data): Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Instrument Parameters'))[0] limitId = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Limits') Limits = G2frame.GPXtree.GetItemPyData(limitId) - + def RefreshIndexPeaksGrid(event): r,c = event.GetRow(),event.GetCol() peaks = G2frame.IndexPeaksTable.GetData() @@ -4026,10 +4032,10 @@ def OnReload(event): data = [peaks,sigs] G2frame.GPXtree.SetItemPyData(IndexId,data) UpdateIndexPeaksGrid(G2frame,data) - + def OnSave(event): pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose Index peaks csv file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose Index peaks csv file', pth, '', 'indexing peaks file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -4045,7 +4051,7 @@ def OnSave(event): File.close() finally: dlg.Destroy() - + def OnExportPreDICT(event): 'Place 2theta positions from Index Peak List into clipboard for cut-&-paste' if wx.TheClipboard.Open(): @@ -4092,7 +4098,7 @@ def KeyEditPickGrid(event): def onRefineCell(event): RefineCell(G2frame) UpdateIndexPeaksGrid(G2frame,data) - + # start of UpdateIndexPeaksGrid controls = None G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.IndexMenu) # needed below @@ -4173,7 +4179,7 @@ def onRefineCell(event): Sigs.append(sig) G2frame.indxPeaks.Bind(wg.EVT_GRID_CELL_LEFT_CLICK, RefreshIndexPeaksGrid) G2frame.indxPeaks.Bind(wg.EVT_GRID_LABEL_LEFT_DCLICK, onCellListDClick) - G2frame.indxPeaks.Bind(wx.EVT_KEY_DOWN, KeyEditPickGrid) + G2frame.indxPeaks.Bind(wx.EVT_KEY_DOWN, KeyEditPickGrid) G2frame.indxPeaks.AutoSizeColumns(False) if len(XY): XY = np.array(XY) @@ -4236,22 +4242,22 @@ def UpdateUnitCellsGrid(G2frame, data, callSeaResSelected=False,New=False,showUs :param wx.Frame G2frame: Main GSAS-II window :param dict data: contents of "Unit Cells List" data tree item - :param bool callSeaResSelected: when True, selects first entry in - UnitCellsTable search results table + :param bool callSeaResSelected: when True, selects first entry in + UnitCellsTable search results table :param bool New: :param bool showUse: when showUse is False (default) the Show flag is cleared in all search tables. When True, and there is a True value - for Show, the flag is set for that in the grid and the row is scrolled - into view. This is currently implemented for indexing (Cell Search + for Show, the flag is set for that in the grid and the row is scrolled + into view. This is currently implemented for indexing (Cell Search Results) only. ''' - global KeyList + global KeyList KeyList = [] - - + + def OnExportCells(event): pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose Indexing Result csv file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose Indexing Result csv file', pth, '', 'indexing result file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -4268,26 +4274,26 @@ def OnExportCells(event): dlg.Destroy() def OnShowGenRefls(event): - '''Generate the reflections from the unit cell and + '''Generate the reflections from the unit cell and display them in the console window ''' OnHklShow(None,indexFrom=' Indexing from unit cell & symmetry settings') for r in G2frame.HKL: print("{0:.0f},{1:.0f},{2:.0f} 2\u03B8={4:7.3f} d={3:8.4f}".format(*r)) - + def OnHklShow(event=None,Print=True,Plot=True,indexFrom=''): - '''Compute the location of powder diffraction peaks from the - cell in controls[6:12] and the space group in ssopt['SGData'] if + '''Compute the location of powder diffraction peaks from the + cell in controls[6:12] and the space group in ssopt['SGData'] if defined, or controls[13], if not. Reflections are placed in G2frame.HKL - If cellDisplayOpts['showExtinct'] is True, all reflections are computed - and reflections that are not present in the G2frame.HKL array are + If cellDisplayOpts['showExtinct'] is True, all reflections are computed + and reflections that are not present in the G2frame.HKL array are placed in G2frame.Extinct - + :params Print: if True, the M20 value, etc. is printed on the console - :returns: None or [Symb,False,M20,X20,Nhkl,frfnd] where + :returns: None or [Symb,False,M20,X20,Nhkl,frfnd] where * Symb: Space group symbol * M20: line position fit metric * X20: number of indexed lines fit metric @@ -4322,19 +4328,19 @@ def OnHklShow(event=None,Print=True,Plot=True,indexFrom=''): Vec = ssopt['ModVec'] maxH = ssopt['maxH'] G2frame.HKL = G2pwd.getHKLMpeak(dmin,Inst,SGData,SSGData,Vec,maxH,A) - if len(peaks[0]): + if len(peaks[0]): peaks = [G2indx.IndexSSPeaks(peaks[0],G2frame.HKL)[1],peaks[1]] #keep esds from peak fit M20,X20 = G2indx.calc_M20SS(peaks[0],G2frame.HKL) else: G2frame.HKL = G2pwd.getHKLpeak(dmin,SGData,A,Inst) G2frame.Extinct = [] - if cellDisplayOpts['showExtinct']: - # generate a table of extinct reflections -- not all, just those not + if cellDisplayOpts['showExtinct']: + # generate a table of extinct reflections -- not all, just those not # close to a allowed peak allpeaks = G2pwd.getHKLpeak(dmin,G2spc.SpcGroup('P 1')[1],A,Inst) - alreadyShown = G2frame.HKL[:,4].round(3) + alreadyShown = G2frame.HKL[:,4].round(3) for peak in allpeaks: # show one reflection only if in a region with no others - pos = peak[4].round(3) + pos = peak[4].round(3) if pos in alreadyShown: continue alreadyShown = np.append(alreadyShown,pos) G2frame.Extinct.append(peak) @@ -4356,7 +4362,7 @@ def OnHklShow(event=None,Print=True,Plot=True,indexFrom=''): else: G2pwpl.PlotPatterns(G2frame,indexFrom=indexFrom) return result - + def OnSortCells(event): '''Sort the display of found unit cells ''' @@ -4373,7 +4379,7 @@ def OnSortCells(event): data = [controls,bravais,cells,dmin,ssopt,magcells] G2frame.GPXtree.SetItemPyData(UnitCellsId,data) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data,showUse=True) - + def CopyUnitCell(event): controls,bravais,cells,dminx,ssopt,magcells = G2frame.GPXtree.GetItemPyData(UnitCellsId) controls = controls[:5]+10*[0.,] @@ -4483,37 +4489,42 @@ def LoadUnitCell(event): G2frame.dataWindow.RefineCell.Enable(True) OnHklShow(None,indexFrom=' Indexing from loaded unit cell & symmetry settings') wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - - def ImportUnitCell(event): - controls,bravais,cells,dminx,ssopt = G2frame.GPXtree.GetItemPyData(UnitCellsId)[:5] - reqrdr = G2frame.dataWindow.ReImportMenuId.get(event.GetId()) - rdlist = G2frame.OnImportGeneric(reqrdr, - G2frame.ImportPhaseReaderlist,'phase') - if len(rdlist) == 0: return - rd = rdlist[0] - Cell = rd.Phase['General']['Cell'] - SGData = rd.Phase['General']['SGData'] - if '1 1' in SGData['SpGrp']: - wx.MessageBox('Unusable space group',caption='Monoclinic '+SGData['SpGrp']+' not usable here',style=wx.ICON_EXCLAMATION) - return - controls[4] = 1 - controls[5] = (SGData['SGLatt']+SGData['SGLaue']).replace('-','') - if controls[5][1:] == 'm3': controls[5] += 'm' - if 'P3' in controls[5] or 'P-3' in controls[5]: controls[5] = 'P6/mmm' - if 'R' in controls[5]: controls[5] = 'R3-H' - controls[6:13] = Cell[1:8] - controls[13] = SGData['SpGrp'] - ssopt['SgResults'] = [] - G2frame.dataWindow.RefineCell.Enable(True) - OnHklShow(None,indexFrom=' Indexing from imported unit cell & symmetry settings') - wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + + # TODO: I think this used to work, but this needs to be revisited due to + # AttributeError: 'G2DataWindow' object has no attribute 'ReImportMenuId' + # from: + # reqrdr = G2frame.dataWindow.ReImportMenuId.get(event.GetId()) + # + # def ImportUnitCell(event): + # controls,bravais,cells,dminx,ssopt = G2frame.GPXtree.GetItemPyData(UnitCellsId)[:5] + # reqrdr = G2frame.dataWindow.ReImportMenuId.get(event.GetId()) + # rdlist = G2frame.OnImportGeneric(reqrdr, + # G2frame.ImportPhaseReaderlist,'phase') + # if len(rdlist) == 0: return + # rd = rdlist[0] + # Cell = rd.Phase['General']['Cell'] + # SGData = rd.Phase['General']['SGData'] + # if '1 1' in SGData['SpGrp']: + # wx.MessageBox('Unusable space group',caption='Monoclinic '+SGData['SpGrp']+' not usable here',style=wx.ICON_EXCLAMATION) + # return + # controls[4] = 1 + # controls[5] = (SGData['SGLatt']+SGData['SGLaue']).replace('-','') + # if controls[5][1:] == 'm3': controls[5] += 'm' + # if 'P3' in controls[5] or 'P-3' in controls[5]: controls[5] = 'P6/mmm' + # if 'R' in controls[5]: controls[5] = 'R3-H' + # controls[6:13] = Cell[1:8] + # controls[13] = SGData['SpGrp'] + # ssopt['SgResults'] = [] + # G2frame.dataWindow.RefineCell.Enable(True) + # OnHklShow(None,indexFrom=' Indexing from imported unit cell & symmetry settings') + # wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) + def onRefineCell(event): data = RefineCell(G2frame) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) def OnIndexPeaks(event): - PatternId = G2frame.PatternId + PatternId = G2frame.PatternId #print ('Peak Indexing') keepcells = [] try: @@ -4544,7 +4555,7 @@ def OnIndexPeaks(event): return G2frame.dataWindow.CopyCell.Enable(False) G2frame.dataWindow.RefineCell.Enable(False) - dlg = wx.ProgressDialog("Generated reflections",'0 '+" cell search for "+bravaisNames[ibrav],101, + dlg = wx.ProgressDialog("Generated reflections",'0 '+" cell search for "+bravaisNames[ibrav],101, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_SKIP|wx.PD_CAN_ABORT) #desn't work in 32 bit versions # style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT) try: @@ -4572,7 +4583,7 @@ def OnIndexPeaks(event): G2frame.dataWindow.MakeNewPhase.Enable(True) G2frame.ifX20 = True wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def SeaResSelected(event=None): '''Responds when "show" is pressed in the UnitCellsTable (search results table) or at end of UpdateUnitCellsGrid (When callSeaResSelected==True; event==None). @@ -4711,7 +4722,7 @@ def SeaResSelected(event=None): gridDisplay.ForceRefresh() G2frame.GPXtree.SetItemPyData(UnitCellsId,data) - + def MakeNewPhase(event): PhaseName = '' dlg = wx.TextEntryDialog(None,'Enter a name for this phase','Phase Name Entry','New phase', @@ -4723,7 +4734,7 @@ def MakeNewPhase(event): for Cell in cells: if Cell[-2]: break - cell = Cell[2:10] + cell = Cell[2:10] sub = G2gd.FindPhaseItem(G2frame) sub = G2frame.GPXtree.AppendItem(parent=sub,text=PhaseName) E,SGData = G2spc.SpcGroup(controls[13]) @@ -4732,7 +4743,7 @@ def MakeNewPhase(event): G2frame.GetStatusBar().SetStatusText('Change space group from '+str(controls[13])+' if needed',1) finally: dlg.Destroy() - + def TransformUnitCell(event): Trans = np.eye(3) Uvec = np.zeros(3) @@ -4757,10 +4768,10 @@ def TransformUnitCell(event): dlg.Destroy() OnHklShow(None,indexFrom=' Indexing from transformed unit cell & symmetry settings') wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnLatSym(event): 'Run Bilbao PseudoLattice cell search' - # look up a space group matching Bravais lattice (should not matter which one) + # look up a space group matching Bravais lattice (should not matter which one) bravaisSPG = {'Fm3m':225,'Im3m':229,'Pm3m':221,'R3-H':146,'P6/mmm':191, 'I4/mmm':139,'P4/mmm':123,'Fmmm':69,'Immm':71, 'Cmmm':65,'Pmmm':47,'C2/m':12,'P2/m':10,'P1':2} @@ -4776,18 +4787,15 @@ def OnLatSym(event): 'Enter angular tolerance for search',5.0,[.1,30.],"%.1f") if dlg.ShowModal() == wx.ID_OK: tolerance = dlg.GetValue() - dlg.Destroy() + dlg.Destroy() else: - dlg.Destroy() + dlg.Destroy() return - #import SUBGROUPS as kSUB wx.BeginBusyCursor() - wx.MessageBox(''' For use of PSEUDOLATTICE, please cite: - Bilbao Crystallographic Server I: Databases and crystallographic computing programs, - M. I. Aroyo, J. M. Perez-Mato, C. Capillas, E. Kroumova, S. Ivantchev, G. Madariaga, A. Kirov & H. Wondratschek - Z. Krist. 221, 1, 15-27 (2006). - doi: https://doi.org/doi:10.1524/zkri.2006.221.1.15''', - caption='Bilbao PSEUDOLATTICE',style=wx.ICON_INFORMATION) + wx.MessageBox(' For use of PSEUDOLATTICE, please cite:\n\n'+ + G2G.GetCite('Bilbao: PSEUDOLATTICE'), + caption='Bilbao PSEUDOLATTICE', + style=wx.ICON_INFORMATION) page = kSUB.subBilbaoCheckLattice(sgNum,cell,tolerance) wx.EndBusyCursor() if not page: return @@ -4800,7 +4808,7 @@ def OnLatSym(event): G2frame.GPXtree.SetItemPyData(pUCid,data) G2frame.OnFileSave(event) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnNISTLatSym(event): 'Run NIST*LATTICE cell search' pUCid = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Unit Cells List') @@ -4854,9 +4862,9 @@ def OnNISTLatSym(event): sizer.Fit(dlg) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: - dlg.Destroy() + dlg.Destroy() else: - dlg.Destroy() + dlg.Destroy() return tol = 3*[nistInput[0]]+3*[nistInput[1]] cell = controls[6:12] @@ -4867,7 +4875,7 @@ def OnNISTLatSym(event): import nistlat out = nistlat.CellSymSearch(cell, center, tolerance=tol, mode=mode,deltaV=delta) wx.EndBusyCursor() - + if not out: return cells.clear() for o in out: @@ -4914,9 +4922,8 @@ def OnNISTLatSym(event): G2frame.GPXtree.SetItemPyData(pUCid,data) G2frame.OnFileSave(event) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnRunSubs(event): - #import SUBGROUPS as kSUB G2frame.dataWindow.RunSubGroupsMag.Enable(False) pUCid = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Unit Cells List') controls,bravais,cells,dminx,ssopt,magcells = G2frame.GPXtree.GetItemPyData(pUCid) @@ -4950,7 +4957,7 @@ def OnRunSubs(event): Landau = True else: maximal = False - Landau = False + Landau = False if nkvec not in [0,3,6,9]: wx.MessageBox('Error: check your propagation vector(s)', caption='Bilbao SUBGROUPS setup error',style=wx.ICON_EXCLAMATION) @@ -4960,14 +4967,11 @@ def OnRunSubs(event): caption='Bilbao SUBGROUPS setup error',style=wx.ICON_EXCLAMATION) return wx.BeginBusyCursor() - wx.MessageBox(''' For use of SUBGROUPS, please cite: - Symmetry-Based Computational Tools for Magnetic Crystallography, - J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and M.I. Aroyo - Annu. Rev. Mater. Res. 2015. 45,217-48. - doi: https://doi.org/10.1146/annurev-matsci-070214-021008''',caption='Bilbao SUBGROUPS',style=wx.ICON_INFORMATION) - + wx.MessageBox(' For use of SUBGROUPS, please cite:\n\n'+ + G2G.GetCite('Bilbao: k-SUBGROUPSMAG'), + caption='Bilbao SUBGROUPS', + style=wx.ICON_INFORMATION) SubGroups,baseList = kSUB.GetNonStdSubgroups(SGData,kvec[:9],star,Landau) -# SUBGROUPS,baseList = kMAG.GetNonStdSubgroups(SGData,kvec[:9],star,Landau,maximal) wx.EndBusyCursor() if SubGroups is None: wx.MessageBox('Check your internet connection?',caption='Bilbao SUBGROUPS error',style=wx.ICON_EXCLAMATION) @@ -4985,7 +4989,7 @@ def OnRunSubs(event): controls[16] = baseList except IndexError: controls.append(baseList) - dlg = wx.ProgressDialog('SUBGROUPS results','Processing '+SubGroups[0][0],len(SubGroups), + dlg = wx.ProgressDialog('SUBGROUPS results','Processing '+SubGroups[0][0],len(SubGroups), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME) for ir,result in enumerate(SubGroups): dlg.Update(ir,newmsg='Processing '+result[0]) @@ -5001,34 +5005,32 @@ def OnRunSubs(event): if RVT is not None: result,Uvec,Trans = RVT phase.update(G2lat.makeBilbaoPhase(result,Uvec,Trans)) - phase['Cell'] = G2lat.TransformCell(controls[6:12],Trans) + phase['Cell'] = G2lat.TransformCell(controls[6:12],Trans) phase['maxequiv'] = maxequiv phase['nAtoms'] = len(TestAtoms(phase,controls[15],SGData,Uvec,Trans,maxequiv,maximal)) magcells.append(phase) dlg.Destroy() magcells[0]['Use'] = True SGData = magcells[0]['SGData'] - A = G2lat.cell2A(magcells[0]['Cell'][:6]) + A = G2lat.cell2A(magcells[0]['Cell'][:6]) G2frame.HKL = np.array(G2pwd.getHKLpeak(1.0,SGData,A,Inst)) G2pwpl.PlotPatterns(G2frame,extraKeys=KeyList) data = [controls,bravais,cells,dmin,ssopt,magcells] G2frame.GPXtree.SetItemPyData(pUCid,data) G2frame.OnFileSave(event) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnRunSubsMag(event,kvec1=None): - #import SUBGROUPS as kSUB - def strTest(text): if '.' in text: # no decimals return False elif text.strip() in [' ','0','1','-1','3/2']: # specials return True - elif '/' in text: #process fraction + elif '/' in text: #process fraction nums = text.split('/') return (0 < int(nums[1]) < 10) and (0 < abs(int(nums[0])) < int(nums[1])) return False - + G2frame.dataWindow.RunSubGroups.Enable(False) pUCid = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Unit Cells List') controls,bravais,cells,dminx,ssopt,magcells = G2frame.GPXtree.GetItemPyData(pUCid) @@ -5078,7 +5080,7 @@ def strTest(text): Landau = True else: maximal = False - Landau = False + Landau = False if nkvec not in [0,3,6,9]: wx.MessageBox('Error: check your propagation vector(s)', caption='Bilbao k-SUBGROUPSMAG setup error',style=wx.ICON_EXCLAMATION) @@ -5089,16 +5091,14 @@ def strTest(text): return magAtms = [atom for atom in controls[15] if atom[1] == atype] wx.BeginBusyCursor() - wx.MessageBox(''' For use of k-SUBGROUPSMAG, please cite: - Symmetry-Based Computational Tools for Magnetic Crystallography, - J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and M.I. Aroyo - Annu. Rev. Mater. Res. 2015. 45,217-48. - doi: https://doi.org/10.1146/annurev-matsci-070214-021008 and - Determining magnetic structures in GSAS-II using the Bilbao Crystallographic Server - tool k-SUBGROUPSMAG, R.B. Von Dreele & L. Elcoro, Acta Cryst. 2024, B80. - doi: https://doi.org/10.1107/S2052520624008436 - ''',caption='Bilbao k-SUBGROUPSMAG',style=wx.ICON_INFORMATION) - + wx.MessageBox( + ' For use of k-SUBGROUPSMAG in GSAS-II, please cite:\n\n'+ + G2G.GetCite('Bilbao: k-SUBGROUPSMAG')+ + '\nand\n'+ + G2G.GetCite('Bilbao+GSAS-II magnetism'), + caption='Bilbao/GSAS-II Magnetism', + style=wx.ICON_INFORMATION) + MAXMAGN,baseList = kSUB.GetNonStdSubgroupsmag(SGData,kvec[:9],star,Landau) wx.EndBusyCursor() if MAXMAGN is None: @@ -5117,9 +5117,9 @@ def strTest(text): controls[16] = baseList except IndexError: controls.append(baseList) - dlg = wx.ProgressDialog('k-SUBGROUPSMAG results','Processing '+MAXMAGN[0][0],len(MAXMAGN), + dlg = wx.ProgressDialog('k-SUBGROUPSMAG results','Processing '+MAXMAGN[0][0],len(MAXMAGN), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME) - + for ir,result in enumerate(MAXMAGN): # result is SPGP,BNS,MV,itemList,altList,superList dlg.Update(ir,newmsg='Processing '+result[0]) @@ -5135,7 +5135,7 @@ def strTest(text): if RVT is not None: result,Uvec,Trans = RVT phase.update(G2lat.makeBilbaoPhase(result,Uvec,Trans,True)) - phase['Cell'] = G2lat.TransformCell(controls[6:12],Trans) + phase['Cell'] = G2lat.TransformCell(controls[6:12],Trans) phase['aType'] = atype phase['allmom'] = allmom phase['magAtms'] = magAtms @@ -5145,7 +5145,7 @@ def strTest(text): dlg.Destroy() magcells[0]['Use'] = True SGData = magcells[0]['SGData'] - A = G2lat.cell2A(magcells[0]['Cell'][:6]) + A = G2lat.cell2A(magcells[0]['Cell'][:6]) G2frame.HKL = np.array(G2pwd.getHKLpeak(1.0,SGData,A,Inst)) G2pwpl.PlotPatterns(G2frame,extraKeys=KeyList) data = [controls,bravais,cells,dmin,ssopt,magcells] @@ -5154,46 +5154,88 @@ def strTest(text): wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) def OnISODISTORT_kvec(phase_nam): # needs attention from Yuanpeng - '''Search for - using the ISODISTORT web service + '''Search for k-vector using the ISODISTORT web service ''' def _showWebPage(event): 'Show a web page when the user presses the "show" button' import tempfile txt = event.GetEventObject().page tmp = tempfile.NamedTemporaryFile(suffix='.html',delete=False) - open(tmp.name,'w').write(txt.replace('', - '',)) + with open(tmp.name,'w') as fp: + fp.write(txt.replace('','',)) fileList.append(tmp.name) G2G.ShowWebPage('file://'+tmp.name,G2frame) + def showWebtext(txt): import tempfile tmp = tempfile.NamedTemporaryFile(suffix='.html',delete=False) - open(tmp.name,'w').write(txt.replace('', - '',)) + with open(tmp.name,'w') as fp: + fp.write(txt.replace('','',)) fileList.append(tmp.name) G2G.ShowWebPage('file://'+tmp.name,G2frame) import tempfile import re import requests - import G2export_CIF - import ISODISTORT as ISO + from GSASII.exports import G2export_CIF + from . import ISODISTORT as ISO + from fractions import Fraction isoformsite = 'https://iso.byu.edu/iso/isodistortform.php' + if not G2frame.kvecSearch['mode']: + return + + k_table = G2frame.GPXtree.GetItemPyData(UnitCellsId) + k_cells, _ = k_table[2:4] + kpoint = None + for row in range(len(k_cells)): + if UnitCellsTable.GetValue(row, 0): + kpoint = cells[row][3:6] + break + + if kpoint is None: + wx.MessageBox( + "Please select a k-vector from the table.", + style=wx.ICON_INFORMATION, + caption='Isotropic Subgroup Generation' + ) + + return + #isoscript='isocifform.php' - isoCite = '''For use of this please cite + isoCite = '''For use of this please cite H. T. Stokes, D. M. Hatch, and B. J. Campbell, ISODISTORT, ISOTROPY Software Suite, iso.byu.edu. B. J. Campbell, H. T. Stokes, D. E. Tanner, and D. M. Hatch, "ISODISPLACE: An Internet Tool for Exploring Structural Distortions." J. Appl. Cryst. 39, 607-614 (2006). ''' - #latTol,coordTol,occTol = 0.001, 0.01, 0.1 + info_str = '''CIF files of the isotropic subgroups associated with the + selected k-vector will be created. + Generated CIF files will be saved in the same directory as the + current project file. + This can take up to a few minutes. Check the terminal for progress. + ''' + wx.MessageBox( + f"{isoCite}\n\n{info_str}", + style=wx.ICON_INFORMATION, + caption='Isotropic Subgroup Generation' + ) + + wx.GetApp().Yield() + + info_msg = "Processing IRREPs for the selected k-vector. " + info_msg += "This can take up to a few minutes." + print(info_msg) + phaseID = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') - Phase = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( - G2frame,phaseID,phase_nam)) - data = Phase + phase_nam = G2frame.kvecSearch['phase'] + Phase = G2frame.GPXtree.GetItemPyData( + G2gd.GetGPXtreeItemId( + G2frame, phaseID, phase_nam + ) + ) + # data = Phase #oacomp,occomp = G2mth.phaseContents(data) #ophsnam = data['General']['Name'] fileList = [] @@ -5204,8 +5246,8 @@ def showWebtext(txt): obj.loadTree() tmp = tempfile.NamedTemporaryFile(suffix='.cif', delete=False) obj.dirname,obj.filename = os.path.split(tmp.name) - obj.phasenam = data['General']['Name'] - obj.Writer('', data['General']['Name']) + obj.phasenam = Phase['General']['Name'] + obj.Writer('', Phase['General']['Name']) parentcif = tmp.name ISOparentcif = ISO.UploadCIF(parentcif) up2 = {'filename': ISOparentcif, 'input': 'uploadparentcif'} @@ -5230,14 +5272,181 @@ def showWebtext(txt): except ValueError: break - # TODO: bring in the searched k-vector. - # we can use the `use` check box to select the k-vector to use. + def setup_kvec_input(k_vec): + """Set up the input for isodistort post request + + Args: + k_vec (str): The k-vector to use for the isodistort request. + + Returns: + dict: New entries and those need to be corrected in the data + to be used in the post request. + """ + from fractions import Fraction + + k_vec_dict = { + " 1 *GM, k16 (0,0,0)": (0, 0, 0), + " 2 *DT, k11 (0,0,g)": (0, 0, "a"), + " 3 *LD, k6 (a,a,0)": ("a", "a", 0), + " 4 *SM, k5 (a,0,0)": ("a", 0, 0), + " 5 *A, k17 (0,0,1/2)": (0, 0, Fraction(1, 2)), + " 6 *H, k15 (1/3,1/3,1/2)": ( + Fraction(1, 3), + Fraction(1, 3), + Fraction(1, 2) + ), + " 7 *K, k13 (1/3,1/3,0)": (Fraction(1, 3), Fraction(1, 3), 0), + " 8 *L, k14 (1/2,0,1/2)": (Fraction(1, 2), 0, Fraction(1, 2)), + " 9 *M, k12 (1/2,0,0)": (Fraction(1, 2), 0, 0), + "10 *P, k10 (1/3,1/3,g)": ( + Fraction(1, 3), + Fraction(1, 3), + "g" + ), + "11 *Q, k8 (a,a,1/2)": ("a", "a", Fraction(1, 2)), + "12 *R, k7 (a,0,1/2)": ("a", 0, Fraction(1, 2)), + "13 *U, k9 (1/2,0,g)": (Fraction(1, 2), 0, "g"), + "14 *B, k1 (a,b,0)": ("a", "b", 0), + "15 *C, k4 (a,a,g)": ("a", "a", "g"), + "16 *D, k3 (a,0,g)": ("a", 0, "g"), + "17 *E, k2 (a,b,1/2)": ("a", "b", Fraction(1, 2)), + "18 *GP, k0 (a,b,g)": ("a", "b", "g") + } + + def match_vector_pattern(k_vec, k_vec_dict): + """Check the k-vector against the standard form in isodistort. + + Args: + k_vec (str): The k-vector to use for the isodistort + request. + k_vec_dict (dict): The standard k-vector form in + isodistort. + + Returns: + str: The standard k-vector form in isodistort. + """ + all_matches = list() + for desc, pattern in k_vec_dict.items(): + if len(pattern) != len(k_vec): + continue + placeholders = {} + match = True + for p_val, k_val in zip(pattern, k_vec): + if isinstance(p_val, str): + if p_val.isalpha(): + if p_val not in placeholders: + placeholders[p_val] = k_val + elif placeholders[p_val] != k_val: + match = False + break + else: + match = False + break + else: + if p_val != k_val: + match = False + break + if match: + all_matches.append(desc) + + idp_params_num = 3 + for match in all_matches: + params = list() + for item in k_vec_dict[match]: + if isinstance(item, str): + params.append(item) + idp_params = list(set(params)) + if len(idp_params) <= idp_params_num: + idp_params_num = len(idp_params) + final_match = match + + return final_match + + k_vec_form = match_vector_pattern(k_vec, k_vec_dict) + + data_update = dict() + data_update['kvec1'] = k_vec_form + kvec_template = k_vec_dict[k_vec_form] + if isinstance(kvec_template[0], str): + num = k_vec[0].numerator + den = k_vec[0].denominator + data_update['kparam11'] = f"{num}/{den}" + if isinstance(kvec_template[1], str): + num = k_vec[1].numerator + den = k_vec[1].denominator + data_update['kparam21'] = f"{num}/{den}" + if isinstance(kvec_template[2], str): + num = k_vec[2].numerator + den = k_vec[2].denominator + data_update['kparam31'] = f"{num}/{den}" + + return data_update + + # The following chunk of code is for converting the k-vector from the + # conventional setting to the primitive setting. + phase_sel = G2frame.kvecSearch['phase'] + _, Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + Phase = Phases[phase_sel] + + lat_type = Phase["General"]["SGData"]["SGLatt"] + lat_sym = Phase["General"]["SGData"]["SGSys"] + if lat_sym == "trigonal": + brav_sym = "hR" + else: + brav_sym = lat_sym[0] + lat_type + + Trans = np.eye(3) + Uvec = np.zeros(3) + Vvec = np.zeros(3) + + newPhase = copy.deepcopy(Phase) + newPhase['ranId'] = ran.randint(0, sys.maxsize) + newPhase['General']['SGData'] = G2spc.SpcGroup('P 1')[1] + newPhase, _ = G2lat.TransformPhase(Phase, newPhase, Trans, + Uvec, Vvec, False) + atoms_pointer = newPhase['General']['AtomPtrs'] + + atom_coords = list() + atom_types = list() + for atom in newPhase["Atoms"]: + coord_tmp = atom[atoms_pointer[0]:atoms_pointer[0] + 3] + atom_coords.append(coord_tmp) + type_tmp = atom[atoms_pointer[1]] + atom_types.append(type_tmp) + + atom_ids = kvs.unique_id_gen(atom_types) + + cell_params = newPhase["General"]["Cell"][1:7] + lat_vectors = kvs.lat_params_to_vec(cell_params) + + hkl_refls = list() + for i in range(6): + for j in range(6): + for k in range(6): + hkl_refls.append([i, j, k]) + + k_search = kvs.kVector( + brav_sym, lat_vectors, + atom_coords, atom_ids,hkl_refls, + [0.], 0. + ) + kpoint = k_search.hklConvToPrim(kpoint) + kpoint_frac = ( + Fraction(kpoint[0]).limit_denominator(10), + Fraction(kpoint[1]).limit_denominator(10), + Fraction(kpoint[2]).limit_denominator(10) + ) + data['input'] = 'kvector' data['irrepcount'] = '1' - data['kvec1'] = ' 1 *GM, k16 (0,0,0)' + + data_update = setup_kvec_input(kpoint_frac) + for key, value in data_update.items(): + data[key] = value + data['nmodstar1'] = '0' out3 = requests.post(isoformsite, data=data).text - + try: pos = out3.index('irrep1') except ValueError: @@ -5262,9 +5471,27 @@ def _get_opt_val(opt_name, out): pos = out3.index("irrep1") pos1 = out3[pos:].index("") str_tmp = out3[pos:][:pos1] - ir_options = re.findall(r'',str_tmp) + ir_options = re.findall( + r'',str_tmp + ) + + G2frame.OnFileSave(None) + orgFilName = G2frame.GSASprojectfile + phsnam = phase_sel + # get restraints & clear geometrical restraints + resId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, 'Restraints') + Restraints = G2frame.GPXtree.GetItemPyData(resId) + resId = G2gd.GetGPXtreeItemId(G2frame, resId, phsnam) + savedRestraints = None + if phsnam in Restraints: + Restraints[phsnam]['Bond']['Bonds'] = [] + Restraints[phsnam]['Angle']['Angles'] = [] + savedRestraints = Restraints[phsnam] + del Restraints[phsnam] + orgData = copy.deepcopy(data) for ir_opt, _ in ir_options: + print("Processing irrep:", ir_opt) data["input"] = "irrep" data['irrep1'] = ir_opt out4 = requests.post(isoformsite, data=data).text @@ -5279,38 +5506,91 @@ def _get_opt_val(opt_name, out): radio_val_pattern = re.compile(r_pattern, re.IGNORECASE) radio_vals = radio_val_pattern.findall(out4) cleaned_radio_vals = [value.strip() for value in radio_vals] + iso_fn = _get_opt_val('isofilename', out4).strip() + data["isofilename"] = iso_fn for radio_val in cleaned_radio_vals: + print("Processing mode:", radio_val) data["input"] = "distort" data["origintype"] = "method2" data["orderparam"] = radio_val + '" CHECKED' - data["isofilename"] = "" out5 = requests.post(isoformsite, data=data).text - continue + out_cif = ISO.GetISOcif(out5, 2) + cif_fn_part1 = radio_val.split()[0] + cif_fn_part2_tmp = radio_val.split(")")[1].split(",")[0] + cif_fn_part2 = cif_fn_part2_tmp.split()[-1] + cif_fn = f"{phase_nam}_{cif_fn_part1}_{cif_fn_part2}.cif" + cif_fn = os.path.join(os.getcwd(), cif_fn) + with open(cif_fn, 'wb') as fl: + fl.write(out_cif.encode("utf-8")) + + try: + rdlist = G2sc.import_generic( + cif_fn, [CIFpr()], fmthint='CIF' + ) + except Exception: + continue + + rd = rdlist[0] + + key_list = [ + 'General', 'Atoms', 'Drawing', + 'Histograms', 'Pawley ref', 'RBModels' + ] + for key in key_list: + Phase[key] = rd.Phase[key] + + newname = rd.Phase['General']['Name'] + Phase['General']['Name'] = newname + + G2phsG.renamePhaseNameTop(G2frame,Phase,G2frame.PickId,Phase['General'],newname) + + G2frame.GSASprojectfile = os.path.splitext( + orgFilName + )[0] + '_' f"{phase_nam}_{cif_fn_part1}_{cif_fn_part2}.gpx" + G2IO.ProjFileSave(G2frame) - # TODO: parse out the distortion info from out5 - # 1. download the CIF file for each search result - # 2. load the CIF file as a phase and create a project for it - # 3. we may want to package up all the project files and save - # to a user specified location for later use. + # restore the original saved project + G2frame.OnFileOpen(None, filename=orgFilName, askSave=False) - os.unlink(tmp.name) + # reopen tree to the original phase + def _ShowPhase(): + phId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, 'Phases') + G2frame.GPXtree.Expand(phId) + phId = G2gd.GetGPXtreeItemId(G2frame, phId, phsnam) + G2frame.GPXtree.SelectItem(phId) + + wx.CallLater(100, _ShowPhase) + + info_msg = f'''Done with subgroup output for the selected k-vector. + Please check output files in the following directory, + {os.getcwd()} + ''' + wx.MessageBox( + info_msg, + style=wx.ICON_INFORMATION, + caption='Isotropic Subgroup Generation' + ) + + try: + os.unlink(tmp.name) + except PermissionError: + pass return - + def updateCellsWindow(event): 'called when k-vec mode is selected' wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - def OnClearCells(event): 'remove previous search results' data[2] = [] data[5] = [] ssopt['SgResults'] = [] wx.CallAfter(UpdateUnitCellsGrid, G2frame, data) - + def OnISODIST(event): phase_sel = G2frame.kvecSearch['phase'] if len(phase_sel.strip()) == 0: @@ -5320,12 +5600,12 @@ def OnISODIST(event): G2G.G2MessageBox(G2frame, err_msg, err_title) def _setupResultsGrid(grid): - '''Turn on scrolling for grid and register the grid in resizeGrids so that it will be + '''Turn on scrolling for grid and register the grid in resizeGrids so that it will be adjusted when the window is resized (also when first created). This ensure that Search Results tables display nicely - with scrollbars internal to the table (plays nicely with table - headers). See _onResizeUnitCellsList for more. + with scrollbars internal to the table (plays nicely with table + headers). See _onResizeUnitCellsList for more. ''' grid.SetScrollRate(10,10) resizeGrids.append(grid) @@ -5342,9 +5622,9 @@ def clearShowFlags(): grid.ForceRefresh() except: pass - + def disableCellEntries(mode=False): - '''Called to disable (or enable with mode==True) the widgets + '''Called to disable (or enable with mode==True) the widgets associated with entering a unit cell into the 2nd Box ''' for item in unitSizerWidgetList: @@ -5352,18 +5632,18 @@ def disableCellEntries(mode=False): item.Enable(mode) except: pass - + def _onResizeUnitCellsList(event): - '''This is called to adjust the sizes of objects in + '''This is called to adjust the sizes of objects in the dataWindow display if that window is resized. - The only task at present is to adjust the size of the - GSGrid tables (search results) to be at most the new - width of the window (less 15 pt to leave room for a + The only task at present is to adjust the size of the + GSGrid tables (search results) to be at most the new + width of the window (less 15 pt to leave room for a scrollbar) but if the grid is smaller than that width - add 15 pt for the scroll bar (might not be needed). For - the table height, make sure that each results table takes - less than 1/2 of the available vertical space. + add 15 pt for the scroll bar (might not be needed). For + the table height, make sure that each results table takes + less than 1/2 of the available vertical space. ''' try: G2frame.dataWindow.Layout() @@ -5382,19 +5662,19 @@ def _onResizeUnitCellsList(event): event.Skip() except: # can fail after window is destroyed pass -# Display of Autoindexing controls +# Display of Autoindexing controls def firstSizer(): - + def OnNcNo(event): controls[2] = NcNo.GetValue() - + def OnIfX20(event): G2frame.ifX20 = x20.GetValue() - + def OnBravais(event): Obj = event.GetEventObject() bravais[bravList.index(Obj.GetId())] = Obj.GetValue() - + firstSizer = wx.BoxSizer(wx.VERTICAL) firstSizer.Add(wx.StaticText(parent=G2frame.dataWindow, label='Autoindexing of "Index Peak List" contents',style=wx.ALIGN_CENTER),0,wx.EXPAND) @@ -5432,55 +5712,29 @@ def OnBravais(event): firstSizer.Add((5,5),0) return firstSizer -# Display of k-vector search controls +# Display of k-vector search controls def kvecSizer(): - + def OnKvecSearch(event): 'Run the k-vector search' - + try: import seekpath seekpath except: - msg = 'Performing a k-vector search requires installation of the Python seekpath package. Press Yes to install this. \n\nGSAS-II will restart after the installation.' - dlg = wx.MessageDialog(G2frame, msg,'Install package?',wx.YES_NO|wx.ICON_QUESTION) - result = wx.ID_NO + G2fil.NeededPackage({'magnetic k-vector search':['seekpath']}) + msg = 'Performing a k-vector search requires installation of the Python seekpath package. Use the Help/Add Package... to install that package.' + dlg = wx.MessageDialog(G2frame, msg,'Install seekpath package') try: - result = dlg.ShowModal() + dlg.ShowModal() finally: dlg.Destroy() - wx.GetApp().Yield() - if result != wx.ID_YES: return - wx.BeginBusyCursor() - try: # can we install via conda? - import conda.cli.python_api - conda.cli.python_api - print('Starting conda install of seekpath...') - GSASIIpath.condaInstall(['seekpath']) - print('conda install of seekpath completed') - except Exception as msg: - print(msg) - try: - print('Starting pip install of seekpath...') - GSASIIpath.pipInstall(['seekpath']) - print('pip install of seekpath completed') - except Exception as msg: - print('install of seekpath failed, sorry\n',msg) - return - finally: - wx.EndBusyCursor() - ans = G2frame.OnFileSave(None) - if not ans: return - project = os.path.abspath(G2frame.GSASprojectfile) - print(f"Restarting GSAS-II with project file {project!r}") - G2fil.openInNewTerm(project) - print ('exiting GSAS-II') - sys.exit() - + return + # msg = G2G.NISTlatUse(True) _, _, cells, _, _, _ = data wx.BeginBusyCursor() - + # grab the satellite peaks. here, gsas-ii will only grab the data # for the histogram under selection. hist_name = G2frame.GPXtree.GetItemText(G2frame.PatternId) @@ -5495,7 +5749,7 @@ def OnKvecSearch(event): G2G.G2MessageBox(G2frame, err_msg, err_title) wx.EndBusyCursor() return - + # we need to grab the instrument parameter and call gsas ii routine # to convert the satellite peaks into d-spacing. Id = G2gd.GetGPXtreeItemId(G2frame, G2frame.PatternId, 'Instrument Parameters') @@ -5504,7 +5758,7 @@ def OnKvecSearch(event): for extra_peak in peakdata["xtraPeaks"]: dsp_tmp = G2lat.Pos2dsp(Parms, extra_peak[0]) xtra_peaks_d.append(dsp_tmp) - + # select a parent phase. error message will be presented in a pop-up # window if an invalid selection is made. phase_sel = G2frame.kvecSearch['phase'] @@ -5514,7 +5768,7 @@ def OnKvecSearch(event): G2G.G2MessageBox(G2frame, err_msg, err_title) wx.EndBusyCursor() return - + # again, gsas ii will only grab the reflection lists for the # histogram under selection. also, only those phases that are being # used for the selected histogram will be included in the @@ -5530,7 +5784,7 @@ def OnKvecSearch(event): G2G.G2MessageBox(G2frame, err_msg, err_title) wx.EndBusyCursor() return - + # grab the search option. By default, we would search over those # high symmetry points only. kvs_option_map = {"HighSymPts": 0,"HighSymPts & HighSymPaths": 1,"General": 2} @@ -5551,7 +5805,7 @@ def OnKvecSearch(event): G2G.G2MessageBox(G2frame, err_msg, err_title) wx.EndBusyCursor() return - + if kx_s <= 0 or ky_s <= 0 or kz_s <= 0: err_title = "Invalid k grid input" err_msg = "The k step is less than or equal to 0. " @@ -5567,19 +5821,19 @@ def OnKvecSearch(event): dialog = wx.MessageDialog(G2frame,warn_msg,warn_title, wx.OK | wx.CANCEL | wx.ICON_INFORMATION) result = dialog.ShowModal() - + if result == wx.ID_OK: pass else: dialog.Destroy() wx.EndBusyCursor() return - + kstep = [kx_s, ky_s, kz_s] else: num_procs = None kstep = None - + # grab the user defined tolerance for determining the optimal k vector. # refer to the following link for more detailed explanation about this, # @@ -5595,10 +5849,10 @@ def OnKvecSearch(event): G2G.G2MessageBox(G2frame, err_msg, err_title) wx.EndBusyCursor() return - + _, Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() Phase = Phases[phase_sel] - + # grab the Bravais lattice type # # given the lattice type and lattice system, the Bravais lattice type @@ -5615,7 +5869,7 @@ def OnKvecSearch(event): brav_sym = "hR" else: brav_sym = lat_sym[0] + lat_type - + # grab all atomic coordinates in the P1 symmetry # # define some matrix as necessary inputs for generating the P1 @@ -5623,14 +5877,14 @@ def OnKvecSearch(event): Trans = np.eye(3) Uvec = np.zeros(3) Vvec = np.zeros(3) - + # expand the structure to P1 symmetry newPhase = copy.deepcopy(Phase) newPhase['ranId'] = ran.randint(0, sys.maxsize) newPhase['General']['SGData'] = G2spc.SpcGroup('P 1')[1] newPhase, _ = G2lat.TransformPhase(Phase,newPhase,Trans,Uvec,Vvec,False) atoms_pointer = newPhase['General']['AtomPtrs'] - + atom_coords = list() atom_types = list() for atom in newPhase["Atoms"]: @@ -5638,21 +5892,21 @@ def OnKvecSearch(event): atom_coords.append(coord_tmp) type_tmp = atom[atoms_pointer[1]] atom_types.append(type_tmp) - + # this will turn each of the atom types into a unique integer number, # which is required by the `seekpath` routine atom_ids = kvs.unique_id_gen(atom_types) - + # grab the parent unit cell and construct the lattice vectors cell_params = newPhase["General"]["Cell"][1:7] lat_vectors = kvs.lat_params_to_vec(cell_params) - + hkl_refls = list() for i in range(6): for j in range(6): for k in range(6): hkl_refls.append([i, j, k]) - + try: # if we choose option-2, we need to use the `kvec_general` module # Otherwise, the computation time would be unacceptably long. @@ -5667,16 +5921,16 @@ def OnKvecSearch(event): G2G.G2MessageBox(G2frame, err_msg, err_title) wx.EndBusyCursor() return - + k_opt = k_search.kOptFinder() k_opt_dist = k_opt[1] ave_dd = k_opt[2] max_dd = k_opt[3] k_opt = [list(k_search.kVecPrimToConv(k)) for k in k_opt[0]] - + wx.EndBusyCursor() cells.clear() - + # display the result select = False for i, k_v in enumerate(k_opt): @@ -5689,7 +5943,7 @@ def OnKvecSearch(event): cells[-1] += [0, False, False] # G2frame.OnFileSave(event) # forces save of project wx.CallAfter(UpdateUnitCellsGrid, G2frame, data, select) # refresh & select 1st result - + kvecSizer = wx.BoxSizer(wx.VERTICAL) kvecSizer.Add(wx.StaticText( parent=G2frame.dataWindow,label='k-Vector Search Mode',style=wx.ALIGN_CENTER),0,wx.EXPAND) @@ -5759,9 +6013,9 @@ def OnKvecSearch(event): kvecSizer.Add(btn) return kvecSizer -# Unit cell display controls +# Unit cell display controls def unitSizer(): - + def OnSSselect(event): if controls[5] in ['Fm3m','Im3m','Pm3m']: SSselect.SetValue(False) @@ -5771,7 +6025,7 @@ def OnSSselect(event): if 'ssSymb' not in ssopt: ssopt.update({'ssSymb':'(abg)','ModVec':[0.1,0.1,0.1],'maxH':1}) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnSelMG(event): ssopt['ssSymb'] = selMG.GetValue() Vec = ssopt['ModVec'] @@ -5780,10 +6034,10 @@ def OnSelMG(event): print (' Selecting: '+controls[13]+ssopt['ssSymb']+ 'maxH:'+str(ssopt['maxH'])) OnHklShow(event,indexFrom=' Indexing from new super group %s'%(controls[13]+ssopt['ssSymb'])) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnModVal(invalid,value,tc): OnHklShow(tc.event,indexFrom=' Indexing from new modulation vector') - + def OnMoveMod(event): Obj = event.GetEventObject() ObjId = Obj.GetId() @@ -5792,15 +6046,15 @@ def OnMoveMod(event): move = Obj.GetValue()*inc/100. Obj.SetValue(0) value = min(0.98,max(-0.98,float(valObj.GetValue())+move)) - valObj.SetValue('%.4f'%(value)) + valObj.SetValue('%.4f'%(value)) ssopt['ModVec'][Id] = value OnHklShow(event,indexFrom=' Indexing from changed modulation vector') - + def OnMaxMH(event): ssopt['maxH'] = int(maxMH.GetValue()) print (' Selecting: '+controls[13]+ssopt['ssSymb']+'maxH:'+str(ssopt['maxH'])) OnHklShow(event,indexFrom=' Indexing from new max. M') - + def OnButton(xpos,ypos): modSym = ssopt['ssSymb'].split(')')[0]+')' if modSym in ['(a0g)','(a1/2g)']: @@ -5816,7 +6070,7 @@ def OnButton(xpos,ypos): print(' Trying: %s %s modulation vector = %.3f %.3f %.3f'%(controls[13],ssopt['ssSymb'],vec[0],vec[1],vec[2])) OnHklShow(None,indexFrom=' Indexing from selected modulation vector') wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnFindOneMV(event): Peaks = np.copy(peaks[0]) print (' Trying: '+controls[13],ssopt['ssSymb']+' maxH: 1') @@ -5831,14 +6085,14 @@ def OnFindOneMV(event): elif len(result[0]) == 1: G2plt.PlotXY(G2frame,[[result[2],1./result[3]],],labelX='k',labelY='fit',newPlot=True, Title='Modulation vector search for %s%s'%(controls[13],ssopt['ssSymb'])) - + finally: dlg.Destroy() OnHklShow(event,indexFrom=' Indexing from best modulation vector') wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnFindMV(event): - + best = 1. bestSS = '' ssopt['SSgResults'] = [] @@ -5847,7 +6101,7 @@ def OnFindMV(event): if len(Vref) > 1: print(' %s skipped - too many variables to search this way'%ssSym) continue - ssopt['ssSymb'] = ssSym + ssopt['ssSymb'] = ssSym Peaks = np.copy(peaks[0]) ssopt['ModVec'] = G2spc.SSGModCheck(ssopt['ModVec'],G2spc.splitSSsym(ssSym)[0],True)[0] print (' Trying: '+controls[13]+ssSym+' maxH: 1') @@ -5867,17 +6121,17 @@ def OnFindMV(event): ssopt['ModVec'],result = G2indx.findMV(Peaks,controls,ssopt,Inst,dlg=None) G2plt.PlotXY(G2frame,[[result[2],1./result[3]],],labelX='k',labelY='fit', newPlot=True,Title='Modulation vector search for %s'%bestSS) - + wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnBravSel(event): brav = bravSel.GetString(bravSel.GetSelection()) controls[5] = brav - controls[13] = SPGlist[brav][0] + controls[13] = SPGlist[brav][0] ssopt['Use'] = False OnHklShow(event,indexFrom=' Indexing from Bravais lattice %s'%brav) wx.CallLater(100,UpdateUnitCellsGrid,G2frame,data) - + def OnSpcSel(event): controls[13] = spcSel.GetString(spcSel.GetSelection()) ssopt['SGData'] = G2spc.SpcGroup(controls[13])[1] @@ -5885,11 +6139,11 @@ def OnSpcSel(event): G2frame.dataWindow.RefineCell.Enable(True) OnHklShow(event,indexFrom=' Indexing from space group %s'%controls[13]) wx.CallLater(100,UpdateUnitCellsGrid,G2frame,data) - + def OnTryAllSG(event): '''Computes extinctions for all possible space groups - creates data for a table that is displayed in the subsequent - UpdateUnitCellsGrid call. + creates data for a table that is displayed in the subsequent + UpdateUnitCellsGrid call. ''' ssopt['SgResults'] = [] ssopt['SgSettings'] = '' @@ -5898,7 +6152,7 @@ def OnTryAllSG(event): ssopt['Use'] = False G2frame.dataWindow.RefineCell.Enable(True) res = OnHklShow(event,False,False,indexFrom=' ') - if res: + if res: ssopt['SgResults'].append(res) if ssopt['SgResults']: ssopt['SgResults'] = G2mth.sortArray(ssopt['SgResults'],2,reverse=True) @@ -5949,18 +6203,18 @@ def SetCellValue(Obj,ObjId,value): setval(controls[6+ObjId]) controls[12] = G2lat.calc_V(G2lat.cell2A(controls[6:12])) volVal.SetValue("%.3f"%(controls[12])) - + def OnMoveCell(event): Obj = event.GetEventObject() ObjId = cellList.index(Obj.GetId()) valObj = valDict[Obj.GetId()] inc = float(shiftChoices[shiftSel.GetSelection()][:-1]) - move = Obj.GetValue() # +1 or -1 + move = Obj.GetValue() # +1 or -1 Obj.SetValue(0) value = float(valObj.GetValue()) * (1. + move*inc/100.) SetCellValue(valObj,ObjId//2,value) OnHklShow(event,indexFrom=' Indexing from new cell') - + def OnCellChange(invalid,value,tc): if invalid: return @@ -5986,11 +6240,11 @@ def OnMagSel(event): ssopt['SGData'] = SGData OnHklShow(event,indexFrom=' Indexing from new spin selection') wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnSpinOp(event): Obj = event.GetEventObject() isym = Indx[Obj.GetId()]+1 - spCode = {'red':-1,'black':1} + spCode = {'red':-1,'black':1} SGData['SGSpin'][isym] = spCode[Obj.GetValue()] G2spc.CheckSpin(isym,SGData) GenSym,GenFlg,BNSsym = G2spc.GetGenSym(SGData) @@ -6000,7 +6254,7 @@ def OnSpinOp(event): SGData['SpnFlp'] = SpnFlp SGData['MagSpGrp'] = G2spc.MagSGSym(SGData) OnHklShow(event,indexFrom=' Indexing from new spin operators') - + def OnBNSlatt(event): Obj = event.GetEventObject() SGData.update(G2spc.SpcGroup(SGData['SpGrp'])[1]) @@ -6018,7 +6272,7 @@ def OnBNSlatt(event): OprNames,SpnFlp = G2spc.GenMagOps(SGData) SGData['SpnFlp'] = SpnFlp OnHklShow(event,indexFrom=' Indexing from new BNS centering') - + def OnShowSpins(event): msg = 'Magnetic space group information' text,table = G2spc.SGPrint(SGData,AddInv=True) @@ -6057,7 +6311,7 @@ def OnMakePks(event): poss = [p for p in hkl[:,4] if (x[0] <= p <= x[-1])] mags = y[np.searchsorted(x,poss)] refs = list(zip(poss,mags)) - if 'T' in Inst['Type'][0]: + if 'T' in Inst['Type'][0]: refs = G2mth.sortArray(refs,0,reverse=True) #big TOFs first else: #'C', 'E' or 'B' refs = G2mth.sortArray(refs,0,reverse=False) #small 2-Thetas or energies first @@ -6065,7 +6319,7 @@ def OnMakePks(event): for ref2 in refs[i+1:]: if abs(ref2[0]-ref1[0]) < 2.*G2pwd.getFWHM(ref1[0],inst): del(refs[i]) - if 'T' in Inst['Type'][0]: + if 'T' in Inst['Type'][0]: refs = G2mth.sortArray(refs,1,reverse=False) else: #'C', 'E' or 'B' refs = G2mth.sortArray(refs,1,reverse=True) @@ -6077,21 +6331,21 @@ def OnNewHklShow(event): disableCellEntries(True) clearShowFlags() OnHklShow(event,indexFrom=' Indexing from unit cell & symmetry settings') - + def OnExtHklShow(event): Obj = event.GetEventObject() if Obj.GetValue(): OnHklShow(event,indexFrom=' Indexing including extinct hkls') else: OnHklShow(event,indexFrom=' Indexing from unit cell & symmetry settings') - + def OnAxHklShow(): mode = Sel.GetStringSelection() if 'None' not in mode: OnHklShow(None,indexFrom=' Indexing showing only %s hkls'%mode) else: OnHklShow(None,indexFrom=' Indexing from unit cell & symmetry settings') - + unitSizer = wx.BoxSizer(wx.VERTICAL) unitSizer.Add(wx.StaticText(parent=G2frame.dataWindow,style=wx.ALIGN_CENTER, label='Unit Cell && Symmetry Settings for Reflection Display'),0,wx.EXPAND) @@ -6102,7 +6356,7 @@ def OnAxHklShow(): cellList = [] valDict = {} Info = {} - + bravSizer = wx.BoxSizer(wx.HORIZONTAL) bravSizer.Add(wx.StaticText(G2frame.dataWindow,label=" Bravais \n lattice ",style=wx.ALIGN_CENTER),0,WACV,5) bravSel = wx.Choice(G2frame.dataWindow,choices=bravaisSymb,size=(75,-1)) @@ -6133,7 +6387,7 @@ def OnAxHklShow(): if ssopt.get('Use',False): #zero for super lattice doesn't work! controls[0] = False unitSizer.Add(bravSizer,0) - + hSizer = wx.BoxSizer(wx.HORIZONTAL) cellSizer = wx.FlexGridSizer(0,min(6,useGUI[1]),3,3) for txt,fmt,ifEdit,Id in zip(*useGUI[2]): @@ -6251,7 +6505,7 @@ def OnAxHklShow(): cellDisplayOpts['Show'] = True hklShow = wx.Button(G2frame.dataWindow,label="show HKL from cell") hklShow.Bind(wx.EVT_BUTTON,OnNewHklShow) - littleSizer.Add(hklShow,0,WACV) + littleSizer.Add(hklShow,0,WACV) if 'E' not in Inst['Type'][0]: littleSizer.Add((5,-1)) if not ssopt.get('Use',False): # Show Extinct not available for super lattice @@ -6265,7 +6519,7 @@ def OnAxHklShow(): indLoc=G2frame.PlotOpts,indKey='hklHighlight',onChoice=OnAxHklShow) unitSizerWidgetList.append(Sel) littleSizer.Add(Sel,0,WACV) - + if 'E' not in Inst['Type'][0]: if 'N' in Inst['Type'][0]: littleSizer.Add((5,-1)) @@ -6281,7 +6535,7 @@ def OnAxHklShow(): makePks.Bind(wx.EVT_BUTTON,OnMakePks) littleSizer.Add(makePks,0,WACV) unitSizer.Add(littleSizer,0) - + # magnetic cell options if 'N' in Inst['Type'][0] and 'MagSpGrp' in SGData: neutSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -6303,9 +6557,9 @@ def OnAxHklShow(): spinColor = ['black','red'] spCode = {-1:'red',1:'black'} for isym,sym in enumerate(GenSym[1:]): - neutSizer.Add(wx.StaticText(G2frame.dataWindow,label=' %s: '%(sym.strip())),0,WACV) + neutSizer.Add(wx.StaticText(G2frame.dataWindow,label=' %s: '%(sym.strip())),0,WACV) spinOp = wx.ComboBox(G2frame.dataWindow,value=spCode[SGData['SGSpin'][isym+1]],choices=spinColor, - style=wx.CB_READONLY|wx.CB_DROPDOWN) + style=wx.CB_READONLY|wx.CB_DROPDOWN) Indx[spinOp.GetId()] = isym spinOp.Bind(wx.EVT_COMBOBOX,OnSpinOp) neutSizer.Add(spinOp,0,WACV) @@ -6317,10 +6571,10 @@ def OnAxHklShow(): unitSizer.Add(neutSizer,0) unitSizer.Add((5,5),0) return unitSizer - + # Space group grid def SpGrpGrid(): - + def OnSelectSgrp(event): 'Called when the Space Group Search Results show column is checked' if event is not None: @@ -6340,7 +6594,7 @@ def OnSelectSgrp(event): ssopt['SGData'] = G2spc.SpcGroup(controls[13])[1] G2frame.dataWindow.RefineCell.Enable(True) OnHklShow(event,True,indexFrom=' Space group selection %s #%d'%(controls[13],r)) - + SpGrpGrid = wx.BoxSizer(wx.VERTICAL) lbl = (' Space group search results from "Try all"'+ '\n '+ssopt.get('SgSettings','')) @@ -6370,9 +6624,9 @@ def OnSelectSgrp(event): _setupResultsGrid(SgDisplay) OnSelectSgrp(None) return SpGrpGrid - + def SSGrid(): - + def OnSelectSSG(event): 'Called when the Super Space Group Search Results show column is checked' if event is not None: @@ -6392,7 +6646,7 @@ def OnSelectSSG(event): # ssopt['SGData'] = G2spc.SpcGroup(controls[13])[1] # G2frame.dataWindow.RefineCell.Enable(True) OnHklShow(event,True,indexFrom=' Super Space group selection %s #%d'%(controls[13],r)) - + SSGrpGrid = wx.BoxSizer(wx.VERTICAL) lbl = (' Super Space group search results from "Try all"'+ '\n '+ssopt.get('SgSettings','')) @@ -6422,22 +6676,22 @@ def OnSelectSSG(event): _setupResultsGrid(SSDisplay) OnSelectSSG(None) return SSGrpGrid - + def MagSubGrid(): - + global KeyList def ClearCurrentShowNext(): KeepShowNext(False) - - KeyList += [['j',ClearCurrentShowNext,'Show next Mag. Spc. Group, clear keep flag on current']] - + + KeyList += [['j',ClearCurrentShowNext,'Show next Mag. Spc. Group, clear keep flag on current']] + def KeepCurrentShowNext(): KeepShowNext(True) - + KeyList += [['k',KeepCurrentShowNext,'Show next Mag. Spc. Group, keep current']] - + def KeepShowNext(KeepCurrent=True): - '''Show next "keep" item in Magnetic Space Group list, possibly resetting the + '''Show next "keep" item in Magnetic Space Group list, possibly resetting the keep flag for the current displayed cell ''' for i in range(len(magcells)): # find plotted setting @@ -6459,7 +6713,7 @@ def KeepShowNext(KeepCurrent=True): MagCellsTable.SetValue(next,1,True) # get SG info and plot SGData = magcells[next]['SGData'] - A = G2lat.cell2A(magcells[next]['Cell'][:6]) + A = G2lat.cell2A(magcells[next]['Cell'][:6]) G2frame.HKL = G2pwd.getHKLpeak(1.0,SGData,A,Inst) G2pwpl.PlotPatterns(G2frame,extraKeys=KeyList) magDisplay.ForceRefresh() @@ -6467,7 +6721,7 @@ def KeepShowNext(KeepCurrent=True): xscroll = G2frame.dataWindow.GetScrollPos(wx.HORIZONTAL) yscroll = magDisplay.CellToRect(next,1)[1]/G2frame.dataWindow.GetScrollPixelsPerUnit()[1] G2frame.dataWindow.Scroll(xscroll,yscroll) - + def RefreshMagCellsGrid(event): 'Display results from k-SUBGROUPSMAG in the Unit Cells tab & allow inspection of results' controls,bravais,cells,dminx,ssopt,magcells = G2frame.GPXtree.GetItemPyData(UnitCellsId) @@ -6499,7 +6753,7 @@ def RefreshMagCellsGrid(event): magDisplay.ForceRefresh() phase['Use'] = True mSGData = phase['SGData'] - A = G2lat.cell2A(phase['Cell'][:6]) + A = G2lat.cell2A(phase['Cell'][:6]) G2frame.HKL = np.array(G2pwd.getHKLpeak(1.0,mSGData,A,Inst)) G2pwpl.PlotPatterns(G2frame,extraKeys=KeyList) elif c == 2: @@ -6568,7 +6822,7 @@ def RefreshMagCellsGrid(event): data = [controls,bravais,cells,dminx,ssopt,magcells] G2frame.GPXtree.SetItemPyData(UnitCellsId,data) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + def OnRefreshKeep(event): controls,bravais,cells,dminx,ssopt,magcells = G2frame.GPXtree.GetItemPyData(UnitCellsId) c = event.GetCol() @@ -6597,7 +6851,7 @@ def OnRefreshKeep(event): magAtms = [atom for atom in controls[15] if atom[1] == atype] else: maxequiv,maximal = dlg.GetValues() - dlg = wx.ProgressDialog('Setting Keep flags','Processing '+magcells[0]['Name'],len(magcells), + dlg = wx.ProgressDialog('Setting Keep flags','Processing '+magcells[0]['Name'],len(magcells), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME) for ip,phase in enumerate(magcells): dlg.Update(ip,newmsg='Processing '+phase['Name']) @@ -6611,7 +6865,7 @@ def OnRefreshKeep(event): data = controls,bravais,cells,dminx,ssopt,magcells G2frame.GPXtree.SetItemPyData(UnitCellsId,data) wx.CallAfter(UpdateUnitCellsGrid,G2frame,data) - + MagSubGrid = wx.BoxSizer(wx.VERTICAL) itemList = [phase.get('gid',ip+1) for ip,phase in enumerate(magcells)] phaseDict = dict(zip(itemList,magcells)) @@ -6667,9 +6921,9 @@ def OnRefreshKeep(event): else: magDisplay.SetReadOnly(r,c,isReadOnly=True) MagSubGrid.Add(magDisplay) - _setupResultsGrid(magDisplay) + _setupResultsGrid(magDisplay) return MagSubGrid - + #### UpdateUnitCellsGrid code starts here # create all menubars accessed here G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.LimitMenu) # Needed below @@ -6702,7 +6956,7 @@ def OnRefreshKeep(event): G2frame.Bind(wx.EVT_MENU, OnNISTLatSym, id=G2G.wxID_NISTLATSYM) G2frame.Bind(wx.EVT_MENU, CopyUnitCell, id=G2G.wxID_COPYCELL) G2frame.Bind(wx.EVT_MENU, LoadUnitCell, id=G2G.wxID_LOADCELL) - G2frame.Bind(wx.EVT_MENU, ImportUnitCell, id=G2G.wxID_IMPORTCELL) + #G2frame.Bind(wx.EVT_MENU, ImportUnitCell, id=G2G.wxID_IMPORTCELL) G2frame.Bind(wx.EVT_MENU, TransformUnitCell, id=G2G.wxID_TRANSFORMCELL) G2frame.Bind(wx.EVT_MENU, onRefineCell, id=G2G.wxID_REFINECELL) G2frame.Bind(wx.EVT_MENU, MakeNewPhase, id=G2G.wxID_MAKENEWPHASE) @@ -6726,23 +6980,23 @@ def OnRefreshKeep(event): 'Tetragonal-I','Tetragonal-P','Orthorhombic-F','Orthorhombic-I','Orthorhombic-A', 'Orthorhombic-B','Orthorhombic-C','Orthorhombic-P', 'Monoclinic-I','Monoclinic-A','Monoclinic-C','Monoclinic-P','Triclinic','Triclinic',] - + G2frame.dataWindow.IndexPeaks.Enable(False) peaks = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Index Peak List')) if peaks: G2frame.dataWindow.IndexPeaks.Enable(True) G2frame.dataWindow.RefineCell.Enable(False) if controls[12] > 1.0 and len(peaks[0]): #if a "real" volume (i.e. not default) and peaks - G2frame.dataWindow.RefineCell.Enable(True) + G2frame.dataWindow.RefineCell.Enable(True) G2frame.dataWindow.CopyCell.Enable(False) - G2frame.dataWindow.MakeNewPhase.Enable(False) + G2frame.dataWindow.MakeNewPhase.Enable(False) G2frame.dataWindow.ExportCells.Enable(False) if cells: G2frame.dataWindow.CopyCell.Enable(True) G2frame.dataWindow.MakeNewPhase.Enable(True) G2frame.dataWindow.ExportCells.Enable(True) elif magcells: - G2frame.dataWindow.CopyCell.Enable(True) + G2frame.dataWindow.CopyCell.Enable(True) if G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Phases'): G2frame.dataWindow.LoadCell.Enable(True) pkId = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Peak List') @@ -6766,11 +7020,11 @@ def OnRefreshKeep(event): if G2frame.dataWindow.XtraPeakMode.IsChecked(): cb = G2G.G2CheckBox(parent,'Search for k-vector',G2frame.kvecSearch,'mode',OnChange=updateCellsWindow) topSizer.Add(cb,0,WACV|wx.LEFT,15) - else: + else: G2frame.kvecSearch['mode'] = False topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) - + mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) G2G.HorizontalLine(mainSizer,G2frame.dataWindow) @@ -6794,11 +7048,11 @@ def OnRefreshKeep(event): mainSizer.Add(wx.StaticText(parent=G2frame.dataWindow, label='Cell Search Results',style=wx.ALIGN_CENTER),0,wx.EXPAND,3) G2frame.dataWindow.currentGrids = [] - + # space group search results if len(ssopt.get('SgResults',[])): mainSizer.Add(SpGrpGrid()) - + # cell search results if cells: mode = 0 @@ -6884,15 +7138,12 @@ def OnRefreshKeep(event): else: gridDisplay.SetReadOnly(r,c,isReadOnly=True) if mode == 2: - #OnISODISTORT_kvec(phase_sel) # TODO: not ready yet - hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(gridDisplay) - # TODO: Add a button to call ISODISTORT - # ISObut = wx.Button(G2frame.dataWindow,label='Call ISODISTORT') - # ISObut.Bind(wx.EVT_BUTTON,OnISODIST) - # hSizer.Add(ISObut) + ISObut = wx.Button(G2frame.dataWindow,label='Call ISODISTORT') + ISObut.Bind(wx.EVT_BUTTON, OnISODISTORT_kvec) + hSizer.Add(ISObut) mainSizer.Add(hSizer) else: @@ -6902,11 +7153,11 @@ def OnRefreshKeep(event): setGrid = gridDisplay gridDisplay.SetCellValue(setRow,2,'True') # set flag in grid disableCellEntries() - + # Subgroup/magnetic s.g. search results if magcells and len(controls) > 16: mainSizer.Add(MagSubGrid()) - + # GUI creation done -- finally G2frame.Contour = False if New: @@ -6921,7 +7172,7 @@ def OnRefreshKeep(event): ##### Reflection list ################################################################################ def UpdateReflectionGrid(G2frame,data,HKLF=False,Name=''): - '''respond to selection of PWDR or HKLF Reflections data tree + '''respond to selection of PWDR or HKLF Reflections data tree item by displaying a table of reflections in the data window. Note that this is used for Single Xtal even though in pwdGUI. @@ -6964,7 +7215,7 @@ def OnPlotHKL(event): SuperVec = General.get('SuperVec',[]) else: Super = 0 - SuperVec = [] + SuperVec = [] if 'list' in str(type(data)): #single crystal data is 2 dict in list refList = data[1]['RefList'] else: #powder data is a dict of dicts; each same structure as SC 2nd dict @@ -6979,7 +7230,7 @@ def OnPlotHKL(event): controls = {'Type' : 'Fo','ifFc' : True,'HKLmax' : Hmax,'HKLmin' : Hmin, 'FoMax' : FoMax,'Zone' : '001','Layer' : 0,'Scale' : 1.0,'Super':Super,'SuperVec':SuperVec} G2plt.PlotSngl(G2frame,newPlot=True,Data=controls,hklRef=refList,Title=phaseName) - + def OnPlot3DHKL(event): '''Plots the reflections in 3D ''' @@ -7011,7 +7262,7 @@ def OnPlot3DHKL(event): 'backColor':[0,0,0],'depthFog':False,'Zclip':10.0,'cameraPos':10.,'Zstep':0.05,'viewUp':[0,1,0], 'Scale':1.0,'oldxy':[],'viewDir':[0,0,1]},'Super':Super,'SuperVec':SuperVec} G2plt.Plot3DSngl(G2frame,newPlot=True,Data=controls,hklRef=refList,Title=phaseName) - + def OnWilsonStat(event): ''' Show Wilson plot for PWDR and HKLF & return Wilson statistics <, & to console ''' @@ -7031,7 +7282,7 @@ def OnWilsonStat(event): else: wx.MessageBox('No reflection list - do Refine first',caption='Reflection plotting') return - + PE = G2elemGUI.PickElement(G2frame,ifNone=False) if PE.ShowModal() == wx.ID_OK: normEle = PE.Elem.strip() @@ -7043,13 +7294,13 @@ def OnWilsonStat(event): XY = [[Ehist[0],Ehist[1]],[Ehist[0],Ehist[2]]] G2plt.PlotXY(G2frame,XY,labelX='sin$^2$%s/%s$^2$'%(GkTheta,Gklambda), labelY=r'ln(<|F$_o$|$^2$>/%sf$^2$)'%GkSigma,newPlot=True,Title='Wilson plot') - + def OnMakeCSV(event): '''Make csv file from displayed ref table. ''' phaseName = G2frame.RefList pth = G2G.GetExportPath(G2frame) - dlg = wx.FileDialog(G2frame, 'Choose Reflection List csv file', pth, '', + dlg = wx.FileDialog(G2frame, 'Choose Reflection List csv file', pth, '', 'Reflection List (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: @@ -7067,10 +7318,10 @@ def OnMakeCSV(event): File.close() finally: dlg.Destroy() - + def MakeReflectionTable(phaseName): '''Returns a wx.grid table (G2G.Table) containing a list of all reflections - for a phase. + for a phase. ''' Super = 0 if phaseName not in ['Unknown',]: @@ -7178,8 +7429,8 @@ def setBackgroundColors(im,it): G2frame.refTable[phaseName].SetCellBackgroundColour(r,12+im+itof,wx.RED) if float(G2frame.refTable[phaseName].GetCellValue(r,3+im)) < 0: G2frame.refTable[phaseName].SetCellBackgroundColour(r,8+im,wx.RED) - - + + if not HKLF and not len(data[phaseName]): return #deleted phase? G2frame.RefList = phaseName @@ -7220,11 +7471,11 @@ def setBackgroundColors(im,it): print (phaseName) print (phases) raise Exception("how did we not find a phase name?") - + def OnToggleExt(event): G2frame.Hide = not G2frame.Hide UpdateReflectionGrid(G2frame,data,HKLF=True,Name=Name) - + def OnPageChanged(event): '''Respond to a press on a phase tab by displaying the reflections. This routine is needed because the reflection table may not have been created yet. @@ -7246,7 +7497,7 @@ def OnSelectPhase(event): finally: dlg.Destroy() - # start of UpdateReflectionGrid + # start of UpdateReflectionGrid G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ReflMenu) if not data: print ('No phases, no reflections') @@ -7288,7 +7539,7 @@ def OnSelectPhase(event): G2frame.Bind(wx.EVT_MENU, OnMakeCSV, id=G2G.wxID_CSVFROMTABLE) G2frame.Bind(wx.EVT_MENU, OnWilsonStat, id=G2G.wxID_WILSONSTAT) G2frame.dataWindow.SelectPhase.Enable(False) - + G2frame.refBook = G2G.GSNoteBook(parent=G2frame.dataWindow) mainSizer = wx.BoxSizer(wx.VERTICAL) G2frame.dataWindow.SetSizer(mainSizer) @@ -7300,7 +7551,7 @@ def OnSelectPhase(event): G2frame.refTable[phase] = G2G.GSGrid(parent=G2frame.refBook) G2frame.refTable[phase].SetRowLabelSize(60) # leave room for big numbers G2frame.refBook.AddPage(G2frame.refTable[phase],phase) - G2frame.refTable[phase].SetScrollRate(10,10) # reflection grids (inside tab) need scroll bars + G2frame.refTable[phase].SetScrollRate(10,10) # reflection grids (inside tab) need scroll bars elif len(data[phase]): #else dict for PWDR G2frame.refTable[phase] = G2G.GSGrid(parent=G2frame.refBook) G2frame.refTable[phase].SetRowLabelSize(60) @@ -7317,15 +7568,15 @@ def OnSelectPhase(event): if phaseName: ShowReflTable(phaseName) G2frame.refBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged) G2frame.dataWindow.SetDataSize() - + ################################################################################ -##### SASD/REFD Substances +##### SASD/REFD Substances ################################################################################ def UpdateSubstanceGrid(G2frame,data): '''respond to selection of SASD/REFD Substance data tree item. ''' import Substances as substFile - + def LoadSubstance(name): subst = substFile.Substances[name] ElList = subst['Elements'].keys() @@ -7359,9 +7610,9 @@ def LoadSubstance(name): data['Substances'][name]['XAnom density'] = recontrst data['Substances'][name]['XAbsorption'] = absorb data['Substances'][name]['XImag density'] = imcontrst - + def OnReloadSubstances(event): - + for name in data['Substances'].keys(): if name not in ['vacuum','unit scatter']: if 'X' in Inst['Type'][0]: @@ -7376,9 +7627,9 @@ def OnReloadSubstances(event): data['Substances'][name]['XAbsorption'] = absorb data['Substances'][name]['XImag density'] = imcontrst UpdateSubstanceGrid(G2frame,data) - + def OnLoadSubstance(event): - + names = list(substFile.Substances.keys()) names.sort() dlg = wx.SingleChoiceDialog(G2frame, 'Which substance?', 'Select substance', names, wx.CHOICEDLG_STYLE) @@ -7389,12 +7640,12 @@ def OnLoadSubstance(event): return finally: dlg.Destroy() - + data['Substances'][name] = {'Elements':{},'Volume':1.0,'Density':1.0, 'Scatt density':0.0,'Real density':0.0,'XAbsorption':0.0,'XImag density':0.0} - LoadSubstance(name) + LoadSubstance(name) UpdateSubstanceGrid(G2frame,data) - + def OnCopySubstance(event): hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) histList = GetHistsLikeSelected(G2frame) @@ -7406,10 +7657,10 @@ def OnCopySubstance(event): 'Copy substances', histList) try: if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): copyList.append(histList[i]) finally: - dlg.Destroy() + dlg.Destroy() for item in copyList: Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Instrument Parameters'))[0] @@ -7425,7 +7676,7 @@ def OnCopySubstance(event): ndata['Substances'][name]['XAbsorption'] = absorb ndata['Substances'][name]['XImag density'] = imcontrst G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Substances'),ndata) - + def OnAddSubstance(event): dlg = wx.TextEntryDialog(None,'Enter a name for this substance','Substance Name Entry','New substance', style=wx.OK|wx.CANCEL) @@ -7440,7 +7691,7 @@ def OnAddSubstance(event): if not data['Substances'][Name]['XAbsorption']: del data['Substances'][Name] UpdateSubstanceGrid(G2frame,data) - + def OnDeleteSubstance(event): TextList = [] for name in data['Substances']: @@ -7457,9 +7708,9 @@ def OnDeleteSubstance(event): finally: dlg.Destroy() del(data['Substances'][name]) - UpdateSubstanceGrid(G2frame,data) - - def OnAddElement(event): + UpdateSubstanceGrid(G2frame,data) + + def OnAddElement(event): TextList = [] for name in data['Substances']: if name not in ['vacuum','unit scatter']: @@ -7476,7 +7727,7 @@ def OnAddElement(event): finally: dlg.Destroy() UpdateSubstanceGrid(G2frame,data) - + def AddElement(name): ElList = list(data['Substances'][name]['Elements'].keys()) dlg = G2elemGUI.PickElements(G2frame,ElList) @@ -7506,7 +7757,7 @@ def AddElement(name): else: return dlg.Destroy() - + def OnDeleteElement(event): TextList = [] for name in data['Substances']: @@ -7543,9 +7794,9 @@ def OnDeleteElement(event): data['Substances'][name]['XAbsorption'] = absorb data['Substances'][name]['XImag density'] = imcontrst UpdateSubstanceGrid(G2frame,data) - + def SubstSizer(): - + def OnNum(invalid,value,tc): if invalid: return name,El,keyId = Indx[tc.GetId()] @@ -7560,7 +7811,7 @@ def OnNum(invalid,value,tc): data['Substances'][name]['XAbsorption'] = absorb data['Substances'][name]['XImag density'] = imcontrst wx.CallAfter(UpdateSubstanceGrid,G2frame,data) - + def OnVolDen(invalid,value,tc): if invalid: return name,keyId = Indx[tc.GetId()] @@ -7582,7 +7833,7 @@ def OnVolDen(invalid,value,tc): data['Substances'][name]['XAbsorption'] = absorb data['Substances'][name]['XImag density'] = imcontrst wx.CallAfter(UpdateSubstanceGrid,G2frame,data) - + def OnIsotope(event): Obj = event.GetEventObject() El,name = Indx[Obj.GetId()] @@ -7592,18 +7843,18 @@ def OnIsotope(event): data['Substances'][name]['XAbsorption'] = absorb data['Substances'][name]['XImag density'] = imcontrst wx.CallAfter(UpdateSubstanceGrid,G2frame,data) - + Indx = {} substSizer = wx.BoxSizer(wx.VERTICAL) substSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Substance list: wavelength: %.5fA'%(wave))) for name in data['Substances']: - G2G.HorizontalLine(substSizer,G2frame.dataWindow) + G2G.HorizontalLine(substSizer,G2frame.dataWindow) substSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Data for '+name+':'),0) if name == 'vacuum': substSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Not applicable'),0) elif name == 'unit scatter': substSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Scattering density,f: %.3f *10%scm%s'%(data['Substances'][name]['Scatt density'],Pwr10,Pwrm2)),0) - else: + else: elSizer = wx.FlexGridSizer(0,8,5,5) Substance = data['Substances'][name] Elems = Substance['Elements'] @@ -7628,7 +7879,7 @@ def OnIsotope(event): 0,WACV) vol = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Substances'][name],'Volume',nDig=(10,2),typeHint=float,OnLeave=OnVolDen) Indx[vol.GetId()] = [name,'Volume'] - vdsSizer.Add(vol,0,WACV) + vdsSizer.Add(vol,0,WACV) vdsSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Density: '), 0,WACV) den = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Substances'][name],'Density',nDig=(10,2),typeHint=float,OnLeave=OnVolDen) @@ -7647,16 +7898,16 @@ def OnIsotope(event): substSizer.Add(denSizer) return substSizer - # start of UpdateSubstanceGrid + # start of UpdateSubstanceGrid G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.SubstanceMenu) Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Instrument Parameters'))[0] Name = G2frame.GPXtree.GetItemPyData(G2frame.PatternId)[2] wave = G2mth.getWave(Inst) - G2frame.Bind(wx.EVT_MENU, OnLoadSubstance, id=G2G.wxID_LOADSUBSTANCE) - G2frame.Bind(wx.EVT_MENU, OnReloadSubstances, id=G2G.wxID_RELOADSUBSTANCES) + G2frame.Bind(wx.EVT_MENU, OnLoadSubstance, id=G2G.wxID_LOADSUBSTANCE) + G2frame.Bind(wx.EVT_MENU, OnReloadSubstances, id=G2G.wxID_RELOADSUBSTANCES) G2frame.Bind(wx.EVT_MENU, OnAddSubstance, id=G2G.wxID_ADDSUBSTANCE) G2frame.Bind(wx.EVT_MENU, OnCopySubstance, id=G2G.wxID_COPYSUBSTANCE) - G2frame.Bind(wx.EVT_MENU, OnDeleteSubstance, id=G2G.wxID_DELETESUBSTANCE) + G2frame.Bind(wx.EVT_MENU, OnDeleteSubstance, id=G2G.wxID_DELETESUBSTANCE) G2frame.Bind(wx.EVT_MENU, OnAddElement, id=G2G.wxID_ELEMENTADD) G2frame.Bind(wx.EVT_MENU, OnDeleteElement, id=G2G.wxID_ELEMENTDELETE) G2frame.dataWindow.ClearData() @@ -7672,7 +7923,7 @@ def OnIsotope(event): G2frame.dataWindow.SetDataSize() ################################################################################ -##### SASD Models +##### SASD Models ################################################################################ def UpdateModelsGrid(G2frame,data): @@ -7697,7 +7948,7 @@ def UpdateModelsGrid(G2frame,data): data['BackFile'] = '' if 'Pair' not in data: data['Pair'] = {'Method':'Moore','MaxRadius':100.,'NBins':100,'Errors':'User','Result':[], - 'Percent error':2.5,'Background':[0,False],'Distribution':[],'Moore':10,'Dist G':100.,} + 'Percent error':2.5,'Background':[0,False],'Distribution':[],'Moore':10,'Dist G':100.,} if 'Shapes' not in data: data['Shapes'] = {'outName':'run','NumAA':100,'Niter':1,'AAscale':1.0,'Symm':1,'bias-z':0.0, 'inflateV':1.0,'AAglue':0.0,'pdbOut':False,'boxStep':4.0} @@ -7706,7 +7957,7 @@ def UpdateModelsGrid(G2frame,data): plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':150.,'viewDir':[0,0,1],} #end patches - + def RefreshPlots(newPlot=False): PlotText = G2frame.G2plotNB.nb.GetPageText(G2frame.G2plotNB.nb.GetSelection()) if 'Powder' in PlotText: @@ -7715,8 +7966,8 @@ def RefreshPlots(newPlot=False): G2plt.PlotSASDSizeDist(G2frame) elif 'Pair' in PlotText: G2plt.PlotSASDPairDist(G2frame) - - + + def OnAddModel(event): if data['Current'] == 'Particle fit': material = 'vacuum' @@ -7738,9 +7989,9 @@ def OnAddModel(event): }) G2sasd.ModelFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) - + wx.CallAfter(UpdateModelsGrid,G2frame,data) - + def OnCopyModel(event): hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) wtFactor = G2frame.GPXtree.GetItemPyData(G2frame.PatternId)[0]['wtFactor'] @@ -7753,12 +8004,12 @@ def OnCopyModel(event): 'Copy models', histList) try: if dlg.ShowModal() == wx.ID_OK: - for i in dlg.GetSelections(): + for i in dlg.GetSelections(): copyList.append(histList[i]) finally: - dlg.Destroy() + dlg.Destroy() for item in copyList: - Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) + Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) ProfDict = G2frame.GPXtree.GetItemPyData(Id)[0] ProfDict['wtFactor'] = wtFactor newdata = copy.deepcopy(data) @@ -7768,10 +8019,10 @@ def OnCopyModel(event): BackId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,newdata['BackFile']) BackSample = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,BackId, 'Sample Parameters')) Profile[5] = BackSample['Scale'][0]*G2frame.GPXtree.GetItemPyData(BackId)[1][1] - UpdateModelsGrid(G2frame,newdata) + UpdateModelsGrid(G2frame,newdata) wx.CallAfter(UpdateModelsGrid,G2frame,data) RefreshPlots(True) - + def OnCopyFlags(event): thisModel = copy.deepcopy(data) hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) @@ -7788,7 +8039,7 @@ def OnCopyFlags(event): try: if dlg.ShowModal() == wx.ID_OK: result = dlg.GetSelections() - for i in result: + for i in result: item = histList[i] Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) newModel = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Models')) @@ -7796,7 +8047,7 @@ def OnCopyFlags(event): for ilev,level in enumerate(newModel['Particle']['Levels']): for form in level: if form in distChoice: - thisForm = thisModel['Particle']['Levels'][ilev][form] + thisForm = thisModel['Particle']['Levels'][ilev][form] for item in parmOrder: if item in thisForm: level[form][item][1] = copy.copy(thisForm[item][1]) @@ -7807,7 +8058,7 @@ def OnCopyFlags(event): level[form]['SFargs'][item][1] = copy.copy(thisForm[item][1]) finally: dlg.Destroy() - + def OnFitModelAll(event): choices = G2gd.GetGPXtreeDataNames(G2frame,['SASD',]) od = {'label_1':'Copy to next','value_1':False,'label_2':'Reverse order','value_2':False} @@ -7829,7 +8080,7 @@ def OnFitModelAll(event): dlg.Destroy() return dlg.Destroy() - dlg = wx.ProgressDialog('SASD Sequential fit','Data set name = '+names[0],len(names), + dlg = wx.ProgressDialog('SASD Sequential fit','Data set name = '+names[0],len(names), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT) wx.BeginBusyCursor() if od['value_2']: @@ -7859,7 +8110,7 @@ def OnFitModelAll(event): break else: G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,sId, 'Models'),copy.deepcopy(IModel)) - + G2sasd.ModelFxn(IProfile,IProfDict,ILimits,ISample,IModel) saveDict = {} for item in parmDict: @@ -7874,10 +8125,10 @@ def OnFitModelAll(event): dlg.Destroy() print (' ***** Small angle sequential refinement successful *****') finally: - wx.EndBusyCursor() + wx.EndBusyCursor() G2frame.GPXtree.SetItemPyData(Id,SeqResult) G2frame.GPXtree.SelectItem(Id) - + def OnFitModel(event): if data['Current'] == 'Size dist.': if not any(Sample['Contrast']): @@ -7888,7 +8139,7 @@ def OnFitModel(event): G2sasd.SizeDistribution(Profile,ProfDict,Limits,Sample,data) G2plt.PlotSASDSizeDist(G2frame) RefreshPlots(True) - + elif data['Current'] == 'Particle fit': SaveState() Results = G2sasd.ModelFit(Profile,ProfDict,Limits,Sample,data) @@ -7899,29 +8150,26 @@ def OnFitModel(event): G2sasd.ModelFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) wx.CallAfter(UpdateModelsGrid,G2frame,data) - + elif data['Current'] == 'Pair distance': SaveState() G2sasd.PairDistFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) G2plt.PlotSASDPairDist(G2frame) wx.CallAfter(UpdateModelsGrid,G2frame,data) - + elif data['Current'] == 'Shapes': SaveState() - wx.MessageBox(''' For use of SHAPES, please cite: - A New Algroithm for the Reconstruction of Protein Molecular Envelopes - from X-ray Solution Scattering Data, - J. Badger, Jour. of Appl. Chrystallogr. 2019, 52, 937-944. - doi: https://doi.org/10.1107/S1600576719009774''', - caption='Program Shapes',style=wx.ICON_INFORMATION) - dlg = wx.ProgressDialog('Running SHAPES','Cycle no.: 0 of 160',161, + wx.MessageBox(' For use of SHAPES, please cite:\n\n'+ + G2G.GetCite('SHAPES'), + caption='Program Shapes',style=wx.ICON_INFORMATION) + dlg = wx.ProgressDialog('Running SHAPES','Cycle no.: 0 of 160',161, style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME) - + data['Pair']['Result'] = [] #clear old results (if any) for now data['Pair']['Result'] = G2shapes.G2shapes(Profile,ProfDict,Limits,data,dlg) wx.CallAfter(UpdateModelsGrid,G2frame,data) - + def OnUnDo(event): DoUnDo() data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, @@ -7935,28 +8183,28 @@ def DoUnDo(): print ('Undo last refinement') file = open(G2frame.undosasd,'rb') PatternId = G2frame.PatternId - G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Models'),cPickle.load(file)) + G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Models'),pickle.load(file)) print (' Models recovered') file.close() - + def SaveState(): G2frame.undosasd = os.path.join(G2frame.dirname,'GSASIIsasd.save') file = open(G2frame.undosasd,'wb') PatternId = G2frame.PatternId for item in ['Models']: - cPickle.dump(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId,item)),file,1) + pickle.dump(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId,item)),file,1) file.close() G2frame.dataWindow.SasdUndo.Enable(True) - + def OnSelectFit(event): data['Current'] = fitSel.GetValue() wx.CallAfter(UpdateModelsGrid,G2frame,data) - + def OnCheckBox(event): Obj = event.GetEventObject() item,ind = Indx[Obj.GetId()] item[ind] = Obj.GetValue() - + def OnIntVal(event): event.Skip() Obj = event.GetEventObject() @@ -7971,15 +8219,15 @@ def OnIntVal(event): item[ind] = value def SizeSizer(): - + def OnShape(event): data['Size']['Shape'][0] = partsh.GetValue() wx.CallAfter(UpdateModelsGrid,G2frame,data) - + def OnMethod(event): data['Size']['Method'] = method.GetValue() wx.CallAfter(UpdateModelsGrid,G2frame,data) - + sizeSizer = wx.BoxSizer(wx.VERTICAL) sizeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Size distribution parameters: '),0) binSizer = wx.FlexGridSizer(0,7,5,5) @@ -7988,14 +8236,14 @@ def OnMethod(event): nbins = wx.ComboBox(G2frame.dataWindow,value=str(data['Size']['Nbins']),choices=bins, style=wx.CB_READONLY|wx.CB_DROPDOWN) Indx[nbins.GetId()] = [data['Size'],'Nbins',0] - nbins.Bind(wx.EVT_COMBOBOX,OnIntVal) + nbins.Bind(wx.EVT_COMBOBOX,OnIntVal) binSizer.Add(nbins,0,WACV) binSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Min diam.: '),0,WACV) minDias = ['10','25','50','100','150','200'] mindiam = wx.ComboBox(G2frame.dataWindow,value=str(data['Size']['MinDiam']),choices=minDias, style=wx.CB_DROPDOWN) mindiam.Bind(wx.EVT_LEAVE_WINDOW,OnIntVal) - mindiam.Bind(wx.EVT_TEXT_ENTER,OnIntVal) + mindiam.Bind(wx.EVT_TEXT_ENTER,OnIntVal) mindiam.Bind(wx.EVT_KILL_FOCUS,OnIntVal) Indx[mindiam.GetId()] = [data['Size'],'MinDiam',0] binSizer.Add(mindiam,0,WACV) @@ -8004,7 +8252,7 @@ def OnMethod(event): maxdiam = wx.ComboBox(G2frame.dataWindow,value=str(data['Size']['MaxDiam']),choices=maxDias, style=wx.CB_DROPDOWN) maxdiam.Bind(wx.EVT_LEAVE_WINDOW,OnIntVal) - maxdiam.Bind(wx.EVT_TEXT_ENTER,OnIntVal) + maxdiam.Bind(wx.EVT_TEXT_ENTER,OnIntVal) maxdiam.Bind(wx.EVT_KILL_FOCUS,OnIntVal) Indx[maxdiam.GetId()] = [data['Size'],'MaxDiam',0] binSizer.Add(maxdiam,0,WACV) @@ -8022,7 +8270,7 @@ def OnMethod(event): 'Unified disk':' Thickness: ', 'Spherical shell': ' Shell thickness'} partsh = wx.ComboBox(G2frame.dataWindow,value=str(data['Size']['Shape'][0]),choices=list(shapes.keys()), style=wx.CB_READONLY|wx.CB_DROPDOWN) - partsh.Bind(wx.EVT_COMBOBOX,OnShape) + partsh.Bind(wx.EVT_COMBOBOX,OnShape) partSizer.Add(partsh,0,WACV) if data['Size']['Shape'][0] not in ['Unified sphere',]: partSizer.Add(wx.StaticText(G2frame.dataWindow,label=shapes[data['Size']['Shape'][0]]),0,WACV) @@ -8038,7 +8286,7 @@ def OnMethod(event): style=wx.CB_READONLY|wx.CB_DROPDOWN) method.Bind(wx.EVT_COMBOBOX,OnMethod) fitSizer.Add(method,0,WACV) - iters = ['10','25','50','100','150','200'] + iters = ['10','25','50','100','150','200'] fitSizer.Add(wx.StaticText(G2frame.dataWindow,label=' No. iterations: '),0,WACV) Method = data['Size']['Method'] iter = wx.ComboBox(G2frame.dataWindow,value=str(data['Size'][Method]['Niter']),choices=iters, @@ -8065,17 +8313,17 @@ def OnMethod(event): sizeSizer.Add(fitSizer,0) return sizeSizer - + def PairSizer(): - + def OnMethod(event): data['Pair']['Method'] = method.GetValue() wx.CallAfter(UpdateModelsGrid,G2frame,data) - + def OnError(event): data['Pair']['Errors'] = error.GetValue() wx.CallAfter(UpdateModelsGrid,G2frame,data) - + def OnMaxRadEst(event): Results = G2sasd.RgFit(Profile,ProfDict,Limits,Sample,data) if not Results[0]: @@ -8083,8 +8331,8 @@ def OnMaxRadEst(event): ' Msg: '+Results[-1]+'\nYou need to rethink your selection of parameters\n'+ \ ' Model restored to previous version') RefreshPlots(True) - wx.CallAfter(UpdateModelsGrid,G2frame,data) - + wx.CallAfter(UpdateModelsGrid,G2frame,data) + def OnMooreTerms(event): data['Pair']['Moore'] = int(round(Limits[1][1]*data['Pair']['MaxRadius']/np.pi))-1 wx.CallAfter(UpdateModelsGrid,G2frame,data) @@ -8096,7 +8344,7 @@ def OnNewVal(invalid,value,tc): 'Back':data['Back'][0]} Profile[2] = G2sasd.getSASDRg(Profile[0],parmDict) RefreshPlots(True) - + pairSizer = wx.BoxSizer(wx.VERTICAL) pairSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Pair distribution parameters: '),0) binSizer = wx.FlexGridSizer(0,6,5,5) @@ -8105,7 +8353,7 @@ def OnNewVal(invalid,value,tc): nbins = wx.ComboBox(G2frame.dataWindow,value=str(data['Pair']['NBins']),choices=bins, style=wx.CB_READONLY|wx.CB_DROPDOWN) Indx[nbins.GetId()] = [data['Pair'],'NBins',0] - nbins.Bind(wx.EVT_COMBOBOX,OnIntVal) + nbins.Bind(wx.EVT_COMBOBOX,OnIntVal) binSizer.Add(nbins,0,WACV) binSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Max diam.: '),0,WACV) maxdiam = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Pair'],'MaxRadius',xmin=10.,nDig=(10,1),OnLeave=OnNewVal) @@ -8147,13 +8395,13 @@ def OnNewVal(invalid,value,tc): errorSizer.Add(percent,0,WACV) errorSizer.Add(error,0,WACV) pairSizer.Add(errorSizer,0) - return pairSizer - + return pairSizer + def ShapesSizer(): - + # def OnPDBout(event): # data['Shapes']['pdbOut'] = not data['Shapes']['pdbOut'] - + def OnShapeSelect(event): r,c = event.GetRow(),event.GetCol() for i in [1,2]: @@ -8181,49 +8429,49 @@ def OnShapeSelect(event): G2pwpl.PlotPatterns(G2frame,plotType='SASD',newPlot=True) G2plt.PlotSASDPairDist(G2frame) G2plt.PlotBeadModel(G2frame,selAtoms,plotDefaults,PDBtext) - + shapeSizer = wx.BoxSizer(wx.VERTICAL) shapeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Shape parameters:'),0) parmSizer = wx.FlexGridSizer(0,4,5,5) -#1st row +#1st row parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' No. amino acids: '),0,WACV) numAA = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'NumAA',xmin=10) parmSizer.Add(numAA,0,WACV) - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Nballs=no. amino acids*'),0,WACV) + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Nballs=no. amino acids*'),0,WACV) scaleAA = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'AAscale',xmin=0.01,xmax=10.,nDig=(10,2)) parmSizer.Add(scaleAA,0,WACV) -#2nd row - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Inflate by (1.-1.4): '),0,WACV) +#2nd row + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Inflate by (1.-1.4): '),0,WACV) inflate = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'inflateV',xmin=1.,xmax=1.4,nDig=(10,2)) parmSizer.Add(inflate,0,WACV) - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Axial symmetry (1-12): '),0,WACV) + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Axial symmetry (1-12): '),0,WACV) symm = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'Symm',xmin=1,xmax=12) parmSizer.Add(symm,0,WACV) #3rd row - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' z-axis bias (-2 to 2): '),0,WACV) + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' z-axis bias (-2 to 2): '),0,WACV) zaxis = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'bias-z',xmin=-2.,xmax=2.,nDig=(10,2)) parmSizer.Add(zaxis,0,WACV) - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' elongation (0-20): '),0,WACV) + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' elongation (0-20): '),0,WACV) glue = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'AAglue',xmin=0.,xmax=20.,nDig=(10,2)) parmSizer.Add(glue,0,WACV) #4th row - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' No. iterations (1-10): '),0,WACV) + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' No. iterations (1-10): '),0,WACV) niter = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'Niter',xmin=1,xmax=10) parmSizer.Add(niter,0,WACV) - parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Output name: '),0,WACV) + parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Output name: '),0,WACV) name = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'outName') parmSizer.Add(name,0,WACV) #last row parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Bead separation (3.5-5): '),0,WACV) beadsep = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Shapes'],'boxStep',xmin=3.5,xmax=5,nDig=(10,1)) - parmSizer.Add(beadsep,0,WACV) + parmSizer.Add(beadsep,0,WACV) # pdb = wx.CheckBox(G2frame.dataWindow,label=' Save as pdb files?: ') # pdb.SetValue(data['Shapes']['pdbOut']) -# pdb.Bind(wx.EVT_CHECKBOX, OnPDBout) +# pdb.Bind(wx.EVT_CHECKBOX, OnPDBout) # parmSizer.Add(pdb,0,WACV) - + shapeSizer.Add(parmSizer) - + if len(data['Pair'].get('Result',[])): shapeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' SHAPES run results:'),0) Atoms,Patterns,PRcalc = data['Pair']['Result'] @@ -8245,12 +8493,12 @@ def OnShapeSelect(event): ShapesResult.SetReadOnly(r,c,isReadOnly=False) else: ShapesResult.SetReadOnly(r,c,isReadOnly=True) - + shapeSizer.Add(ShapesResult,0) return shapeSizer def PartSizer(): - + FormFactors = {'Sphere':{},'Spheroid':{'Aspect ratio':[1.0,False]}, 'Cylinder':{'Length':[100.,False]},'Cylinder diam':{'Diameter':[100.,False]}, 'Cylinder AR':{'Aspect ratio':[1.0,False]},'Unified sphere':{}, @@ -8258,22 +8506,22 @@ def PartSizer(): 'Unified disk':{'Thickness':[100.,False]}, 'Unified tube':{'Length':[100.,False],'Thickness':[10.,False]}, 'Spherical shell':{'Shell thickness':[1.5,False] }, } - + StructureFactors = {'Dilute':{},'Hard sphere':{'VolFr':[0.1,False],'Dist':[100.,False]}, 'Sticky hard sphere':{'VolFr':[0.1,False],'Dist':[100.,False],'epis':[0.05,False],'Sticky':[0.2,False]}, 'Square well':{'VolFr':[0.1,False],'Dist':[100.,False],'Depth':[0.1,False],'Width':[1.,False]}, 'InterPrecipitate':{'VolFr':[0.1,False],'Dist':[100.,False]},} - + ffDistChoices = ['Sphere','Spheroid','Cylinder','Cylinder diam', 'Cylinder AR','Unified sphere','Unified rod','Unified rod AR', 'Unified disk','Unified tube','Spherical shell',] - + ffMonoChoices = ['Sphere','Spheroid','Cylinder','Cylinder AR',] - + sfChoices = ['Dilute','Hard sphere','Sticky hard sphere','Square well','InterPrecipitate',] - + slMult = 1000. - + def OnValue(event): event.Skip() Obj = event.GetEventObject() @@ -8295,7 +8543,7 @@ def OnValue(event): sldrObj.SetValue(slMult*logv) G2sasd.ModelFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) - + def OnSelect(event): Obj = event.GetEventObject() item,key = Indx[Obj.GetId()] @@ -8311,7 +8559,7 @@ def OnSelect(event): wx.CallAfter(UpdateModelsGrid,G2frame,data) G2sasd.ModelFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) - + def OnDelLevel(event): Obj = event.GetEventObject() item = Indx[Obj.GetId()] @@ -8319,7 +8567,7 @@ def OnDelLevel(event): wx.CallAfter(UpdateModelsGrid,G2frame,data) G2sasd.ModelFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) - + def OnParmSlider(event): Obj = event.GetEventObject() item,key,pvObj = Indx[Obj.GetId()] @@ -8332,7 +8580,7 @@ def OnParmSlider(event): pvObj.SetValue('%.3g'%(item[key][0])) G2sasd.ModelFxn(Profile,ProfDict,Limits,Sample,data) RefreshPlots(True) - + def SizeSizer(): sizeSizer = wx.FlexGridSizer(0,4,5,5) sizeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Distribution: '),0,WACV) @@ -8353,15 +8601,15 @@ def SizeSizer(): Indx[ffChoice.GetId()] = [level['Controls'],'FormFact'] ffChoice.Bind(wx.EVT_COMBOBOX,OnSelect) sizeSizer.Add(ffChoice,0,WACV) - + sizeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Material: '),0,WACV) matSel = wx.ComboBox(G2frame.dataWindow,value=level['Controls']['Material'], choices=list(Substances['Substances'].keys()),style=wx.CB_READONLY|wx.CB_DROPDOWN) Indx[matSel.GetId()] = [level['Controls'],'Material'] - matSel.Bind(wx.EVT_COMBOBOX,OnSelect) + matSel.Bind(wx.EVT_COMBOBOX,OnSelect) sizeSizer.Add(matSel,0,WACV) #do neutron test here? rho = Substances['Substances'][level['Controls']['Material']].get('XAnom density',0.0) - level['Controls']['Contrast'] = contrast = (rho-rhoMat)**2 + level['Controls']['Contrast'] = contrast = (rho-rhoMat)**2 sizeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Resonant X-ray contrast: '),0,WACV) sizeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' %.2f 10%scm%s'%(contrast,Pwr20,Pwrm4)),0,WACV) if 'Mono' not in level['Controls']['DistType']: @@ -8381,7 +8629,7 @@ def SizeSizer(): Best = G2sasd.Bestimate(Parms['G'][0],Parms['Rg'][0],Parms['P'][0]) sizeSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Estimated Dist B: %12.4g'%(Best)),0,WACV) return sizeSizer - + def ParmSizer(): parmSizer = wx.FlexGridSizer(0,3,5,5) parmSizer.AddGrowableCol(2,1) @@ -8396,7 +8644,7 @@ def ParmSizer(): if parm == 'MinSize': parmSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Dist '+parm,style=wx.ALIGN_CENTER)) else: - parmVar = wx.CheckBox(G2frame.dataWindow,label='Refine? Dist '+parm) + parmVar = wx.CheckBox(G2frame.dataWindow,label='Refine? Dist '+parm) parmVar.SetValue(Parms[parm][1]) parmVar.Bind(wx.EVT_CHECKBOX, OnSelect) parmSizer.Add(parmVar,0,WACV) @@ -8404,7 +8652,7 @@ def ParmSizer(): # azmthOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'azmthOff',nDig=(10,2),typeHint=float,OnLeave=OnAzmthOff) parmValue = wx.TextCtrl(G2frame.dataWindow,value='%.3g'%(Parms[parm][0]), style=wx.TE_PROCESS_ENTER) - parmValue.Bind(wx.EVT_TEXT_ENTER,OnValue) + parmValue.Bind(wx.EVT_TEXT_ENTER,OnValue) parmValue.Bind(wx.EVT_KILL_FOCUS,OnValue) parmSizer.Add(parmValue,0,WACV) if parm == 'P': @@ -8425,7 +8673,7 @@ def ParmSizer(): for iarg,Args in enumerate([FFargs,SFargs]): for parm in parmOrder: if parm in Args: - parmVar = wx.CheckBox(G2frame.dataWindow,label='Refine? '+fTypes[iarg]+parm) + parmVar = wx.CheckBox(G2frame.dataWindow,label='Refine? '+fTypes[iarg]+parm) parmVar.SetValue(Args[parm][1]) Indx[parmVar.GetId()] = [Args[parm],1] parmVar.Bind(wx.EVT_CHECKBOX, OnSelect) @@ -8433,7 +8681,7 @@ def ParmSizer(): # azmthOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'azmthOff',nDig=(10,2),typeHint=float,OnLeave=OnAzmthOff) parmValue = wx.TextCtrl(G2frame.dataWindow,value='%.3g'%(Args[parm][0]), style=wx.TE_PROCESS_ENTER) - parmValue.Bind(wx.EVT_TEXT_ENTER,OnValue) + parmValue.Bind(wx.EVT_TEXT_ENTER,OnValue) parmValue.Bind(wx.EVT_KILL_FOCUS,OnValue) parmSizer.Add(parmValue,0,WACV) value = Args[parm][0] @@ -8466,9 +8714,9 @@ def ParmSizer(): topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Matrix: '),0,WACV) matsel = wx.ComboBox(G2frame.dataWindow,value=data['Particle']['Matrix']['Name'], choices=list(Substances['Substances'].keys()),style=wx.CB_READONLY|wx.CB_DROPDOWN) - Indx[matsel.GetId()] = [data['Particle']['Matrix'],'Name'] + Indx[matsel.GetId()] = [data['Particle']['Matrix'],'Name'] matsel.Bind(wx.EVT_COMBOBOX,OnSelect) #Do neutron test here? - rhoMat = Substances['Substances'][data['Particle']['Matrix']['Name']].get('XAnom density',0.0) + rhoMat = Substances['Substances'][data['Particle']['Matrix']['Name']].get('XAnom density',0.0) topSizer.Add(matsel,0,WACV) topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Volume fraction: '),0,WACV) volfrac = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Particle']['Matrix']['VolFrac'],0,typeHint=float) @@ -8498,7 +8746,7 @@ def ParmSizer(): topLevel.Add(strfctr,0,WACV) partSizer.Add(ParmSizer(),0,wx.EXPAND) return partSizer - + def OnEsdScale(event): event.Skip() try: @@ -8510,11 +8758,11 @@ def OnEsdScale(event): ProfDict['wtFactor'] = 1./value**2 esdScale.SetValue('%.3f'%(value)) RefreshPlots(True) - + def OnBackChange(invalid,value,tc): Profile[4][:] = value RefreshPlots() - + def OnBackFile(event): #multiple backgrounds? data['BackFile'] = backFile.GetValue() if data['BackFile']: @@ -8553,12 +8801,12 @@ def OnBackFile(event): #multiple backgrounds? topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Modeling by: '),0,WACV) fitSel = wx.ComboBox(G2frame.dataWindow,value=data['Current'],choices=models, style=wx.CB_READONLY|wx.CB_DROPDOWN) - fitSel.Bind(wx.EVT_COMBOBOX,OnSelectFit) + fitSel.Bind(wx.EVT_COMBOBOX,OnSelectFit) topSizer.Add(fitSel,0,WACV) topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Error multiplier: '),0,WACV) # azmthOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'azmthOff',nDig=(10,2),typeHint=float,OnLeave=OnAzmthOff) esdScale = wx.TextCtrl(G2frame.dataWindow,value='%.3f'%(1./np.sqrt(ProfDict['wtFactor'])),style=wx.TE_PROCESS_ENTER) - esdScale.Bind(wx.EVT_TEXT_ENTER,OnEsdScale) + esdScale.Bind(wx.EVT_TEXT_ENTER,OnEsdScale) esdScale.Bind(wx.EVT_KILL_FOCUS,OnEsdScale) topSizer.Add(esdScale,0,WACV) topSizer.Add((-1,-1),1,wx.EXPAND) @@ -8571,7 +8819,7 @@ def OnBackFile(event): #multiple backgrounds? G2frame.GetStatusBar().SetStatusText('Size distribution by Maximum entropy',1) elif 'IPG' in data['Size']['Method']: G2frame.GetStatusBar().SetStatusText('Size distribution by Interior-Point Gradient',1) - mainSizer.Add(SizeSizer()) + mainSizer.Add(SizeSizer()) elif 'Particle' in data['Current']: G2frame.dataWindow.SasSeqFit.Enable(True) mainSizer.Add(PartSizer(),1,wx.EXPAND) @@ -8581,7 +8829,7 @@ def OnBackFile(event): #multiple backgrounds? elif 'Shape' in data['Current']: G2frame.dataWindow.SasSeqFit.Enable(False) mainSizer.Add(ShapesSizer(),1,wx.EXPAND) - G2G.HorizontalLine(mainSizer,G2frame.dataWindow) + G2G.HorizontalLine(mainSizer,G2frame.dataWindow) backSizer = wx.BoxSizer(wx.HORIZONTAL) backSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Background:'),0,WACV) backVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Back'],0, @@ -8599,12 +8847,12 @@ def OnBackFile(event): #multiple backgrounds? backFile = wx.ComboBox(parent=G2frame.dataWindow,value=data['BackFile'],choices=Choices, style=wx.CB_READONLY|wx.CB_DROPDOWN) backFile.Bind(wx.EVT_COMBOBOX,OnBackFile) - backSizer.Add(backFile) + backSizer.Add(backFile) mainSizer.Add(backSizer) G2frame.dataWindow.SetDataSize() ################################################################################ -##### REFD Models +##### REFD Models ################################################################################ def UpdateREFDModelsGrid(G2frame,data): @@ -8629,16 +8877,16 @@ def OnCopyModel(event): Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item) G2frame.GPXtree.SetItemPyData( G2gd.GetGPXtreeItemId(G2frame,Id,'Models'),copy.deepcopy(data)) - + def OnFitModel(event): - + SaveState() G2pwd.REFDRefine(Profile,ProfDict,Inst,Limits,Substances,data) x,xr,y = G2pwd.makeSLDprofile(data,Substances) ModelPlot(data,x,xr,y) G2pwpl.PlotPatterns(G2frame,plotType='REFD') wx.CallAfter(UpdateREFDModelsGrid,G2frame,data) - + def OnModelPlot(event): hst = G2frame.GPXtree.GetItemText(G2frame.PatternId) histList = GetFileList(G2frame,'REFD') @@ -8667,7 +8915,7 @@ def OnModelPlot(event): model = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,mId,'Models')) if len(model['Layers']) == 2: #see if any real layers defined; will be > 2 continue - Substances = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,mId,'Substances'))['Substances'] + Substances = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,mId,'Substances'))['Substances'] x,xr,y = G2pwd.makeSLDprofile(model,Substances) if od['value_1']: XY.append([xr,y]) @@ -8684,9 +8932,9 @@ def OnModelPlot(event): if od['value_1']: linePos = linePos[-1]-linePos LinePos.append(linePos) - G2plt.PlotXY(G2frame,XY,labelX=disLabel,labelY=r'$SLD,\ 10^{10}cm^{-2}$',newPlot=True, + G2plt.PlotXY(G2frame,XY,labelX=disLabel,labelY=r'$SLD,\ 10^{10}cm^{-2}$',newPlot=True, Title='Scattering length density',lines=True,names=[],vertLines=LinePos) - + def OnFitModelAll(event): choices = G2gd.GetGPXtreeDataNames(G2frame,['REFD',]) od = {'label_1':'Copy to next','value_1':False,'label_2':'Reverse order','value_2':False} @@ -8708,7 +8956,7 @@ def OnFitModelAll(event): dlg.Destroy() return dlg.Destroy() - dlg = wx.ProgressDialog('REFD Sequential fit','Data set name = '+names[0],len(names), + dlg = wx.ProgressDialog('REFD Sequential fit','Data set name = '+names[0],len(names), style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT) wx.BeginBusyCursor() if od['value_2']: @@ -8738,17 +8986,17 @@ def OnFitModelAll(event): break else: G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,sId, 'Models'),copy.deepcopy(IModel)) - + SeqResult[name] = {'variables':result[0],'varyList':varyList,'sig':sig,'Rvals':Rvals, 'covMatrix':covMatrix,'title':name,'parmDict':parmDict} else: dlg.Destroy() print (' ***** Small angle sequential refinement successful *****') finally: - wx.EndBusyCursor() + wx.EndBusyCursor() G2frame.GPXtree.SetItemPyData(Id,SeqResult) G2frame.GPXtree.SelectItem(Id) - + def ModelPlot(data,x,xr,y): laySeq = data['Layer Seq'].split() nLines = len(laySeq)+1 @@ -8764,7 +9012,7 @@ def ModelPlot(data,x,xr,y): disLabel = r'$Distance\ from\ substrate,\ \AA$' G2plt.PlotXY(G2frame,XY,labelX=disLabel,labelY=r'$SLD,\ 10^{10}cm^{-2}$',newPlot=True, Title='Scattering length density',lines=True,names=[],vertLines=[linePos,]) - + def OnUnDo(event): DoUnDo() data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, @@ -8780,55 +9028,55 @@ def DoUnDo(): print ('Undo last refinement') file = open(G2frame.undorefd,'rb') PatternId = G2frame.PatternId - G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Models'),cPickle.load(file)) + G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId, 'Models'),pickle.load(file)) print (' Model recovered') file.close() - + def SaveState(): G2frame.undorefd = os.path.join(G2frame.dirname,'GSASIIrefd.save') file = open(G2frame.undorefd,'wb') PatternId = G2frame.PatternId - cPickle.dump(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId,'Models')),file,1) + pickle.dump(G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,PatternId,'Models')),file,1) file.close() G2frame.dataWindow.REFDUndo.Enable(True) - + def ControlSizer(): - + def OnRefPos(event): data['Zero'] = refpos.GetValue() x,xr,y = G2pwd.makeSLDprofile(data,Substances) ModelPlot(data,x,xr,y) - + def OnMinSel(event): data['Minimizer'] = minSel.GetValue() - + def OnWeight(event): data['2% weight'] = weight.GetValue() - + def OnSLDplot(event): x,xr,y = G2pwd.makeSLDprofile(data,Substances) ModelPlot(data,x,xr,y) - + # def OnQ4fftplot(event): # q4fft.SetValue(False) # R,F = G2pwd.makeRefdFFT(Limits,Profile) # XY = [[R[:2500],F[:2500]],] # G2plt.PlotXY(G2frame,XY,labelX='thickness',labelY='F(R)',newPlot=True, # Title='Fourier transform',lines=True) - + def OndQSel(event): data['dQ type'] = dQSel.GetStringSelection() Recalculate() - + def NewRes(invalid,value,tc): Recalculate() - + def Recalculate(): G2pwd.REFDModelFxn(Profile,Inst,Limits,Substances,data) x,xr,y = G2pwd.makeSLDprofile(data,Substances) ModelPlot(data,x,xr,y) G2pwpl.PlotPatterns(G2frame,plotType='REFD') - + controlSizer = wx.BoxSizer(wx.VERTICAL) resol = wx.BoxSizer(wx.HORIZONTAL) choice = ['None','const '+GkDelta+'Q/Q',] @@ -8872,24 +9120,24 @@ def Recalculate(): # plotSizer.Add(q4fft,0,WACV) controlSizer.Add(plotSizer,0) return controlSizer - + def OverallSizer(): #'DualFitFile':'', 'DualFltBack':[0.0,False],'DualScale':[1.0,False] future for neutrons - more than one? - + def OnScaleRef(event): data['Scale'][1] = scaleref.GetValue() - + def OnBackRef(event): data['FltBack'][1] = backref.GetValue() - + def OnSliderMax(event): data['slider max'] = float(slidermax.GetValue()) wx.CallAfter(UpdateREFDModelsGrid,G2frame,data) - + def Recalculate(invalid,value,tc): if invalid: return - + G2pwd.REFDModelFxn(Profile,Inst,Limits,Substances,data) x,xr,y = G2pwd.makeSLDprofile(data,Substances) ModelPlot(data,x,xr,y) @@ -8917,7 +9165,7 @@ def Recalculate(invalid,value,tc): slidermax.Bind(wx.EVT_COMBOBOX,OnSliderMax) overall.Add(slidermax,0,WACV) return overall - + def LayerSizer(): #'Penetration':[0.,False]? @@ -8937,12 +9185,12 @@ def OnSelect(event): G2pwd.REFDModelFxn(Profile,Inst,Limits,Substances,data) G2pwpl.PlotPatterns(G2frame,plotType='REFD') wx.CallAfter(UpdateREFDModelsGrid,G2frame,data) - + def OnCheckBox(event): Obj = event.GetEventObject() item,parm = Indx[Obj.GetId()] data['Layers'][item][parm][1] = Obj.GetValue() - + def OnInsertLayer(event): Obj = event.GetEventObject() ind = Indx[Obj.GetId()] @@ -8951,7 +9199,7 @@ def OnInsertLayer(event): G2pwd.REFDModelFxn(Profile,Inst,Limits,Substances,data) G2pwpl.PlotPatterns(G2frame,plotType='REFD') wx.CallAfter(UpdateREFDModelsGrid,G2frame,data) - + def OnDeleteLayer(event): Obj = event.GetEventObject() ind = Indx[Obj.GetId()] @@ -8959,7 +9207,7 @@ def OnDeleteLayer(event): data['Layer Seq'] = ' '.join([str(i+1) for i in range(len(data['Layers'])-2)]) G2pwd.REFDModelFxn(Profile,Inst,Limits,Substances,data) G2pwpl.PlotPatterns(G2frame,plotType='REFD') - wx.CallAfter(UpdateREFDModelsGrid,G2frame,data) + wx.CallAfter(UpdateREFDModelsGrid,G2frame,data) def Recalculate(invalid,value,tc): if invalid: @@ -8974,12 +9222,12 @@ def Recalculate(invalid,value,tc): def OnMoveParm(event): Obj = event.GetEventObject() ilay,parm,parmObj = Indx[Obj.GetId()] - move = Obj.GetValue() # +1 or -1 + move = Obj.GetValue() # +1 or -1 Obj.SetValue(0) data['Layers'][ilay][parm][0] += move parmObj.SetValue(data['Layers'][ilay][parm][0]) Recalculate(False,1.0,None) - + def OnParmSlider(event): Obj = event.GetEventObject() ilay,parmObj = Indx[Obj.GetId()] @@ -8988,9 +9236,9 @@ def OnParmSlider(event): parmObj.SetValue(data['Layers'][ilay]['Thick'][0]) Recalculate(False,1.0,None) - Indx = {} + Indx = {} layerSizer = wx.BoxSizer(wx.VERTICAL) - + for ilay,layer in enumerate(data['Layers']): if not ilay: layerSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Top layer (superphase):'),0) @@ -9028,13 +9276,13 @@ def OnParmSlider(event): label=' Real scat. den.: %.4g'%(realScatt)),0,WACV) imagScatt = data['Layers'][ilay]['iDenMul'][0] midlayer.Add(wx.StaticText(G2frame.dataWindow, - label=' Imag scat. den.: %.4g'%(imagScatt)),0,WACV) + label=' Imag scat. den.: %.4g'%(imagScatt)),0,WACV) else: midlayer.Add(wx.StaticText(G2frame.dataWindow,label=', air or gas'),0,WACV) layerSizer.Add(midlayer) if midName == 'unit scatter': nxtlayer = wx.BoxSizer(wx.HORIZONTAL) - nxtlayer.Add(wx.StaticText(G2frame.dataWindow,label=' Real Den. : '),0,WACV) + nxtlayer.Add(wx.StaticText(G2frame.dataWindow,label=' Real Den. : '),0,WACV) nxtlayer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Layers'][ilay]['DenMul'],0, nDig=(10,4),OnLeave=Recalculate),0,WACV) varBox = wx.CheckBox(G2frame.dataWindow,label='Refine?') @@ -9042,7 +9290,7 @@ def OnParmSlider(event): varBox.SetValue(data['Layers'][ilay]['DenMul'][1]) varBox.Bind(wx.EVT_CHECKBOX, OnCheckBox) nxtlayer.Add(varBox,0,WACV) - nxtlayer.Add(wx.StaticText(G2frame.dataWindow,label=' Imag Den. : '),0,WACV) + nxtlayer.Add(wx.StaticText(G2frame.dataWindow,label=' Imag Den. : '),0,WACV) nxtlayer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Layers'][ilay]['iDenMul'],0, nDig=(10,4),OnLeave=Recalculate),0,WACV) varBox = wx.CheckBox(G2frame.dataWindow,label='Refine?') @@ -9109,10 +9357,10 @@ def OnParmSlider(event): delet.Bind(wx.EVT_BUTTON,OnDeleteLayer) newlayer.Add(delet) layerSizer.Add(newlayer) - G2G.HorizontalLine(layerSizer,G2frame.dataWindow) - + G2G.HorizontalLine(layerSizer,G2frame.dataWindow) + return layerSizer - + def OnRepSeq(event): event.Skip() stack = repseq.GetValue() @@ -9161,7 +9409,7 @@ def OnRepSeq(event): x,xr,y = G2pwd.makeSLDprofile(data,Substances) ModelPlot(data,x,xr,y) G2pwpl.PlotPatterns(G2frame,plotType='REFD') - + # start of UpdateREFDModelsGrid G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.REFDModelMenu) Substances = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Substances'))['Substances'] @@ -9190,9 +9438,9 @@ def OnRepSeq(event): G2frame.dataWindow.SetSizer(mainSizer) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Controls:')) mainSizer.Add(ControlSizer()) - G2G.HorizontalLine(mainSizer,G2frame.dataWindow) + G2G.HorizontalLine(mainSizer,G2frame.dataWindow) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Global parameters:')) - mainSizer.Add(OverallSizer()) + mainSizer.Add(OverallSizer()) G2G.HorizontalLine(mainSizer,G2frame.dataWindow) Nlayers = len(data['Layers']) if Nlayers > 2: @@ -9201,7 +9449,7 @@ def OnRepSeq(event): lineSizer = wx.BoxSizer(wx.HORIZONTAL) lineSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Layer sequence: '),0,WACV) repseq = wx.TextCtrl(G2frame.dataWindow,value = data['Layer Seq'],style=wx.TE_PROCESS_ENTER,size=(500,25)) - repseq.Bind(wx.EVT_TEXT_ENTER,OnRepSeq) + repseq.Bind(wx.EVT_TEXT_ENTER,OnRepSeq) repseq.Bind(wx.EVT_KILL_FOCUS,OnRepSeq) lineSizer.Add(repseq,0,WACV) mainSizer.Add(lineSizer) @@ -9210,11 +9458,11 @@ def OnRepSeq(event): Str += ' %d: %s'%(ilay+1,layer['Name']) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=Str)) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=' NB: Repeat sequence by e.g. 6*(1 2) ')) - G2G.HorizontalLine(mainSizer,G2frame.dataWindow) + G2G.HorizontalLine(mainSizer,G2frame.dataWindow) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Layers: scatt. densities are 10%scm%s = 10%s%s%s'%(Pwr10,Pwrm2,Pwrm6,Angstr,Pwrm2))) mainSizer.Add(LayerSizer()) G2frame.dataWindow.SetDataSize() - + ################################################################################ ##### PDF controls ################################################################################ @@ -9234,14 +9482,14 @@ def computePDF(G2frame,data): if not pId: print(key,'Entry',name,'Not found.') problem = True - continue + continue xydata[key] = G2frame.GPXtree.GetItemPyData(pId) if problem: print('PDF computation aborted') return powId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,data['Sample']['Name']) limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,powId,'Limits')) - Xlimits = [limits[1][0],limits[0][1]] #use lower limit but ignore upper limit + Xlimits = [limits[1][0],limits[0][1]] #use lower limit but ignore upper limit inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,powId,'Instrument Parameters'))[0] auxPlot = G2pwd.CalcPDF(data,inst,Xlimits,xydata) try: @@ -9256,7 +9504,7 @@ def computePDF(G2frame,data): def OptimizePDF(G2frame,data,showFit=True,maxCycles=5): '''Optimize the PDF to minimize the difference between G(r) and the expected value for - low r (-4 pi r #density). + low r (-4 pi r #density). ''' xydata = {} for key in ['Sample','Sample Bkg.','Container','Container Bkg.']: @@ -9266,20 +9514,20 @@ def OptimizePDF(G2frame,data,showFit=True,maxCycles=5): powName = data['Sample']['Name'] powId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,powName) limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,powId,'Limits')) - Xlimits = [limits[1][0],limits[0][1]] #use lower limit but ignore upper limit + Xlimits = [limits[1][0],limits[0][1]] #use lower limit but ignore upper limit inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,powId,'Instrument Parameters'))[0] res = G2pwd.OptimizePDF(data,xydata,Xlimits,inst,showFit,maxCycles) return res['success'] - + def UpdatePDFGrid(G2frame,data): '''respond to selection of PWDR PDF data tree item. - ''' - + ''' + def PDFFileSizer(): - + def FillFileSizer(fileSizer,key): #fileSizer is a FlexGridSizer(3,4) - + def OnSelectFile(event): Obj = event.GetEventObject() fileKey,itemKey,fmt = itemDict[Obj.GetId()] @@ -9291,24 +9539,24 @@ def OnSelectFile(event): mult.SetValue(data[fileKey]['Mult']) ResetFlatBkg() wx.CallAfter(OnComputePDF,None) - + def OnMoveMult(event): data[key]['Mult'] += multSpin.GetValue()*0.01 mult.SetValue(data[key]['Mult']) multSpin.SetValue(0) wx.CallAfter(OnComputePDF,None) - + def OnMult(invalid,value,tc): if invalid: return ResetFlatBkg() wx.CallAfter(OnComputePDF,None) - + def OnRefMult(event): item['Refine'] = refMult.GetValue() if item['Refine']: G2frame.GetStatusBar().SetStatusText('Be sure Mult is close to anticipated value. '+ \ 'Suggest setting Flat Bkg. to 0 before Optimize Mult',1) - + def GetExposure(backFile): dataId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'PWDR'+dataFile[4:]) dataComments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,dataId,'Comments')) @@ -9333,14 +9581,14 @@ def GetExposure(backFile): sumExp = float(item.split('=')[1]) backExp = expTime*sumExp return -dataExp/backExp - + item = data[key] fileList = [''] + GetFileList(G2frame,'PWDR') fileSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' '+key+' file:'),0,WACV) fileName = wx.ComboBox(G2frame.dataWindow,value=item['Name'],choices=fileList, style=wx.CB_READONLY|wx.CB_DROPDOWN) itemDict[fileName.GetId()] = [key,'Name','%s'] - fileName.Bind(wx.EVT_COMBOBOX,OnSelectFile) + fileName.Bind(wx.EVT_COMBOBOX,OnSelectFile) fileSizer.Add(fileName,0,) fileSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label='Multiplier:'),0,WACV) mulBox = wx.BoxSizer(wx.HORIZONTAL) @@ -9360,7 +9608,7 @@ def GetExposure(backFile): fileSizer.Add(refMult,0,WACV) else: fileSizer.Add((5,5),0) - + def ResetFlatBkg(): Smin = np.min(G2frame.GPXtree.GetItemPyData( G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'PWDR'+dataFile[4:]))[1][1]) @@ -9380,10 +9628,10 @@ def ResetFlatBkg(): Smin += Cmul*Cmin data['Flat Bkg'] = max(0,Smin) G2frame.flatBkg.SetValue(data['Flat Bkg']) - + PDFfileSizer = wx.BoxSizer(wx.VERTICAL) PDFfileSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' PDF data files: '),0) - PDFfileSizer.Add((5,5),0) + PDFfileSizer.Add((5,5),0) if 'C' in inst['Type'][0]: str = ' Sample file: PWDR%s Wavelength, A: %.5f Energy, keV: %.3f Polariz.: %.2f '%(dataFile[4:],wave,keV,polariz) PDFfileSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=str),0) @@ -9396,17 +9644,17 @@ def ResetFlatBkg(): FillFileSizer(fileSizer,key) PDFfileSizer.Add(fileSizer,0) return PDFfileSizer - + def SampleSizer(): - + def FillElemSizer(elemSizer,ElData): - + def OnElemNum(invalid,value,tc): if invalid: return data['Form Vol'] = max(10.0,SumElementVolumes()) wx.CallAfter(UpdatePDFGrid,G2frame,data) wx.CallAfter(OnComputePDF,tc.event) - + elemSizer.Add(wx.StaticText(parent=G2frame.dataWindow, label=' Element: '+'%2s'%(ElData['Symbol'])+' * '),0,WACV) num = G2G.ValidatedTxtCtrl(G2frame.dataWindow,ElData,'FormulaNo',nDig=(10,3),xmin=0.0, @@ -9415,31 +9663,31 @@ def OnElemNum(invalid,value,tc): elemSizer.Add(wx.StaticText(parent=G2frame.dataWindow, label="f': %.3f"%(ElData['fp'])+' f": %.3f'%(ElData['fpp'])+' mu: %.2f barns'%(ElData['mu']) ), 0,WACV) - + def AfterChange(invalid,value,tc): if invalid: return wx.CallAfter(UpdatePDFGrid,G2frame,data) wx.CallAfter(OnComputePDF,tc.event) - + def OnGeometry(event): data['Geometry'] = geometry.GetValue() wx.CallAfter(UpdatePDFGrid,G2frame,data) #UpdatePDFGrid(G2frame,data) wx.CallAfter(OnComputePDF,event) - + sampleSizer = wx.BoxSizer(wx.VERTICAL) if not ElList: sampleSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Sample information: fill in this 1st'),0) else: sampleSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Sample information: '),0) - sampleSizer.Add((5,5),0) + sampleSizer.Add((5,5),0) Abs = G2lat.CellAbsorption(ElList,data['Form Vol']) Trans = G2pwd.Transmission(data['Geometry'],Abs*data['Pack'],data['Diam']) elemSizer = wx.FlexGridSizer(0,3,5,1) for El in ElList: FillElemSizer(elemSizer,ElList[El]) sampleSizer.Add(elemSizer,0) - sampleSizer.Add((5,5),0) + sampleSizer.Add((5,5),0) midSizer = wx.BoxSizer(wx.HORIZONTAL) midSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Formula volume: '),0,WACV) formVol = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Form Vol',nDig=(10,3),xmin=10.0, @@ -9465,18 +9713,18 @@ def OnGeometry(event): typeHint=float,OnLeave=AfterChange) geoBox.Add(diam,0) sampleSizer.Add(geoBox,0) - sampleSizer.Add((5,5),0) + sampleSizer.Add((5,5),0) geoBox = wx.BoxSizer(wx.HORIZONTAL) geoBox.Add(wx.StaticText(G2frame.dataWindow,label=' Packing: '),0,WACV) pack = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Pack',nDig=(10,2),xmin=0.01, typeHint=float,OnLeave=AfterChange) geoBox.Add(pack,0) - geoBox.Add(wx.StaticText(G2frame.dataWindow,label=' Sample transmission: %.3f %%'%(100.*Trans)),0,WACV) + geoBox.Add(wx.StaticText(G2frame.dataWindow,label=' Sample transmission: %.3f %%'%(100.*Trans)),0,WACV) sampleSizer.Add(geoBox,0) return sampleSizer - + def SFGctrlSizer(): - + def OnOptimizePDF(event): '''Optimize Flat Bkg, BackRatio & Ruland corrections to remove spurious "intensity" from portion of G(r) with r>> import G2script as G2sc will provide access to GSASIIscriptable without changing the sys.path; also see :ref:`ScriptingShortcut`. - Note that this only affects the current Python installation. If more than one - Python installation will be used with GSAS-II (for example because different - conda environments are used), this command should be called from within each + Note that this only affects the current Python installation. If more than one + Python installation will be used with GSAS-II (for example because different + conda environments are used), this command should be called from within each Python environment. - If more than one GSAS-II installation will be used with a Python installation, + If more than one GSAS-II installation will be used with a Python installation, this shortcut can only be used with one of them. ''' f = GSASIIpath.makeScriptShortcut() @@ -129,27 +136,44 @@ def ShowVersions(): out += f" {s:12s}{pkgver}: {msg}\n" out += GSASIIpath.getG2VersionInfo() out += "\n\n" - try: + try: out += f"GSAS-II location: {GSASIIpath.path2GSAS2}\n" except: out += "GSAS-II location: not set\n" - try: - out += f"Binary location: {GSASIIpath.binaryPath}\n" + try: + if GSASIIpath.binaryPath is None: + from . import pyspg + loc = os.path.dirname(pyspg.__file__) + else: + loc = GSASIIpath.binaryPath + try: + f = os.path.join(loc,'GSASIIversion.txt') + with open(f,'r') as fp: + version = fp.readline().strip() + vnum = fp.readline().strip() + dated = f'{vnum}, {version}' + except: + dated = 'undated' + out += f"Binary location: {loc} ({dated})\n" except: out += "Binary location: not found\n" return out def LoadG2fil(): - '''Setup GSAS-II importers. + '''Setup GSAS-II importers. Delay importing this module when possible, it is slow. Multiple calls are not. Only the first does anything. ''' if len(Readers['Pwdr']) > 0: return # initialize imports + before = list(G2fil.condaRequestList.keys()) Readers['Pwdr'] = G2fil.LoadImportRoutines("pwd", "Powder_Data") Readers['Phase'] = G2fil.LoadImportRoutines("phase", "Phase") Readers['Image'] = G2fil.LoadImportRoutines("img", "Image") Readers['HKLF'] = G2fil.LoadImportRoutines('sfact','Struct_Factor') + # save list of importers that could not be loaded + Readers['importErrpkgs'] = [i for i in G2fil.condaRequestList.keys() + if i not in before] # initialize exports for obj in G2fil.LoadExportRoutines(None): @@ -172,7 +196,7 @@ def LoadG2fil(): def LoadDictFromProjFile(ProjFile): '''Read a GSAS-II project file and load items to dictionary - + :param str ProjFile: GSAS-II project (name.gpx) full file name :returns: Project,nameList, where @@ -218,10 +242,12 @@ def LoadDictFromProjFile(ProjFile): G2fil.G2Print ('\n*** Error attempt to open project file that does not exist: \n {}'. format(ProjFile)) raise IOError('GPX file {} does not exist'.format(ProjFile)) + errmsg = '' try: Project, nameList = G2stIO.GetFullGPX(ProjFile) except Exception as msg: - raise IOError(msg) + errmsg = msg + if errmsg: raise IOError(errmsg) return Project,nameList def SaveDictToProjFile(Project,nameList,ProjFile): @@ -268,10 +294,12 @@ def PreSetup(data): def SetupGeneral(data, dirname): '''Initialize phase data. ''' + errmsg = '' try: G2elem.SetupGeneral(data,dirname) except ValueError as msg: - raise G2ScriptException(msg) + errmsg = msg + if errmsg: raise G2ScriptException(errmsg) def make_empty_project(author=None, filename=None): """Creates an dictionary in the style of GSASIIscriptable, for an empty @@ -313,21 +341,21 @@ def make_empty_project(author=None, filename=None): def GenerateReflections(spcGrp,cell,Qmax=None,dmin=None,TTmax=None,wave=None): """Generates the crystallographically unique powder diffraction reflections - for a lattice and space group (see :func:`GSASIIlattice.GenHLaue`). + for a lattice and space group (see :func:`GSASIIlattice.GenHLaue`). - :param str spcGrp: A GSAS-II formatted space group (with spaces between + :param str spcGrp: A GSAS-II formatted space group (with spaces between axial fields, e.g. 'P 21 21 21' or 'P 42/m m c'). Note that non-standard - space groups, such as 'P 21/n' or 'F -1' are allowed (see - :func:`GSASIIspc.SpcGroup`). - :param list cell: A list/tuple with six unit cell constants, + space groups, such as 'P 21/n' or 'F -1' are allowed (see + :func:`GSASIIspc.SpcGroup`). + :param list cell: A list/tuple with six unit cell constants, (a, b, c, alpha, beta, gamma) with values in Angstroms/degrees. - Note that the cell constants are not checked for consistency + Note that the cell constants are not checked for consistency with the space group. - :param float Qmax: Reflections up to this Q value are computed + :param float Qmax: Reflections up to this Q value are computed (do not use with dmin or TTmax) - :param float dmin: Reflections with d-space above this value are computed + :param float dmin: Reflections with d-space above this value are computed (do not use with Qmax or TTmax) - :param float TTmax: Reflections up to this 2-theta value are computed + :param float TTmax: Reflections up to this 2-theta value are computed (do not use with dmin or Qmax, use of wave is required.) :param float wave: wavelength in Angstroms for use with TTmax (ignored otherwise.) @@ -336,16 +364,11 @@ def GenerateReflections(spcGrp,cell,Qmax=None,dmin=None,TTmax=None,wave=None): Example: - >>> import os,sys - >>> sys.path.insert(0,'/Users/toby/software/G2/GSASII') - >>> import GSASIIscriptable as G2sc - GSAS-II binary directory: /Users/toby/software/G2/GSASII/bin - 17 values read from config file /Users/toby/software/G2/GSASII/config.py >>> refs = G2sc.GenerateReflections('P 1', ... (5.,6.,7.,90.,90.,90), ... TTmax=20,wave=1) >>> for r in refs: print(r) - ... + ... [0, 0, 1, 7.0] [0, 1, 0, 6.0] [1, 0, 0, 5.0] @@ -383,21 +406,67 @@ class G2ImportException(Exception): class G2ScriptException(Exception): pass -def import_generic(filename, readerlist, fmthint=None, bank=None): +def downloadFile(URL,download_loc=None): + '''Download the URL + ''' + + import requests + fname = os.path.split(URL)[1] + if download_loc is None: + import tempfile + download_loc = tempfile.gettempdir() + elif os.path.isdir(download_loc) and os.path.exists(download_loc): + pass + elif os.path.exists(os.path.dirname(download_loc)): + download_loc,fname = os.path.split(download_loc) + pass + else: + raise G2ScriptException(f"Import error: Cannot download to {download_loc}") + G2fil.G2Print(f'Preparing to download {URL}') + response = requests.get(URL) + filename = os.path.join(download_loc,fname) + with open(filename,'wb') as fp: + fp.write(response.content) + G2fil.G2Print(f'File {filename} written') + return filename + +def import_generic(filename, readerlist, fmthint=None, bank=None, + URL=False, download_loc=None): """Attempt to import a filename, using a list of reader objects. Returns the first reader object which worked.""" + if URL is True: + filename = downloadFile(filename,download_loc) # Translated from OnImportGeneric method in GSASII.py primaryReaders, secondaryReaders = [], [] + hintcount = 0 for reader in readerlist: if fmthint is not None and fmthint not in reader.formatName: continue + hintcount += 1 flag = reader.ExtensionValidator(filename) if flag is None: secondaryReaders.append(reader) elif flag: primaryReaders.append(reader) if not secondaryReaders and not primaryReaders: - raise G2ImportException("Could not read file: ", filename) + # common reason for read error -- package needed? + l = [] + for i in Readers['importErrpkgs']: + for j in G2fil.condaRequestList[i]: + if j not in l: l.append(j) + print(f'\nReading of file {filename!r} failed.') + if fmthint is not None and hintcount == 0: + print(f'\nNo readers matched hint {fmthint!r}\n') + if Readers['importErrpkgs']: + print('Not all importers are available due to uninstalled Python packages.') + print('The following importer(s) are not available:\n'+ + f'\t{", ".join(Readers["importErrpkgs"])}') + print('because the following optional Python package(s) are not installed:\n'+ + f'\t{", ".join(l)}\n') + print('Available importers:') + for reader in readerlist: + print(f'\t{reader.longFormatName}') + raise G2ImportException(f"Could not read file: {filename}") with open(filename, 'r'): rd_list = [] @@ -454,7 +523,7 @@ def import_generic(filename, readerlist, fmthint=None, bank=None): G2fil.G2Print("{} block # {} read by Reader {}" .format(filename,bank,rd.formatName)) return rd_list - raise G2ImportException("No reader could read file: " + filename) + raise G2ImportException(f"No reader could read file: {filename}") def load_iprms(instfile, reader, bank=None): @@ -470,8 +539,8 @@ def load_iprms(instfile, reader, bank=None): # New GSAS File, load appropriate bank with open(instfile) as f: lines = f.readlines() - if bank is None: - bank = reader.powderentry[2] + if bank is None: + bank = reader.powderentry[2] nbank,iparms = G2fil.ReadInstprm(lines, bank, reader.Sample) reader.instfile = instfile @@ -496,13 +565,12 @@ def load_iprms(instfile, reader, bank=None): if key[4:6] == " ": Iparm[key] = IparmC[key] elif int(key[4:6].strip()) == bank: - Iparm[key[:4]+' 1'+key[6:]] = IparmC[key] + Iparm[key[:4]+' 1'+key[6:]] = IparmC[key] reader.instbank = bank elif ibanks == 1: reader.instbank = 1 - else: - raise G2ImportException("Instrument parameter file has {} banks, select one with instbank param." - .format(ibanks)) + else: + raise G2ImportException(f"Instrument parameter file has {ibanks} banks, select one with instbank param.") reader.powderentry[2] = 1 reader.instfile = instfile reader.instmsg = '{} bank {}'.format(instfile,reader.instbank) @@ -591,11 +659,11 @@ def load_pwd_from_reader(reader, instprm, existingnames=[],bank=None): Tmin = np.min(reader.powderdata[0]) Tmax = np.max(reader.powderdata[0]) Tmin1 = Tmin - if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4: + if 'NT' in Iparm1['Type'][0] and G2lat.Pos2dsp(Iparm1,Tmin) < 0.4: Tmin1 = G2lat.Dsp2pos(Iparm1,0.4) default_background = [['chebyschev-1', False, 3, 1.0, 0.0, 0.0], - {'nDebye': 0, 'debyeTerms': [], 'nPeaks': 0, + {'nDebye': 0, 'debyeTerms': [], 'nPeaks': 0, 'peaksList': [],'background PWDR':['',1.0,False]}] output_dict = {'Reflection Lists': {}, @@ -666,7 +734,7 @@ def _getCorrImage(ImageReaderlist,proj,imageRef): :param list ImageReaderlist: list of Reader objects for images :param object proj: references a :class:`G2Project` project - :param imageRef: A reference to the desired image in the project. + :param imageRef: A reference to the desired image in the project. Either the Image tree name (str), the image's index (int) or a image object (:class:`G2Image`) @@ -698,7 +766,7 @@ def _getCorrImage(ImageReaderlist,proj,imageRef): raise Exception('Error reading dark image {}'.format(imagefile)) sumImg += np.array(darkImage*darkScale,dtype=np.int32) if 'background image' in Controls: - backImg,backScale = Controls['background image'] + backImg,backScale = Controls['background image'] if backImg: #ignores any transmission effect in the background image bImgObj = proj.image(backImg) formatName = bImgObj.data['Image Controls'].get('formatName','') @@ -728,7 +796,7 @@ def _getCorrImage(ImageReaderlist,proj,imageRef): return np.asarray(sumImg,dtype=np.int32) def _constr_type(var): - '''returns the constraint type based on phase/histogram use + '''returns the constraint type based on phase/histogram use in a variable ''' if var.histogram and var.phase: @@ -739,7 +807,7 @@ def _constr_type(var): return 'Hist' else: return 'Global' - + class G2ObjectWrapper(object): """Base class for all GSAS-II object wrappers. @@ -773,25 +841,25 @@ def items(self): return self.data.items() -class G2Project(G2ObjectWrapper): - """Represents an entire GSAS-II project. The object contains these +class G2Project(G2ObjectWrapper): + """Represents an entire GSAS-II project. The object contains these class variables: * G2Project.filename: contains the .gpx filename - * G2Project.names: contains the contents of the project "tree" as a list - of lists. Each top-level entry in the tree is an item in the list. The + * G2Project.names: contains the contents of the project "tree" as a list + of lists. Each top-level entry in the tree is an item in the list. The name of the top-level item is the first item in the inner list. Children of that item, if any, are subsequent entries in that list. - * G2Project.data: contains the entire project as a dict. The keys + * G2Project.data: contains the entire project as a dict. The keys for the dict are the top-level names in the project tree (initial items in the G2Project.names inner lists) and each top-level item is stored - as a dict. + as a dict. - * The contents of Top-level entries will be found in the item - named 'data', as an example, ``G2Project.data['Notebook']['data']`` + * The contents of Top-level entries will be found in the item + named 'data', as an example, ``G2Project.data['Notebook']['data']`` * The contents of child entries will be found in the item - using the names of the parent and child, for example + using the names of the parent and child, for example ``G2Project.data['Phases']['NaCl']`` :param str gpxfile: Existing .gpx file to be loaded. If nonexistent, @@ -799,29 +867,29 @@ class variables: :param str author: Author's name (not yet implemented) :param str newgpx: The filename the project should be saved to in the future. If both newgpx and gpxfile are present, the project is - loaded from the file named by gpxfile and then when saved will + loaded from the file named by gpxfile and then when saved will be written to the file named by newgpx. - :param str filename: To be deprecated. Serves the same function as newgpx, - which has a somewhat more clear name. - (Do not specify both newgpx and filename). + :param str filename: To be deprecated. Serves the same function as newgpx, + which has a somewhat more clear name. + (Do not specify both newgpx and filename). There are two ways to initialize this object: >>> # Load an existing project file >>> proj = G2Project('filename.gpx') - + >>> # Create a new project >>> proj = G2Project(newgpx='new_file.gpx') - + Histograms can be accessed easily. >>> # By name >>> hist = proj.histogram('PWDR my-histogram-name') - + >>> # Or by index >>> hist = proj.histogram(0) >>> assert hist.id == 0 - + >>> # Or by random id >>> assert hist == proj.histogram(hist.ranId) @@ -836,9 +904,9 @@ class variables: 'instrument_parameters.prm') >>> phase = proj.add_phase('my_phase.cif', histograms=[hist]) - Parameters for Rietveld refinement can be turned on and off at the project level - as well as described in - :meth:`~G2Project.set_refinement`, :meth:`~G2Project.iter_refinements` and + Parameters for Rietveld refinement can be turned on and off at the project level + as well as described in + :meth:`~G2Project.set_refinement`, :meth:`~G2Project.iter_refinements` and :meth:`~G2Project.do_refinements`. """ def __init__(self, gpxfile=None, author=None, filename=None, newgpx=None): @@ -862,7 +930,7 @@ def __init__(self, gpxfile=None, author=None, filename=None, newgpx=None): if not os.path.exists(dr): raise Exception("Directory {} for filename/newgpx does not exist".format(dr)) self.filename = filename - else: + else: self.filename = os.path.abspath(os.path.expanduser(gpxfile)) else: raise ValueError("Not sure what to do with gpxfile {}. Does not exist?".format(gpxfile)) @@ -902,17 +970,21 @@ def save(self, filename=None): try: modList += [sp] except NameError: - pass + pass controls_data['PythonVersions'] = G2fil.get_python_versions(modList) - # G2 version info + # G2 version info controls_data['LastSavedUsing'] = str(GSASIIpath.GetVersionNumber()) - if GSASIIpath.HowIsG2Installed().startswith('git'): - try: + try: + if GSASIIpath.HowIsG2Installed().startswith('git'): g2repo = GSASIIpath.openGitRepo(GSASIIpath.path2GSAS2) commit = g2repo.head.commit - controls_data['LastSavedUsing'] += f" git {commit.hexsha[:6]} script" - except: - pass + controls_data['LastSavedUsing'] += f" git {commit.hexsha[:8]} script" + else: + gv = getSavedVersionInfo() + if gv is not None: + Controls['LastSavedUsing'] += f" static {gv.git_version[:8]}" + except: + pass # .gpx name if filename: filename = os.path.abspath(os.path.expanduser(filename)) @@ -923,61 +995,73 @@ def save(self, filename=None): SaveDictToProjFile(self.data, self.names, self.filename) def add_powder_histogram(self, datafile, iparams=None, phases=[], - fmthint=None, - databank=None, instbank=None, multiple=False): - """Loads a powder data histogram or multiple powder histograms + fmthint=None, databank=None, instbank=None, + multiple=False, URL=False): + """Loads a powder data histogram or multiple powder histograms into the project. - Note that the data type (x-ray/CW neutron/TOF) for the histogram + Note that the data type (x-ray/CW neutron/TOF) for the histogram will be set from the instrument parameter file. The instrument - geometry is assumed to be Debye-Scherrer except for - dual-wavelength x-ray, where Bragg-Brentano is assumed. + geometry is assumed to be Debye-Scherrer except for + dual-wavelength x-ray, where Bragg-Brentano is assumed. :param str datafile: A filename with the powder data file to read. Note that in unix fashion, "~" can be used to indicate the home directory (e.g. ~/G2data/data.fxye). - :param str iparams: A filenme for an instrument parameters file, + :param str iparams: A filename for an instrument parameters file, or a pair of instrument parameter dicts from :func:`load_iprms`. - This may be omitted for readers that provide the instrument + This may be omitted for readers that provide the instrument parameters in the file. (Only a few importers do this.) :param list phases: A list of phases to link to the new histogram, phases can be references by object, name, rId or number. - Alternately, use 'all' to link to all phases in the project. + Alternately, use 'all' to link to all phases in the project. :param str fmthint: If specified, only importers where the format name (reader.formatName, as shown in Import menu) contains the supplied string will be tried as importers. If not specified, all importers consistent with the file extension will be tried (equivalent to "guess format" in menu). - :param int databank: Specifies a dataset number to read, if file contains - more than set of data. This should be 1 to read the first bank in + :param int databank: Specifies a dataset number to read, if file contains + more than set of data. This should be 1 to read the first bank in the file (etc.) regardless of the number on the Bank line, etc. - Default is None which means the first dataset in the file is read. - When multiple is True, optionally a list of dataset numbers can + Default is None which means the first dataset in the file is read. + When multiple is True, optionally a list of dataset numbers can be supplied here. - :param int instbank: Specifies an instrument parameter set to read, if - the instrument parameter file contains more than set of parameters. - This will match the INS # in an GSAS type file so it will typically - be 1 to read the first parameter set in the file (etc.) - Default is None which means there should only be one parameter set + :param int instbank: Specifies an instrument parameter set to read, if + the instrument parameter file contains more than set of parameters. + This will match the INS # in an GSAS type file so it will typically + be 1 to read the first parameter set in the file (etc.) + Default is None which means there should only be one parameter set in the file. - :param bool multiple: If False (default) only one dataset is read, but if + :param bool multiple: If False (default) only one dataset is read, but if specified as True, all selected banks of data (see databank) - are read in. + are read in. + :param bool URL: if True, the contents of datafile is a URL and + if not a dict, the contents of iparams is also a URL. + both files will be downloaded to a temporary location + and read. The downloaded files will not be saved. + If URL is specified and the Python requests package is + not installed, a `ModuleNotFoundError` Exception will occur. + will occur. :returns: A :class:`G2PwdrData` object representing the histogram, or if multiple is True, a list of :class:`G2PwdrData` objects is returned. """ LoadG2fil() - datafile = os.path.abspath(os.path.expanduser(datafile)) - try: - iparams = os.path.abspath(os.path.expanduser(iparams)) - except: - pass - pwdrreaders = import_generic(datafile, Readers['Pwdr'],fmthint=fmthint,bank=databank) + if not URL: + datafile = os.path.abspath(os.path.expanduser(datafile)) + pwdrreaders = import_generic(datafile, Readers['Pwdr'],fmthint=fmthint, + bank=databank, URL=URL) if not multiple: pwdrreaders = pwdrreaders[0:1] histlist = [] + if URL: + iparmfile = downloadFile(iparams) + else: + try: + iparmfile = os.path.abspath(os.path.expanduser(iparams)) + except: + pass for r in pwdrreaders: - histname, new_names, pwdrdata = load_pwd_from_reader(r, iparams, + histname, new_names, pwdrdata = load_pwd_from_reader(r, iparmfile, [h.name for h in self.histograms()],bank=instbank) if histname in self.data: G2fil.G2Print("Warning - redefining histogram", histname) @@ -1003,9 +1087,9 @@ def add_powder_histogram(self, datafile, iparams=None, phases=[], def clone_powder_histogram(self, histref, newname, Y, Yerr=None): '''Creates a copy of a powder diffraction histogram with new Y values. The X values are not changed. The number of Y values must match the - number of X values. + number of X values. - :param histref: The histogram object, the name of the histogram (str), or ranId + :param histref: The histogram object, the name of the histogram (str), or ranId or histogram index. :param str newname: The name to be assigned to the new histogram :param list Y: A set of intensity values @@ -1024,15 +1108,15 @@ def clone_powder_histogram(self, histref, newname, Y, Yerr=None): newhist = 'PWDR '+newname if len(Y) != len(self[orighist]['data'][1][0]): raise Exception("clone error: length of Y does not match number of X values ({})" - .format(len(self[orighist]['data'][1][0]))) + .format(len(self[orighist]['data'][1][0]))) if Yerr is not None and len(Yerr) != len(self[orighist]['data'][1][0]): raise Exception("clone error: length of Yerr does not match number of X values ({})" .format(len(self[orighist]['data'][1][0]))) - + self[newhist] = copy.deepcopy(self[orighist]) # intensities yo = self[newhist]['data'][1][1] = ma.MaskedArray(Y,mask=self[orighist]['data'][1][1].mask) - + Ymin,Ymax = yo.min(),yo.max() # set to zero: weights, calc, bkg, obs-calc for i in [2,3,4,5]: @@ -1055,42 +1139,42 @@ def clone_powder_histogram(self, histref, newname, Y, Yerr=None): self.names.append([newhist]+subkeys) self.update_ids() return self.histogram(newhist) - + def add_simulated_powder_histogram(self, histname, iparams, Tmin, Tmax, Tstep=None, wavelength=None, scale=None, phases=[], ibank=None, Npoints=None): """Create a simulated powder data histogram for the project. - Requires an instrument parameter file. + Requires an instrument parameter file. Note that in unix fashion, "~" can be used to indicate the home directory (e.g. ~/G2data/data.prm). The instrument parameter file will determine if the histogram is x-ray, CW neutron, TOF, etc. as well - as the instrument type. + as the instrument type. :param str histname: A name for the histogram to be created. :param str iparams: The instrument parameters file, a filename. :param float Tmin: Minimum 2theta or TOF (millisec) for dataset to be simulated :param float Tmax: Maximum 2theta or TOF (millisec) for dataset to be simulated - :param float Tstep: Step size in 2theta or deltaT/T (TOF) for simulated dataset. + :param float Tstep: Step size in 2theta or deltaT/T (TOF) for simulated dataset. Default is to compute this from Npoints. :param float wavelength: Wavelength for CW instruments, overriding the value - in the instrument parameters file if specified. For single-wavelength histograms, - this should be a single float value, for K alpha 1,2 histograms, this should - be a list or tuple with two values. + in the instrument parameters file if specified. For single-wavelength histograms, + this should be a single float value, for K alpha 1,2 histograms, this should + be a list or tuple with two values. :param float scale: Histogram scale factor which multiplies the pattern. Note that simulated noise is added to the pattern, so that if the maximum intensity is - small, the noise will mask the computed pattern. The scale needs to be a large + small, the noise will mask the computed pattern. The scale needs to be a large number for neutrons. The default, None, provides a scale of 1 for x-rays, 10,000 for CW neutrons and 100,000 for TOF. :param list phases: Phases to link to the new histogram. Use proj.phases() to link to all defined phases. - :param int ibank: provides a bank number for the instrument parameter file. The + :param int ibank: provides a bank number for the instrument parameter file. The default is None, corresponding to load the first bank. - :param int Νpoints: the number of data points to be used for computing the + :param int Νpoints: the number of data points to be used for computing the diffraction pattern. Defaults as None, which sets this to 2500. Do not specify both Npoints and Tstep. Due to roundoff the actual number of points used may differ - by +-1 from Npoints. Must be below 25,000. + by +-1 from Npoints. Must be below 25,000. :returns: A :class:`G2PwdrData` object representing the histogram """ @@ -1134,7 +1218,7 @@ def add_simulated_powder_histogram(self, histname, iparams, Tmin, Tmax, Tstep=No x = np.exp((np.arange(0,N))*Tstep+np.log(Tmin*1000.)) N = len(x) unit = 'millisec' - else: + else: if Npoints: N = Npoints else: @@ -1174,19 +1258,23 @@ def add_simulated_powder_histogram(self, histname, iparams, Tmin, Tmax, Tstep=No if wavelength is not None: if 'Lam1' in pwdrdata['Instrument Parameters'][0]: # have alpha 1,2 here + errmsg = '' try: pwdrdata['Instrument Parameters'][0]['Lam1'][0] = float(wavelength[0]) pwdrdata['Instrument Parameters'][0]['Lam1'][1] = float(wavelength[0]) pwdrdata['Instrument Parameters'][0]['Lam2'][0] = float(wavelength[1]) pwdrdata['Instrument Parameters'][0]['Lam2'][1] = float(wavelength[1]) except: - raise G2ScriptException("add_simulated_powder_histogram Error: only one wavelength with alpha 1+2 histogram?") + errmsg = "add_simulated_powder_histogram Error: only one wavelength with alpha 1+2 histogram?" + if errmsg: raise G2ScriptException(errmsg) elif 'Lam' in pwdrdata['Instrument Parameters'][0]: + errmsg = '' try: pwdrdata['Instrument Parameters'][0]['Lam'][0] = float(wavelength) pwdrdata['Instrument Parameters'][0]['Lam'][1] = float(wavelength) except: - raise G2ScriptException("add_simulated_powder_histogram Error: invalid wavelength?") + errmsg = "add_simulated_powder_histogram Error: invalid wavelength?" + if errmsg: raise G2ScriptException(errmsg) else: raise G2ScriptException("add_simulated_powder_histogram Error: can't set a wavelength for a non-CW dataset") self.data[histname] = pwdrdata @@ -1195,38 +1283,44 @@ def add_simulated_powder_histogram(self, histname, iparams, Tmin, Tmax, Tstep=No for phase in phases: phase = self.phase(phase) self.link_histogram_phase(histname, phase) - + self.set_Controls('cycles', 0) self.data[histname]['data'][0]['Dummy'] = True return self.histogram(histname) - + def add_phase(self, phasefile=None, phasename=None, histograms=[], fmthint=None, mag=False, - spacegroup='P 1',cell=None): + spacegroup='P 1',cell=None, URL=False): """Loads a phase into the project, usually from a .cif file - :param str phasefile: The CIF file (or other file type, see fmthint) - that the phase will be read from. + :param str phasefile: The CIF file (or other file type, see fmthint) + that the phase will be read from. May be left as None (the default) if the phase will be constructed - a step at a time. - :param str phasename: The name of the new phase, or None for the - default. A phasename must be specified when a phasefile is not. + a step at a time. + :param str phasename: The name of the new phase, or None for the + default. A phasename must be specified when a phasefile is not. :param list histograms: The names of the histograms to associate with this phase. Use proj.histograms() to add to all histograms. :param str fmthint: If specified, only importers where the format name (reader.formatName, as shown in Import menu) contains the supplied string will be tried as importers. If not specified, all importers consistent with the file extension will be tried - (equivalent to "guess format" in menu). Specifying this + (equivalent to "guess format" in menu). Specifying this is optional but is strongly encouraged. :param bool mag: Set to True to read a magCIF - :param str spacegroup: The space group name as a string. The - space group must follow the naming rules used in - :func:`GSASIIspc.SpcGroup`. Defaults to 'P 1'. Note that + :param str spacegroup: The space group name as a string. The + space group must follow the naming rules used in + :func:`GSASIIspc.SpcGroup`. Defaults to 'P 1'. Note that this is only used when phasefile is None. - :param list cell: a list with six unit cell constants + :param list cell: a list with six unit cell constants (a, b, c, alpha, beta and gamma in Angstrom/degrees). - + :param bool URL: if True, the contents of phasefile is a URL + and the file will be downloaded to a temporary location + and read. The downloaded file will not be saved. + If URL is specified and the Python requests package is + not installed, a `ModuleNotFoundError` Exception will occur. + will occur. + :returns: A :class:`G2Phase` object representing the new phase. """ @@ -1268,19 +1362,23 @@ def add_phase(self, phasefile=None, phasename=None, histograms=[], self.update_ids() return self.phase(phasename) - - phasefile = os.path.abspath(os.path.expanduser(phasefile)) - if not os.path.exists(phasefile): - raise G2ImportException(f'File {phasefile} does not exist') + + if not URL: + phasefile = os.path.abspath(os.path.expanduser(phasefile)) + if not os.path.exists(phasefile): + raise G2ImportException(f'File {phasefile} does not exist') + else: + print(f'reading phase from URL {phasefile}') # TODO: handle multiple phases in a file - phasereaders = import_generic(phasefile, Readers['Phase'], fmthint=fmthint) + phasereaders = import_generic(phasefile, Readers['Phase'], + fmthint=fmthint, URL=URL) phasereader = phasereaders[0] - + if phasereader.MPhase and mag: phObj = phasereader.MPhase else: phObj = phasereader.Phase - + phasename = phasename or phObj['General']['Name'] phaseNameList = [p.name for p in self.phases()] phasename = G2obj.MakeUniqueLabel(phasename, phaseNameList) @@ -1334,10 +1432,10 @@ def add_phase(self, phasefile=None, phasename=None, histograms=[], return self.phase(phasename) def add_single_histogram(self, datafile, phase=None, fmthint=None): - """Loads a powder data histogram or multiple powder histograms + """Loads a powder data histogram or multiple powder histograms into the project. - :param str datafile: A filename with the single crystal data file + :param str datafile: A filename with the single crystal data file to read. Note that in unix fashion, "~" can be used to indicate the home directory (e.g. ~/G2data/data.hkl). :param phases: A phase to link to the new histogram. A @@ -1345,8 +1443,8 @@ def add_single_histogram(self, datafile, phase=None, fmthint=None): If not specified, no phase will be linked. :param str fmthint: If specified, only importers where the format name (reader.formatName, as shown in Import menu) contains the - supplied string will be tried as importers. If not specified, - an error will be generated, as the file format will not distinguish + supplied string will be tried as importers. If not specified, + an error will be generated, as the file format will not distinguish well between different data types. :returns: A :class:`G2Single` object representing the histogram @@ -1362,7 +1460,7 @@ def add_single_histogram(self, datafile, phase=None, fmthint=None): for r in readers: # only expect 1 histname = 'HKLF ' + G2obj.StripUnicode( os.path.split(r.readfilename)[1],'_') - #HistName = G2obj.MakeUniqueLabel(HistName, existingnames) + #HistName = G2obj.MakeUniqueLabel(HistName, existingnames) newnames = [histname,'Instrument Parameters','Reflection List'] if histname in self.data: G2fil.G2Print("Warning - redefining histogram", histname) @@ -1383,7 +1481,7 @@ def add_single_histogram(self, datafile, phase=None, fmthint=None): self.update_ids() if phase is None: return self.link_histogram_phase(histname, phase) - + def link_histogram_phase(self, histogram, phase): """Associates a given histogram and phase. @@ -1391,7 +1489,6 @@ def link_histogram_phase(self, histogram, phase): :meth:`~G2Project.histogram` :meth:`~G2Project.phase`""" - import GSASIImath as G2mth hist = self.histogram(histogram) phase = self.phase(phase) @@ -1427,9 +1524,9 @@ def reload(self): def refine(self, newfile=None, printFile=None, makeBack=False): '''Invoke a refinement for the project. The project is written to - the currently selected gpx file and then either a single or sequential refinement + the currently selected gpx file and then either a single or sequential refinement is performed depending on the setting of 'Seq Data' in Controls - (set in :meth:`get_Controls`). + (set in :meth:`get_Controls`). ''' seqSetting = self.data['Controls']['data'].get('Seq Data',[]) if not seqSetting: @@ -1474,12 +1571,12 @@ def _seqrefine(self): OK,Msg = G2strMain.SeqRefine(self.filename,None) def histogram(self, histname): - """Returns the histogram object associated with histname, or None + """Returns the histogram object associated with histname, or None if it does not exist. - :param histname: The name of the histogram (str), or ranId or + :param histname: The name of the histogram (str), or ranId or (for powder) the histogram index. - :returns: A :class:`G2PwdrData` object, or :class:`G2Single` object, or + :returns: A :class:`G2PwdrData` object, or :class:`G2Single` object, or None if the histogram does not exist .. seealso:: @@ -1511,12 +1608,12 @@ def histogram(self, histname): return histogram def histType(self, histname): - """Returns the type for histogram object associated with histname, or + """Returns the type for histogram object associated with histname, or None if it does not exist. - :param histname: The name of the histogram (str), or ranId or + :param histname: The name of the histogram (str), or ranId or (for powder) the histogram index. - :returns: 'PWDR' for a Powder histogram, + :returns: 'PWDR' for a Powder histogram, 'HKLF' for a single crystal histogram, or None if the histogram does not exist @@ -1550,7 +1647,7 @@ def histType(self, histname): elif histogram.name.startswith('PWDR '): if histogram.id == histname or histogram.ranId == histname: return 'PWDR' - + def histograms(self, typ=None): """Return a list of all histograms, as :class:`G2PwdrData` objects @@ -1559,7 +1656,7 @@ def histograms(self, typ=None): :param ste typ: The prefix (type) the histogram such as 'PWDR ' for powder or 'HKLF ' for single crystal. If None - (the default) all known histograms types are found. + (the default) all known histograms types are found. :returns: a list of objects .. seealso:: @@ -1572,11 +1669,11 @@ def histograms(self, typ=None): # list of names). then it must be a histogram, unless labeled Phases or Restraints if typ is None: for obj in self.names: - if obj[0].startswith('PWDR ') or obj[0].startswith('HKLF '): + if obj[0].startswith('PWDR ') or obj[0].startswith('HKLF '): output.append(self.histogram(obj[0])) else: for obj in self.names: - if len(obj) > 1 and obj[0].startswith(typ): + if len(obj) > 1 and obj[0].startswith(typ): output.append(self.histogram(obj[0])) return output @@ -1584,7 +1681,7 @@ def phase(self, phasename): """ Gives an object representing the specified phase in this project. - :param str phasename: A reference to the desired phase. Either the phase + :param str phasename: A reference to the desired phase. Either the phase name (str), the phase's ranId, the phase's index (both int) or a phase object (:class:`G2Phase`) :returns: A :class:`G2Phase` object @@ -1632,12 +1729,12 @@ def _images(self): """Returns a list of all the images in the project. """ return [i[0] for i in self.names if i[0].startswith('IMG ')] - + def image(self, imageRef): """ Gives an object representing the specified image in this project. - :param str imageRef: A reference to the desired image. Either the Image + :param str imageRef: A reference to the desired image. Either the Image tree name (str), the image's index (int) or a image object (:class:`G2Image`) :returns: A :class:`G2Image` object @@ -1654,18 +1751,18 @@ def image(self, imageRef): if imageRef in self._images(): return G2Image(self.data[imageRef], imageRef, self) + errmsg = '' try: # imageRef should be an index num = int(imageRef) - imageRef = self._images()[num] + imageRef = self._images()[num] return G2Image(self.data[imageRef], imageRef, self) except ValueError: - raise Exception("imageRef {} not an object, name or image index in current selected project" - .format(imageRef)) + errmsg = "imageRef {imageRef} not an object, name or image index in current selected project" except IndexError: - raise Exception("imageRef {} out of range (max={}) in current selected project" - .format(imageRef,len(self._images())-1)) - + errmsg = "imageRef {imageRef} out of range (max={len(self._images())-1)}) in current selected project" + if errmsg: raise G2ScriptException(errmsg) + def images(self): """ Returns a list of all the images in the project. @@ -1673,17 +1770,17 @@ def images(self): :returns: A list of :class:`G2Image` objects """ return [G2Image(self.data[i],i,self) for i in self._images()] - + def _pdfs(self): """Returns a list of all the PDF entries in the project. """ return [i[0] for i in self.names if i[0].startswith('PDF ')] - + def pdf(self, pdfRef): """ Gives an object representing the specified PDF entry in this project. - :param pdfRef: A reference to the desired image. Either the PDF + :param pdfRef: A reference to the desired image. Either the PDF tree name (str), the pdf's index (int) or a PDF object (:class:`G2PDF`) :returns: A :class:`G2PDF` object @@ -1701,15 +1798,17 @@ def pdf(self, pdfRef): if pdfRef in self._pdfs(): return G2PDF(self.data[pdfRef], pdfRef, self) + errmsg = '' try: # pdfRef should be an index num = int(pdfRef) - pdfRef = self._pdfs()[num] + pdfRef = self._pdfs()[num] return G2PDF(self.data[pdfRef], pdfRef, self) except ValueError: - raise Exception(f"pdfRef {pdfRef} not an object, name or PDF index in current selected project") + errmsg = f"pdfRef {pdfRef} not an object, name or PDF index in current selected project" except IndexError: - raise Exception(f"pdfRef {pdfRef} out of range (max={len(G2SmallAngle)-1}) in current selected project") + errmsg = f"pdfRef {pdfRef} out of range (max={len(G2SmallAngle)-1}) in current selected project" + if errmsg: raise Exception(errmsg) def pdfs(self): """ Returns a list of all the PDFs in the project. @@ -1717,15 +1816,15 @@ def pdfs(self): :returns: A list of :class:`G2PDF` objects """ return [G2PDF(self.data[i],i,self) for i in self._pdfs()] - + def copy_PDF(self, PDFobj, histogram): - '''Creates a PDF entry that can be used to compute a PDF + '''Creates a PDF entry that can be used to compute a PDF as a copy of settings in an existing PDF (:class:`G2PDF`) - object. - This places an entry in the project but :meth:`G2PDF.calculate` - must be used to actually perform the PDF computation. + object. + This places an entry in the project but :meth:`G2PDF.calculate` + must be used to actually perform the PDF computation. - :param PDFobj: A :class:`G2PDF` object which may be + :param PDFobj: A :class:`G2PDF` object which may be in a separate project or the dict associated with the PDF object (G2PDF.data). :param histogram: A reference to a histogram, @@ -1743,19 +1842,19 @@ def copy_PDF(self, PDFobj, histogram): self.data[PDFname]['PDF Controls'][i] = [] G2fil.G2Print('Adding "{}" to project'.format(PDFname)) return G2PDF(self.data[PDFname], PDFname, self) - + def add_PDF(self, prmfile, histogram): - '''Creates a PDF entry that can be used to compute a PDF. + '''Creates a PDF entry that can be used to compute a PDF. Note that this command places an entry in the project, but :meth:`G2PDF.calculate` must be used to actually perform - the computation. + the computation. :param str datafile: The powder data file to read, a filename. :param histogram: A reference to a histogram, which can be reference by object, name, or number. :returns: A :class:`G2PDF` object for the PDF entry ''' - + LoadG2fil() PDFname = 'PDF ' + self.histogram(histogram).name[4:] peaks = {'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]} @@ -1800,7 +1899,7 @@ def seqref(self): """ if 'Sequential results' not in self.data: return return G2SeqRefRes(self.data['Sequential results']['data'], self) - + def update_ids(self): """Makes sure all phases and histograms have proper hId and pId""" # Translated from GetUsedHistogramsAndPhasesfromTree, @@ -1817,10 +1916,10 @@ def do_refinements(self, refinements=[{}], histogram='all', phase='all', around :meth:`iter_refinements` :param list refinements: A list of dictionaries specifiying changes to be made to - parameters before refinements are conducted. - See the :ref:`Refinement_recipe` section for how this is defined. - If not specified, the default value is ``[{}]``, which performs a single - refinement step is performed with the current refinement settings. + parameters before refinements are conducted. + See the :ref:`Refinement_recipe` section for how this is defined. + If not specified, the default value is ``[{}]``, which performs a single + refinement step is performed with the current refinement settings. :param str histogram: Name of histogram for refinements to be applied to, or 'all'; note that this can be overridden for each refinement step via a "histograms" entry in the dict. @@ -1829,19 +1928,19 @@ def do_refinements(self, refinements=[{}], histogram='all', phase='all', step via a "phases" entry in the dict. :param list outputnames: Provides a list of project (.gpx) file names to use for each refinement step (specifying None skips the save step). - See :meth:`save`. + See :meth:`save`. Note that this can be overridden using an "output" entry in the dict. :param bool makeBack: determines if a backup ).bckX.gpx) file is made before a refinement is performed. The default is False. - + To perform a single refinement without changing any parameters, use this call: .. code-block:: python - + my_project.do_refinements([]) """ - + for proj in self.iter_refinements(refinements, histogram, phase, outputnames, makeBack): pass @@ -1852,7 +1951,7 @@ def iter_refinements(self, refinements, histogram='all', phase='all', """Conducts a series of refinements, iteratively. Stops after every refinement and yields this project, to allow error checking or logging of intermediate results. Parameter use is the same as for - :meth:`do_refinements` (which calls this method). + :meth:`do_refinements` (which calls this method). >>> def checked_refinements(proj): ... for p in proj.iter_refinements(refs): @@ -1863,7 +1962,7 @@ def iter_refinements(self, refinements, histogram='all', phase='all', ... if is_something_wrong(p): ... raise Exception("I need a human!") - + """ if outputnames: if len(refinements) != len(outputnames): @@ -1921,20 +2020,20 @@ def set_refinement(self, refinement, histogram='all', phase='all'): (see :class:`G2PwdrData`), the histogram name (str) or the index number (int) of the histogram in the project, numbered starting from 0. Omitting the parameter or the string 'all' indicates that parameters in - all histograms should be set. + all histograms should be set. :param phase: Specifies either 'all' (default), a single phase or a list of phases. Phases may be specified as phase objects (see :class:`G2Phase`), the phase name (str) or the index number (int) of the phase in the project, numbered starting from 0. Omitting the parameter or the string 'all' indicates that parameters in - all phases should be set. + all phases should be set. Note that refinement parameters are categorized as one of three types: 1. Histogram parameters 2. Phase parameters 3. Histogram-and-Phase (HAP) parameters - + .. seealso:: :meth:`G2PwdrData.set_refinements` :meth:`G2PwdrData.clear_refinements` @@ -2039,12 +2138,12 @@ def _sasd(self): """Returns a list of all the SASD entries in the project. """ return [i[0] for i in self.names if i[0].startswith('SASD ')] - + def SAS(self, sasRef): """ Gives an object representing the specified SAS entry in this project. - :param sasRef: A reference to the desired SASD entry. Either the SASD + :param sasRef: A reference to the desired SASD entry. Either the SASD tree name (str), the SASD's index (int) or a SASD object (:class:`G2SmallAngle`) :returns: A :class:`G2SmallAngle` object @@ -2062,16 +2161,18 @@ def SAS(self, sasRef): if sasRef in self._sasd(): return G2SmallAngle(self.data[sasRef], sasRef, self) + errmsg = '' try: # sasRef should be an index num = int(sasRef) - sasRef = self._sasd()[num] + sasRef = self._sasd()[num] return G2PDF(self.data[sasRef], sasRef, self) except ValueError: - raise Exception(f"sasRef {sasRef} not an object, name or SAS index in current selected project") + errmsg = f"sasRef {sasRef} not an object, name or SAS index in current selected project" except IndexError: - raise Exception(f"sasRef {sasRef} out of range (max={len(self._sasd())-1}) in current selected project") - + errmsg = "sasRef {sasRef} out of range (max={len(self._sasd())-1}) in current selected project" + if errmsg: raise Exception(errmsg) + def SASs(self): """ Returns a list of all the Small Angle histograms in the project. @@ -2079,9 +2180,9 @@ def SASs(self): :returns: A list of :class:`G2SmallAngle` objects """ return [G2SmallAngle(self.data[i],i,self) for i in self._sasd()] - + def add_SmallAngle(self, datafile): - '''Placeholder for an eventual routine that will read a small angle dataset + '''Placeholder for an eventual routine that will read a small angle dataset from a file. :param str datafile: The SASD data file to read, a filename. @@ -2089,12 +2190,12 @@ def add_SmallAngle(self, datafile): ''' raise G2ScriptException("Error: add_SmallAngle not yet implemented.") #return G2SmallAngle(self.data[PDFname], PDFname, self) - + def get_Constraints(self,ctype): '''Returns a list of constraints of the type selected. :param str ctype: one of the following keywords: 'Hist', 'HAP', 'Phase', 'Global' - :returns: a list of constraints, see the + :returns: a list of constraints, see the :ref:`constraint definition descriptions `. Note that if this list is changed (for example by deleting elements or by changing them) the constraints in the project are changed. @@ -2105,29 +2206,29 @@ def get_Constraints(self,ctype): raise Exception(("get_Constraints error: value of ctype ({})" +" must be 'Hist', 'HAP', 'Phase', or 'Global'.") .format(ctype)) - + def add_HoldConstr(self,varlist,reloadIdx=True,override=False): - '''Set a hold constraint on a list of variables. + '''Set a hold constraint on a list of variables. - Note that this will cause the project to be saved if not + Note that this will cause the project to be saved if not already done so. It will always save the .gpx file before creating constraint(s) if reloadIdx is True. - :param list varlist: A list of variables to hold. - Each value in the list may be one of the following three items: - (A) a :class:`GSASIIobj.G2VarObj` object, - (B) a variable name (str), or + :param list varlist: A list of variables to hold. + Each value in the list may be one of the following three items: + (A) a :class:`GSASIIobj.G2VarObj` object, + (B) a variable name (str), or (C) a list/tuple of arguments for :meth:`make_var_obj`. - :param bool reloadIdx: If True (default) the .gpx file will be + :param bool reloadIdx: If True (default) the .gpx file will be saved and indexed prior to use. This is essential if atoms, phases or histograms have been added to the project. - :param bool override: This routine looks up variables using - :func:`GSASIIobj.getDescr` (which is not comprehensive). If + :param bool override: This routine looks up variables using + :func:`GSASIIobj.getDescr` (which is not comprehensive). If not found, the routine will throw an exception, unless - override=True is specified. - + override=True is specified. + Example:: - + gpx.add_HoldConstr(('0::A4','0:1:D12',':0:Lam')) ''' @@ -2149,34 +2250,34 @@ def add_HoldConstr(self,varlist,reloadIdx=True,override=False): self.add_constraint_raw(_constr_type(var), [[1.0, var], None, None, 'h']) if vardefwarn and not override: raise Exception('Constraint var error: undefined variables.\n\tIf correct, use override=True in call.\n\tAlso, please let GSAS-II developers know so definition can be added.') - + def add_EquivConstr(self,varlist,multlist=[],reloadIdx=True,override=False): - '''Set a equivalence on a list of variables. + '''Set a equivalence on a list of variables. - Note that this will cause the project to be saved if not + Note that this will cause the project to be saved if not already done so. It will always save the .gpx file before creating a constraint if reloadIdx is True. - :param list varlist: A list of variables to make equivalent to the - first item in the list. - Each value in the list may be one of the following three items: - (A) a :class:`GSASIIobj.G2VarObj` object, - (B) a variable name (str), or + :param list varlist: A list of variables to make equivalent to the + first item in the list. + Each value in the list may be one of the following three items: + (A) a :class:`GSASIIobj.G2VarObj` object, + (B) a variable name (str), or (C) a list/tuple of arguments for :meth:`make_var_obj`. :param list multlist: a list of multipliers for each variable in varlist. If there are fewer values than supplied for varlist then missing values will be set to 1. The default is [] which - means that all multipliers are 1. - :param bool reloadIdx: If True (default) the .gpx file will be + means that all multipliers are 1. + :param bool reloadIdx: If True (default) the .gpx file will be saved and indexed prior to use. This is essential if atoms, phases or histograms have been added to the project. - :param bool override: This routine looks up variables using - :func:`GSASIIobj.getDescr` (which is not comprehensive). If + :param bool override: This routine looks up variables using + :func:`GSASIIobj.getDescr` (which is not comprehensive). If not found, the routine will throw an exception, unless - override=True is specified. + override=True is specified. Examples:: - + gpx.add_EquivConstr(('0::AUiso:0','0::AUiso:1','0::AUiso:2')) gpx.add_EquivConstr(('0::dAx:0','0::dAx:1'),[1,-1]) @@ -2221,32 +2322,32 @@ def add_EquivConstr(self,varlist,multlist=[],reloadIdx=True,override=False): self.add_constraint_raw(typ, constr) def add_EqnConstr(self,total,varlist,multlist=[],reloadIdx=True,override=False): - '''Set a constraint equation on a list of variables. + '''Set a constraint equation on a list of variables. - Note that this will cause the project to be saved if not + Note that this will cause the project to be saved if not already done so. It will always save the .gpx file before creating a constraint if reloadIdx is True. :param float total: A value that the constraint must equal - :param list varlist: A list of variables to use in the equation. - Each value in the list may be one of the following three items: - (A) a :class:`GSASIIobj.G2VarObj` object, - (B) a variable name (str), or + :param list varlist: A list of variables to use in the equation. + Each value in the list may be one of the following three items: + (A) a :class:`GSASIIobj.G2VarObj` object, + (B) a variable name (str), or (C) a list/tuple of arguments for :meth:`make_var_obj`. :param list multlist: a list of multipliers for each variable in varlist. If there are fewer values than supplied for varlist then missing values will be set to 1. The default is [] which - means that all multipliers are 1. - :param bool reloadIdx: If True (default) the .gpx file will be + means that all multipliers are 1. + :param bool reloadIdx: If True (default) the .gpx file will be saved and indexed prior to use. This is essential if atoms, phases or histograms have been added to the project. - :param bool override: This routine looks up variables using - :func:`GSASIIobj.getDescr` (which is not comprehensive). If + :param bool override: This routine looks up variables using + :func:`GSASIIobj.getDescr` (which is not comprehensive). If not found, the routine will throw an exception, unless - override=True is specified. + override=True is specified. Example:: - + gpx.add_EqnConstr(1.0,('0::Ax:0','0::Ax:1'),[1,1]) ''' @@ -2289,50 +2390,50 @@ def add_EqnConstr(self,total,varlist,multlist=[],reloadIdx=True,override=False): typ_prev = typ var_prev = var if vardefwarn and not override: - raise Exception('Constraint var error: undefined variables.\n\tIf correct, use override=True in call.\n\tAlso, please let GSAS-II developers know so definition can be added.') + raise Exception('Constraint var error: undefined variables.\n\tIf correct, use override=True in call.\n\tAlso, please let GSAS-II developers know so definition can be added.') constr += [float(total), None, 'c'] self.add_constraint_raw(typ, constr) def add_NewVarConstr(self,varlist,multlist=[],name=None,vary=False, reloadIdx=True,override=False): - '''Set a new-variable constraint from a list of variables to - create a new parameter from two or more predefined parameters. + '''Set a new-variable constraint from a list of variables to + create a new parameter from two or more predefined parameters. - Note that this will cause the project to be saved, if not + Note that this will cause the project to be saved, if not already done so. It will always save the .gpx file before creating a constraint if reloadIdx is True. - :param list varlist: A list of variables to use in the expression. - Each value in the list may be one of the following three items: - (A) a :class:`GSASIIobj.G2VarObj` object, - (B) a variable name (str), or + :param list varlist: A list of variables to use in the expression. + Each value in the list may be one of the following three items: + (A) a :class:`GSASIIobj.G2VarObj` object, + (B) a variable name (str), or (C) a list/tuple of arguments for :meth:`make_var_obj`. :param list multlist: a list of multipliers for each variable in varlist. If there are fewer values than supplied for varlist then missing values will be set to 1. The default is [] which means that all multipliers are 1. - :param str name: An optional string to be supplied as a name for this - new parameter. + :param str name: An optional string to be supplied as a name for this + new parameter. :param bool vary: Determines if the new variable should be flagged to - be refined. - :param bool reloadIdx: If True (default) the .gpx file will be + be refined. + :param bool reloadIdx: If True (default) the .gpx file will be saved and indexed prior to use. This is essential if atoms, phases or histograms have been added to the project. - :param bool override: This routine looks up variables using - :func:`GSASIIobj.getDescr` (which is not comprehensive). If + :param bool override: This routine looks up variables using + :func:`GSASIIobj.getDescr` (which is not comprehensive). If not found, the routine will throw an exception, unless - override=True is specified. + override=True is specified. Examples:: - + gpx.add_NewVarConstr(('0::AFrac:0','0::AFrac:1'),[0.5,0.5],'avg',True) gpx.add_NewVarConstr(('0::AFrac:0','0::AFrac:1'),[1,-1],'diff',False,False) - The example above is a way to treat two variables that are closely correlated. - The first variable, labeled as avg, allows the two variables to refine in tandem - while the second variable (diff) tracks their difference. In the initial stages of + The example above is a way to treat two variables that are closely correlated. + The first variable, labeled as avg, allows the two variables to refine in tandem + while the second variable (diff) tracks their difference. In the initial stages of refinement only avg would be refined, but in the final stages, it might be possible - to refine diff. The second False value in the second example prevents the + to refine diff. The second False value in the second example prevents the .gpx file from being saved. ''' if reloadIdx: @@ -2375,19 +2476,19 @@ def add_NewVarConstr(self,varlist,multlist=[],name=None,vary=False, raise Exception('Constraint var error: undefined variables.\n\tIf correct, use override=True in call.\n\tAlso, please let GSAS-II developers know so definition can be added.') constr += [name, bool(vary), 'f'] self.add_constraint_raw(typ, constr) - + def add_constraint_raw(self, cons_scope, constr): """Adds a constraint to the project. :param str cons_scope: should be one of "Hist", "Phase", "HAP", or "Global". - :param list constr: a constraint coded with :class:`GSASIIobj.G2VarObj` - objects as described in the + :param list constr: a constraint coded with :class:`GSASIIobj.G2VarObj` + objects as described in the :ref:`constraint definition descriptions `. WARNING this function does not check the constraint is well-constructed. - Please use :meth:`G2Project.add_HoldConstr` or - :meth:`G2Project.add_EquivConstr` (etc.) instead, unless you are really - certain you know what you are doing. + Please use :meth:`G2Project.add_HoldConstr` or + :meth:`G2Project.add_EquivConstr` (etc.) instead, unless you are really + certain you know what you are doing. """ constrs = self.data['Constraints']['data'] if 'Global' not in constrs: @@ -2398,11 +2499,11 @@ def hold_many(self, vars, ctype): """Apply holds for all the variables in vars, for constraint of a given type. This routine has been superceeded by :meth:`add_Hold` - :param list vars: A list of variables to hold. Each may be a + :param list vars: A list of variables to hold. Each may be a :class:`GSASIIobj.G2VarObj` object, a variable name (str), or a list/tuple of arguments for :meth:`make_var_obj`. - :param str ctype: A string constraint type specifier, passed directly to - :meth:`add_constraint_raw` as consType. Should be one of "Hist", "Phase", + :param str ctype: A string constraint type specifier, passed directly to + :meth:`add_constraint_raw` as consType. Should be one of "Hist", "Phase", or "HAP" ("Global" not implemented). """ G2fil.G2Print('G2Phase.hold_many Warning: replace calls to hold_many() with add_Hold()') @@ -2421,7 +2522,7 @@ def make_var_obj(self, phase=None, hist=None, varname=None, atomId=None, Automatically converts string phase, hist, or atom names into the ID required by G2VarObj. - Note that this will cause the project to be saved if not + Note that this will cause the project to be saved if not already done so. """ @@ -2464,7 +2565,8 @@ def make_var_obj(self, phase=None, hist=None, varname=None, atomId=None, return G2obj.G2VarObj(phase, hist, varname, atomId) def add_image(self, imagefile, fmthint=None, defaultImage=None, - indexList=None, cacheImage=False): + indexList=None, cacheImage=False, + URL=False, download_loc=None): """Load an image into a project :param str imagefile: The image file to read, a filename. @@ -2473,20 +2575,42 @@ def add_image(self, imagefile, fmthint=None, defaultImage=None, supplied string will be tried as importers. If not specified, all importers consistent with the file extension will be tried (equivalent to "guess format" in menu). - :param str defaultImage: The name of an image to use as a default for - setting parameters for the image file to read. - :param list indexList: specifies the image numbers (counting from zero) + :param str defaultImage: The name of an image to use as a default for + setting parameters for the image file to read. + :param list indexList: specifies the image numbers (counting from zero) to be used from the file when a file has multiple images. A value of ``[0,2,3]`` will cause the only first, third and fourth images in the file - to be included in the project. - :param bool cacheImage: When True, the image is cached to save - in rereading it later. Default is False (no caching). - - :returns: a list of :class:`G2Image` object(s) for the added image(s) + to be included in the project. + :param bool cacheImage: When True, the image is cached to save + in rereading it later. Default is False (no caching). + :param bool URL: if True, the contents of imagefile is a URL + and the file will be downloaded and saved. The file will be + written in the specified directory (see `download_loc`) + or a temporary location, if not specified. Note that + if a temporary location, if the proiject (.gpx) file is + saved, the image may not be accessible if the .gpx file + is later reopened. Default is False. + If URL is specified and the Python requests package is + not installed, a `ModuleNotFoundError` Exception will occur. + will occur. + :param str download_loc: a location or file name where the + image will be saved. Note that for almost all image types, + the image cannot be read if the file extension does not + match what is expected for the format. (This can be determined + by looking at the importer code; if `strictExtension=True`, + the extension must be in the `extensionlist` list.) + If only a directory is specified, the file name will be taken + from the URL, which will likely cause problems if it does + not match the needed extension. + If URL is specified and the default download_loc + value is used (None), the image will be saved in a temporary + location that will persist until the OS removes it. + :returns: a list of :class:`G2Image` object(s) for the added image(s) """ LoadG2fil() - imagefile = os.path.abspath(os.path.expanduser(imagefile)) - readers = import_generic(imagefile, Readers['Image'], fmthint=fmthint) + if not URL: imagefile = os.path.abspath(os.path.expanduser(imagefile)) + readers = import_generic(imagefile, Readers['Image'], + fmthint=fmthint, URL=URL, download_loc=download_loc) objlist = [] for i,rd in enumerate(readers): if indexList is not None and i not in indexList: @@ -2573,7 +2697,7 @@ def add_image(self, imagefile, fmthint=None, defaultImage=None, Data['centerAzm'] = False Data['fullIntegrate'] = GSASIIpath.GetConfigValue('fullIntegrate',True) Data['setRings'] = False - Data['background image'] = ['',-1.0] + Data['background image'] = ['',-1.0] Data['dark image'] = ['',-1.0] Data['Flat Bkg'] = 0.0 Data['Oblique'] = [0.5,False] @@ -2593,20 +2717,20 @@ def add_image(self, imagefile, fmthint=None, defaultImage=None, objlist.append(G2Image(self.data[TreeName], TreeName, self)) del rd.Image return objlist - + def imageMultiDistCalib(self,imageList=None,verbose=False): - '''Invokes a global calibration fit (same as Image Controls/Calibration/Multi-distance Recalibrate - menu command) with images as multiple distance settings. - Note that for this to work properly, the initial calibration parameters + '''Invokes a global calibration fit (same as Image Controls/Calibration/Multi-distance Recalibrate + menu command) with images as multiple distance settings. + Note that for this to work properly, the initial calibration parameters (center, wavelength, distance & tilts) must be close enough to converge. This may produce a better result if run more than once. See :ref:`MultiDist_Example` for example code. - :param str imageList: the images to include in the fit, if not specified + :param str imageList: the images to include in the fit, if not specified all images in the project will be included. - :returns: parmDict,covData where parmDict has the refined parameters + :returns: parmDict,covData where parmDict has the refined parameters and their values and covData is a dict containing the covariance matrix ('covMatrix'), the number of ring picks ('obs') the reduced Chi-squared ('chisq'), the names of the variables ('varyList') and their values ('variables') @@ -2678,7 +2802,7 @@ def imageMultiDistCalib(self,imageList=None,verbose=False): #GSASIIpath.IPyBreak() G2fil.G2Print('\nFitting',obsArr.shape[0],'ring picks and',len(varList),'variables...') result = G2img.FitMultiDist(obsArr,varList,parmDict,covar=True,Print=verbose) - + for img in imageList: # update GPX info with fit results name = img.name #print ('updating',name) @@ -2698,32 +2822,32 @@ def imageMultiDistCalib(self,imageList=None,verbose=False): for H in HKL[key][:N]: ellipse = G2img.GetEllipse(H[3],Data) Data['ellipses'].append(copy.deepcopy(ellipse+('b',))) - + covData = {'title':'Multi-distance recalibrate','covMatrix':result[3], 'obs':obsArr.shape[0],'chisq':result[0], 'varyList':varList,'variables':result[1]} return parmDict,covData - + def set_Frozen(self, variable=None, histogram=None, mode='remove'): '''Removes one or more Frozen variables (or adds one) (See :ref:`Parameter Limits` description.) - Note that use of this + Note that use of this will cause the project to be saved if not already done so. - :param str variable: a variable name as a str or + :param str variable: a variable name as a str or (as a :class:`GSASIIobj.G2VarObj` object). Should - not contain wildcards. + not contain wildcards. If None (default), all frozen variables are deleted - from the project, unless a sequential fit and - a histogram is specified. + from the project, unless a sequential fit and + a histogram is specified. :param histogram: A reference to a histogram, which can be reference by object, name, or number. - Used for sequential fits only. + Used for sequential fits only. :param str mode: The default mode is to remove variables from the appropriate Frozen list, but if the mode - is specified as 'add', the variable is added to the - list. - :returns: True if the variable was added or removed, False + is specified as 'add', the variable is added to the + list. + :returns: True if the variable was added or removed, False otherwise. Exceptions are generated with invalid requests. ''' Controls = self.data['Controls']['data'] @@ -2741,7 +2865,7 @@ def set_Frozen(self, variable=None, histogram=None, mode='remove'): raise Exception('set_Frozen error: variable must be specified') else: raise Exception('Undefined mode ({}) in set_Frozen'.format(mode)) - + if histogram is None: h = 'FrozenList' else: @@ -2778,9 +2902,9 @@ def set_Frozen(self, variable=None, histogram=None, mode='remove'): return True def get_Frozen(self, histogram=None): - '''Gets a list of Frozen variables. + '''Gets a list of Frozen variables. (See :ref:`Parameter Limits` description.) - Note that use of this + Note that use of this will cause the project to be saved if not already done so. :param histogram: A reference to a histogram, @@ -2812,13 +2936,13 @@ def get_Frozen(self, histogram=None): return [str(i) for i in Controls['parmFrozen']['FrozenList']] else: return [] - + def get_Controls(self, control, variable=None): '''Return project controls settings :param str control: the item to be returned. See below for allowed values. - :param str variable: a variable name as a str or - (as a :class:`GSASIIobj.G2VarObj` object). + :param str variable: a variable name as a str or + (as a :class:`GSASIIobj.G2VarObj` object). Used only with control set to "parmMin" or "parmMax". :returns: The value for the control. @@ -2829,16 +2953,16 @@ def get_Controls(self, control, variable=None): of histogram names or an empty list when in non-sequential mode. * Reverse Seq: returns True or False. True indicates that fitting of the sequence of histograms proceeds in reversed order. - * seqCopy: returns True or False. True indicates that results from + * seqCopy: returns True or False. True indicates that results from each sequential fit are used as the starting point for the next histogram. - * parmMin & parmMax: retrieves a maximum or minimum value for - a refined parameter. Note that variable will be a GSAS-II - variable name, optionally with * specified for a histogram + * parmMin & parmMax: retrieves a maximum or minimum value for + a refined parameter. Note that variable will be a GSAS-II + variable name, optionally with * specified for a histogram or atom number. Return value will be a float. (See :ref:`Parameter Limits` description.) - * Anything else returns the value in the Controls dict, if present. An - exception is raised if the control value is not present. + * Anything else returns the value in the Controls dict, if present. An + exception is raised if the control value is not present. .. seealso:: :meth:`set_Controls` @@ -2868,33 +2992,33 @@ def get_Controls(self, control, variable=None): else: G2fil.G2Print('Defined Controls:',self.data['Controls']['data'].keys()) raise Exception('{} is an invalid control value'.format(control)) - + def set_Controls(self, control, value, variable=None): '''Set project controls. - Note that use of this with control set to parmMin or parmMax + Note that use of this with control set to parmMin or parmMax will cause the project to be saved if not already done so. - :param str control: the item to be set. See below for allowed values. + :param str control: the item to be set. See below for allowed values. :param value: the value to be set. :param str variable: used only with control set to "parmMin" or "parmMax" Allowed values for *control* parameter: * ``'cycles'``: sets the maximum number of cycles (value must be int) - * ``'sequential'``: sets the histograms to be used for a sequential refinement. - Use an empty list to turn off sequential fitting. - The values in the list may be the name of the histogram (a str), or + * ``'sequential'``: sets the histograms to be used for a sequential refinement. + Use an empty list to turn off sequential fitting. + The values in the list may be the name of the histogram (a str), or a ranId or index (int values), see :meth:`histogram`. * ``'seqCopy'``: when True, the results from each sequential fit are used as - the starting point for the next. After each fit is is set to False. - Ignored for non-sequential fits. + the starting point for the next. After each fit is is set to False. + Ignored for non-sequential fits. * ``'Reverse Seq'``: when True, sequential refinement is performed on the reversed list of histograms. - * ``'parmMin'`` & ``'parmMax'``: set a maximum or minimum value for a refined - parameter. Note that variable will be a GSAS-II variable name, + * ``'parmMin'`` & ``'parmMax'``: set a maximum or minimum value for a refined + parameter. Note that variable will be a GSAS-II variable name, optionally with * specified for a histogram or atom number and - value must be a float. + value must be a float. (See :ref:`Parameter Limits` description.) .. seealso:: @@ -2928,22 +3052,22 @@ def set_Controls(self, control, value, variable=None): self.data['Controls']['data'][control+'Dict'][key] = float(value) else: raise Exception('{} is an invalid control value'.format(control)) - + def copyHistParms(self,sourcehist,targethistlist='all',modelist='all'): '''Copy histogram information from one histogram to others :param sourcehist: is a histogram object (:class:`G2PwdrData`) or a histogram name or the index number of the histogram :param list targethistlist: a list of histograms where each item in the - list can be a histogram object (:class:`G2PwdrData`), + list can be a histogram object (:class:`G2PwdrData`), a histogram name or the index number of the histogram. - if the string 'all' (default value), then all histograms in - the project are used. - :param list modelist: May be a list of sections to copy, which may - include 'Background', 'Instrument Parameters', 'Limits' and + if the string 'all' (default value), then all histograms in + the project are used. + :param list modelist: May be a list of sections to copy, which may + include 'Background', 'Instrument Parameters', 'Limits' and 'Sample Parameters' (items may be shortened to uniqueness and capitalization is ignored, so ['b','i','L','s'] will work.) - The default value, 'all' causes the listed sections to + The default value, 'all' causes the listed sections to ''' sections = ('Background','Instrument Parameters','Limits', @@ -2965,15 +3089,15 @@ def copyHistParms(self,sourcehist,targethistlist='all',modelist='all'): hist_out = self.histogram(h) if not hist_out: raise Exception('{} is not a valid histogram'.format(h)) - for key in copysections: + for key in copysections: hist_out[key] = copy.deepcopy(hist_in[key]) def get_VaryList(self): - '''Returns a list of the refined variables in the + '''Returns a list of the refined variables in the last refinement cycle :returns: a list of variables or None if no refinement has been - performed. + performed. ''' try: return self['Covariance']['data']['varyList'] @@ -2981,11 +3105,11 @@ def get_VaryList(self): return def get_ParmList(self): - '''Returns a list of all the parameters defined in the + '''Returns a list of all the parameters defined in the last refinement cycle :returns: a list of parameters or None if no refinement has been - performed. + performed. ''' if 'parmDict' not in self['Covariance']['data']: raise G2ScriptException('No parameters found in project, has a refinement been run?') @@ -2993,16 +3117,16 @@ def get_ParmList(self): return list(self['Covariance']['data']['parmDict'].keys()) except: return - + def get_Variable(self,var): - '''Returns the value and standard uncertainty (esd) for a variable + '''Returns the value and standard uncertainty (esd) for a variable parameters, as defined in the last refinement cycle - :param str var: a variable name of form '

::', such as + :param str var: a variable name of form '

::', such as ':0:Scale' - :returns: (value,esd) if the parameter is refined or - (value, None) if the variable is in a constraint or is not - refined or None if the parameter is not found. + :returns: (value,esd) if the parameter is refined or + (value, None) if the variable is in a constraint or is not + refined or None if the parameter is not found. ''' if 'parmDict' not in self['Covariance']['data']: raise G2ScriptException('No parameters found in project, has a refinement been run?') @@ -3017,17 +3141,17 @@ def get_Variable(self,var): return (val,None) def get_Covariance(self,varList): - '''Returns the values and covariance matrix for a series of variable + '''Returns the values and covariance matrix for a series of variable parameters. as defined in the last refinement cycle :param tuple varList: a list of variable names of form '

::' :returns: (valueList,CovMatrix) where valueList contains the (n) values - in the same order as varList (also length n) and CovMatrix is a + in the same order as varList (also length n) and CovMatrix is a (n x n) matrix. If any variable name is not found in the varyList - then None is returned. + then None is returned. - Use this code, where sig provides standard uncertainties for - parameters and where covArray provides the correlation between + Use this code, where sig provides standard uncertainties for + parameters and where covArray provides the correlation between off-diagonal terms:: sig = np.sqrt(np.diag(covMatrix)) @@ -3042,43 +3166,42 @@ def get_Covariance(self,varList): if 'parmDict' not in self['Covariance']['data']: raise G2ScriptException('No parameters found in project, has a refinement been run?') vals = [self['Covariance']['data']['parmDict'][i] for i in varList] - import GSASIImath as G2mth cov = G2mth.getVCov(varList, self['Covariance']['data']['varyList'], self['Covariance']['data']['covMatrix']) return (vals,cov) - + def ComputeWorstFit(self): - '''Computes the worst-fit parameters in a model. + '''Computes the worst-fit parameters in a model. - :returns: (keys, derivCalcs, varyList) where: + :returns: (keys, derivCalcs, varyList) where: * keys is a list of parameter names - where the names are ordered such that first entry in the list + where the names are ordered such that first entry in the list will produce the largest change in the fit if refined and the last entry will have the smallest change; - - * derivCalcs is a dict where the key is a variable name and the - value is a list with three partial derivative values for - d(Chi**2)/d(var) where the derivatives are computed - for values v-d to v; v-d to v+d; v to v+d where v is - the current value for the variable and d is a small delta + + * derivCalcs is a dict where the key is a variable name and the + value is a list with three partial derivative values for + d(Chi**2)/d(var) where the derivatives are computed + for values v-d to v; v-d to v+d; v to v+d where v is + the current value for the variable and d is a small delta value chosen for that variable type; - * varyList is a list of the parameters that are currently set to - be varied. + * varyList is a list of the parameters that are currently set to + be varied. ''' self.save() derivCalcs,varyList = G2strMain.Refine(self.filename,None,allDerivs=True) keys = sorted(derivCalcs,key=lambda x:abs(derivCalcs[x][1]),reverse=True) return (keys,derivCalcs,varyList) - + class G2AtomRecord(G2ObjectWrapper): - """Wrapper for an atom record. Allows many atom properties to be access - and changed. See the :ref:`Atom Records description ` + """Wrapper for an atom record. Allows many atom properties to be access + and changed. See the :ref:`Atom Records description ` for the details on what information is contained in an atom record. - Scripts should not try to create a :class:`G2AtomRecord` object directly as + Scripts should not try to create a :class:`G2AtomRecord` object directly as these objects are created via access from a :class:`G2Phase` object. Example showing some uses of :class:`G2AtomRecord` methods: @@ -3104,22 +3227,22 @@ def __init__(self, data, indices, proj): self.data = data self.cx, self.ct, self.cs, self.cia = indices self.proj = proj - + # @property # def X(self): -# '''Get or set the associated atom's X. -# Use as ``x = atom.X`` to obtain the value and +# '''Get or set the associated atom's X. +# Use as ``x = atom.X`` to obtain the value and # ``atom.X = x`` to set the value. # ''' # pass # @X.setter # def X(self, val): # pass - + @property def label(self): - '''Get the associated atom's label. - Use as ``x = atom.label`` to obtain the value and + '''Get the associated atom's label. + Use as ``x = atom.label`` to obtain the value and ``atom.label = x`` to set the value. ''' return self.data[self.ct-1] @@ -3129,11 +3252,11 @@ def label(self, val): @property def type(self): - '''Get or set the associated atom's type. Call as a variable - (``x = atom.type``) to obtain the value or use - ``atom.type = x`` to change the type. It is the user's - responsibility to make sure that the atom type is valid; - no checking is done here. + '''Get or set the associated atom's type. Call as a variable + (``x = atom.type``) to obtain the value or use + ``atom.type = x`` to change the type. It is the user's + responsibility to make sure that the atom type is valid; + no checking is done here. .. seealso:: :meth:`element` @@ -3141,16 +3264,16 @@ def type(self): return self.data[self.ct] @type.setter def type(self, val): - # TODO: should check if atom type is defined + # TODO: should check if atom type is defined self.data[self.ct] = str(val) - + @property def element(self): - '''Parses element symbol from the atom type symbol for the atom + '''Parses element symbol from the atom type symbol for the atom associated with the current object. .. seealso:: - :meth:`type` + :meth:`type` ''' import re try: @@ -3162,7 +3285,7 @@ def element(self): @property def refinement_flags(self): '''Get or set refinement flags for the associated atom. - Use as ``x = atom.refinement_flags`` to obtain the flags and + Use as ``x = atom.refinement_flags`` to obtain the flags and ``atom.refinement_flags = "XU"`` (etc) to set the value. ''' return self.data[self.ct+1] @@ -3177,8 +3300,8 @@ def refinement_flags(self, other): @property def coordinates(self): '''Get or set the associated atom's coordinates. - Use as ``x = atom.coordinates`` to obtain a tuple with - the three (x,y,z) values and ``atom.coordinates = (x,y,z)`` + Use as ``x = atom.coordinates`` to obtain a tuple with + the three (x,y,z) values and ``atom.coordinates = (x,y,z)`` to set the values. Changes needed to adapt for changes in site symmetry have not yet been @@ -3194,11 +3317,11 @@ def coordinates(self, val): except: raise ValueError(f"conversion error with coordinates {val}") # TODO: should recompute the atom site symmetries here - + @property def occupancy(self): - '''Get or set the associated atom's site fraction. - Use as ``x = atom.occupancy`` to obtain the value and + '''Get or set the associated atom's site fraction. + Use as ``x = atom.occupancy`` to obtain the value and ``atom.occupancy = x`` to set the value. ''' return self.data[self.cx+3] @@ -3208,11 +3331,11 @@ def occupancy(self, val): @property def mult(self): - '''Get the associated atom's multiplicity value. Should not be - changed by user. + '''Get the associated atom's multiplicity value. Should not be + changed by user. ''' return self.data[self.cs+1] - + @property def ranId(self): '''Get the associated atom's Random Id number. Don't change this. @@ -3230,10 +3353,10 @@ def adp_flag(self): @property def ADP(self): - '''Get or set the associated atom's Uiso or Uaniso value(s). - Use as ``x = atom.ADP`` to obtain the value(s) and + '''Get or set the associated atom's Uiso or Uaniso value(s). + Use as ``x = atom.ADP`` to obtain the value(s) and ``atom.ADP = x`` to set the value(s). For isotropic atoms - a single float value is returned (or used to set). For + a single float value is returned (or used to set). For anisotropic atoms a list of six values is used. .. seealso:: @@ -3254,10 +3377,10 @@ def ADP(self, value): @property def uiso(self): - '''A synonym for :meth:`ADP` to be used for Isotropic atoms. - Get or set the associated atom's Uiso value. - Use as ``x = atom.uiso`` to obtain the value and - ``atom.uiso = x`` to set the value. A + '''A synonym for :meth:`ADP` to be used for Isotropic atoms. + Get or set the associated atom's Uiso value. + Use as ``x = atom.uiso`` to obtain the value and + ``atom.uiso = x`` to set the value. A single float value is returned or used to set. .. seealso:: @@ -3274,18 +3397,18 @@ def uiso(self, value): self.ADP = value class G2PwdrData(G2ObjectWrapper): - """Wraps a Powder Data Histogram. + """Wraps a Powder Data Histogram. The object contains these class variables: * G2PwdrData.proj: contains a reference to the :class:`G2Project` - object that contains this histogram + object that contains this histogram * G2PwdrData.name: contains the name of the histogram * G2PwdrData.data: contains the histogram's associated data in a dict, as documented for the :ref:`Powder Diffraction Tree`. The actual histogram values are contained in the 'data' dict item, - as documented for Data. + as documented for Data. - Scripts should not try to create a :class:`G2PwdrData` object directly as + Scripts should not try to create a :class:`G2PwdrData` object directly as :meth:`G2PwdrData.__init__` should be invoked from inside :class:`G2Project`. """ @@ -3344,14 +3467,14 @@ def Background(self): def add_back_peak(self,pos,int,sig,gam,refflags=[]): '''Adds a background peak to the Background parameters - + :param float pos: position of peak, a 2theta or TOF value :param float int: integrated intensity of background peak, usually large :param float sig: Gaussian width of background peak, usually large :param float gam: Lorentzian width of background peak, usually unused (small) - :param list refflags: a list of 1 to 4 boolean refinement flags for - pos,int,sig & gam, respectively (use [0,1] to refine int only). - Defaults to [] which means nothing is refined. + :param list refflags: a list of 1 to 4 boolean refinement flags for + pos,int,sig & gam, respectively (use [0,1] to refine int only). + Defaults to [] which means nothing is refined. ''' if 'peaksList' not in self.Background[1]: self.Background[1]['peaksList'] = [] @@ -3367,7 +3490,7 @@ def add_back_peak(self,pos,int,sig,gam,refflags=[]): def del_back_peak(self,peaknum): '''Removes a background peak from the Background parameters - + :param int peaknum: the number of the peak (starting from 0) ''' npks = self.Background[1].get('nPeaks',0) @@ -3375,15 +3498,15 @@ def del_back_peak(self,peaknum): raise Exception('peak {} not found in histogram {}'.format(peaknum,self.name)) del self.Background[1]['peaksList'][peaknum] self.Background[1]['nPeaks'] = len(self.Background[1]['peaksList']) - + def ref_back_peak(self,peaknum,refflags=[]): '''Sets refinement flag for a background peak - + :param int peaknum: the number of the peak (starting from 0) - :param list refflags: a list of 1 to 4 boolean refinement flags for + :param list refflags: a list of 1 to 4 boolean refinement flags for pos,int,sig & gam, respectively. If a flag is not specified - it defaults to False (use [0,1] to refine int only). - Defaults to [] which means nothing is refined. + it defaults to False (use [0,1] to refine int only). + Defaults to [] which means nothing is refined. ''' npks = self.Background[1].get('nPeaks',0) if peaknum >= npks: @@ -3394,7 +3517,7 @@ def ref_back_peak(self,peaknum,refflags=[]): flags[i] = bool(f) for i,f in enumerate(flags): self.Background[1]['peaksList'][peaknum][2*i+1] = f - + @property def id(self): self.proj.update_ids() @@ -3405,20 +3528,20 @@ def id(self, val): self.data['data'][0]['hId'] = val def Limits(self,typ,value=None): - '''Used to obtain or set the histogram limits. When a value is - specified, the appropriate limit is set. Otherwise, the value is - returned. Note that this provides an alternative to setting - histogram limits with the :meth:`G2Project:do_refinements` or - :meth:`G2PwdrData.set_refinements` methods. + '''Used to obtain or set the histogram limits. When a value is + specified, the appropriate limit is set. Otherwise, the value is + returned. Note that this provides an alternative to setting + histogram limits with the :meth:`G2Project:do_refinements` or + :meth:`G2PwdrData.set_refinements` methods. - :param str typ: a string which must be either 'lower' + :param str typ: a string which must be either 'lower' (for 2-theta min or TOF min) or 'upper' (for 2theta max or TOF max). - Anything else produces an error. - :param float value: the number to set the limit (in units of degrees - or TOF (microsec.). If not specified, the command returns the - selected limit value rather than setting it. - :returns: The current value of the requested limit - (when ``value=None``). Units are + Anything else produces an error. + :param float value: the number to set the limit (in units of degrees + or TOF (microsec.). If not specified, the command returns the + selected limit value rather than setting it. + :returns: The current value of the requested limit + (when ``value=None``). Units are 2-theta (degrees) or TOF (microsec). Examples:: @@ -3442,14 +3565,14 @@ def Limits(self,typ,value=None): self.data['Limits'][1][1] = float(value) else: raise G2ScriptException('G2PwdData.Limit error: must be "lower" or "upper"') - + def Excluded(self,value=None): '''Used to obtain or set the excluded regions for a histogram. - When a value is specified, the excluded regions are set. + When a value is specified, the excluded regions are set. Otherwise, the list of excluded region pairs is returned. - Note that excluded regions may be an empty list or a list + Note that excluded regions may be an empty list or a list of regions to be excluded, where each region is provided - as pair of numbers, where the lower limit comes first. + as pair of numbers, where the lower limit comes first. Some sample excluded region lists are:: [[4.5, 5.5], [8.0, 9.0]] @@ -3459,28 +3582,28 @@ def Excluded(self,value=None): [] The first above describes two excluded regions from 4.5-5.5 and 8-9 - degrees 2-theta. The second is for a TOF pattern and also - describes two excluded regions, for 130-140 and 160-170 milliseconds. - The third line would be the case where there are no excluded - regions. + degrees 2-theta. The second is for a TOF pattern and also + describes two excluded regions, for 130-140 and 160-170 milliseconds. + The third line would be the case where there are no excluded + regions. :param list value: A list of pairs of excluded region numbers - (as two-element lists). Some error checking/reformatting is done, - but users are expected to get this right. Use the GUI to - create examples or check input. Numbers in the list - are in units of degrees or TOF (microsec.). + (as two-element lists). Some error checking/reformatting is done, + but users are expected to get this right. Use the GUI to + create examples or check input. Numbers in the list + are in units of degrees or TOF (microsec.). If a value is not specified, the command returns the list of excluded regions. - :returns: The list of excluded regions (when ``value=None``). Units are + :returns: The list of excluded regions (when ``value=None``). Units are 2-theta (degrees) or TOF (microsec). - Example 1:: + Example 1:: h = gpx.histogram(0) # adds an excluded region (11-13 degrees) h.Excluded(h.Excluded() + [[11,13]]) - Example 2:: + Example 2:: h = gpx.histogram(0) # changes the range of the first excluded region excl = h.Excluded() @@ -3488,7 +3611,7 @@ def Excluded(self,value=None): h.Excluded(excl) Example 3:: - + h = gpx.histogram(0) # deletes all excluded regions h.Excluded([]) ''' @@ -3510,7 +3633,7 @@ def Excluded(self,value=None): s += f'Upper limit {h} too high. ' except: raise G2ScriptException(f'G2PwdData.Excluded error: incorrectly formatted list or\n\tinvalid value. In: {value}') - if s: + if s: raise G2ScriptException(f'G2PwdData.Excluded error(s): {s}') self.data['Limits'][2:] = listValues @@ -3588,20 +3711,20 @@ def SetInstParms(Inst): def getdata(self,datatype): '''Provides access to the histogram data of the selected data type - :param str datatype: must be one of the following values + :param str datatype: must be one of the following values (case is ignored): - + * 'X': the 2theta or TOF values for the pattern * 'Q': the 2theta or TOF values for the pattern transformed to Q * 'd': the 2theta or TOF values for the pattern transformed to d-space - * 'Yobs': the observed intensity values + * 'Yobs': the observed intensity values * 'Yweight': the weights for each data point (1/sigma**2) - * 'Ycalc': the computed intensity values + * 'Ycalc': the computed intensity values * 'Background': the computed background values * 'Residual': the difference between Yobs and Ycalc (obs-calc) :returns: an numpy MaskedArray with data values of the requested type - + ''' enums = ['x', 'yobs', 'yweight', 'ycalc', 'background', 'residual', 'q', 'd'] if datatype.lower() not in enums: @@ -3616,33 +3739,33 @@ def getdata(self,datatype): return self.data['data'][1][enums.index(datatype.lower())] def y_calc(self): - '''Returns the calculated intensity values; better to + '''Returns the calculated intensity values; better to use :meth:`getdata` ''' return self.data['data'][1][3] def reflections(self): - '''Returns a dict with an entry for every phase in the + '''Returns a dict with an entry for every phase in the current histogram. Within each entry is a dict with keys - 'RefList' (reflection list, see - :ref:`Powder Reflections `), - 'Type' (histogram type), 'FF' - (form factor information), 'Super' (True if this is superspace - group). + 'RefList' (reflection list, see + :ref:`Powder Reflections `), + 'Type' (histogram type), 'FF' + (form factor information), 'Super' (True if this is superspace + group). ''' return self.data['Reflection Lists'] - + def Export(self,fileroot,extension,fmthint=None): '''Write the histogram into a file. The path is specified by fileroot and extension. - + :param str fileroot: name of the file, optionally with a path (extension is ignored) :param str extension: includes '.', must match an extension in global exportersByExtension['powder'] or a Exception is raised. - :param str fmthint: If specified, the first exporter where the format + :param str fmthint: If specified, the first exporter where the format name (obj.formatName, as shown in Export menu) contains the - supplied string will be used. If not specified, an error + supplied string will be used. If not specified, an error will be generated showing the possible choices. :returns: name of file that was written ''' @@ -3671,10 +3794,10 @@ def Export(self,fileroot,extension,fmthint=None): self._SetFromArray(obj) obj.Writer(self.name,fil) return fil - + def _SetFromArray(self,expObj): - '''Load a histogram into the exporter in preparation for use of - the .Writer method in the object. + '''Load a histogram into the exporter in preparation for use of + the .Writer method in the object. :param Exporter expObj: Exporter object ''' @@ -3682,7 +3805,7 @@ def _SetFromArray(self,expObj): expObj.Histograms[self.name]['Data'] = self.data['data'][1] for key in 'Instrument Parameters','Sample Parameters','Reflection Lists': expObj.Histograms[self.name][key] = self.data[key] - + def plot(self, Yobs=True, Ycalc=True, Background=True, Residual=True): try: import matplotlib.pyplot as plt @@ -3701,7 +3824,7 @@ def plot(self, Yobs=True, Ycalc=True, Background=True, Residual=True): def get_wR(self): """returns the overall weighted profile R factor for a histogram - + :returns: a wR value as a percentage or None if not defined """ return self['data'][0].get('wR') @@ -3719,12 +3842,12 @@ def _decodeHist(self,hist): return self.proj.histograms()[hist].name else: raise G2ScriptException("Invalid histogram reference: "+str(hist)) - + def set_background(self, key, value): - '''Set background parameters (this serves a similar function as in - :meth:`set_refinements`, but with a simplified interface). + '''Set background parameters (this serves a similar function as in + :meth:`set_refinements`, but with a simplified interface). - :param str key: a string that defines the background parameter that will + :param str key: a string that defines the background parameter that will be changed. Must appear in the table below. ================= ============== =========================================== @@ -3732,14 +3855,14 @@ def set_background(self, key, value): ================= ============== =========================================== fixedHist int, str, reference to a histogram in the current None or project or None to remove the reference. - G2PwdrData - fixedFileMult float multiplier applied to intensities in - the background histogram where a value - of -1.0 means full subtraction of + G2PwdrData + fixedFileMult float multiplier applied to intensities in + the background histogram where a value + of -1.0 means full subtraction of the background histogram. ================= ============== =========================================== - :param value: a value to set the selected background parameter. The meaning + :param value: a value to set the selected background parameter. The meaning and type for this parameter is listed in the table above. ''' @@ -3753,11 +3876,11 @@ def set_background(self, key, value): bkgDict['background PWDR'][1] = float(value) else: raise ValueError("Invalid key in set_background:", key) - + def set_refinements(self, refs): """Sets the PWDR histogram refinement parameter 'key' to the specification 'value'. - :param dict refs: A dictionary of the parameters to be set. See the + :param dict refs: A dictionary of the parameters to be set. See the :ref:`Histogram_parameters_table` table for a description of what these dictionaries should be. @@ -3819,10 +3942,12 @@ def set_refinements(self, refs): elif key == 'Instrument Parameters': instrument, secondary = self.data['Instrument Parameters'] for iparam in value: + errmsg = '' try: instrument[iparam][2] = True except IndexError: - raise ValueError("Invalid key:", iparam) + errmsg = f"Invalid key: {iparam}" + if errmsg: raise ValueError(errmsg) else: raise ValueError("Unknown key:", key) # Fit fixed points after the fact - ensure they are after fixed points @@ -3839,7 +3964,7 @@ def clear_refinements(self, refs): """Clears the PWDR refinement parameter 'key' and its associated value. :param dict refs: A dictionary of parameters to clear. - See the :ref:`Histogram_parameters_table` table for what can be specified. + See the :ref:`Histogram_parameters_table` table for what can be specified. """ for key, value in refs.items(): if key == 'Limits': @@ -3881,7 +4006,6 @@ def add_peak(self,area,dspace=None,Q=None,ttheta=None): Note: only one of the parameters: dspace, Q or ttheta may be specified. See :ref:`PeakRefine` for an example. ''' - import GSASIImath as G2mth if (not dspace) + (not Q) + (not ttheta) != 2: G2fil.G2Print('add_peak error: too many or no peak position(s) specified') return @@ -3898,7 +4022,7 @@ def add_peak(self,area,dspace=None,Q=None,ttheta=None): def set_peakFlags(self,peaklist=None,area=None,pos=None,sig=None,gam=None, alp=None,bet=None): '''Set refinement flags for peaks - + :param list peaklist: a list of peaks to change flags. If None (default), changes are made to all peaks. :param bool area: Sets or clears the refinement flag for the peak area value. @@ -3913,12 +4037,12 @@ def set_peakFlags(self,peaklist=None,area=None,pos=None,sig=None,gam=None, If None (the default), no change is made. :param bool bet: Sets or clears the refinement flag for the peak beta (TOF width) value. If None (the default), no change is made. - + Note that when peaks are first created the area flag is on and the other flags are initially off. Example:: - + set_peakFlags(sig=False,gam=True) causes the sig refinement flag to be cleared and the gam flag to be set, in both cases for @@ -3939,22 +4063,21 @@ def set_peakFlags(self,peaklist=None,area=None,pos=None,sig=None,gam=None, def refine_peaks(self,mode='useIP'): '''Causes a refinement of peak position, background and instrument parameters - - :param str mode: this determines how peak widths are determined. If - the value is 'useIP' (the default) then the width parameter values (sigma, gamma, - alpha,...) are computed from the histogram's instrument parameters. If the + + :param str mode: this determines how peak widths are determined. If + the value is 'useIP' (the default) then the width parameter values (sigma, gamma, + alpha,...) are computed from the histogram's instrument parameters. If the value is 'hold', then peak width parameters are not overridden. In - this case, it is not possible to refine the instrument parameters - associated with the peak widths and an attempt to do so will result in + this case, it is not possible to refine the instrument parameters + associated with the peak widths and an attempt to do so will result in an error. - :returns: a list of dicts with refinement results. Element 0 has uncertainties + :returns: a list of dicts with refinement results. Element 0 has uncertainties on refined values (also placed in self.data['Peak List']['sigDict']) element 1 has the peak fit result, element 2 has the peak fit uncertainties - and element 3 has r-factors from the fit. + and element 3 has r-factors from the fit. (These are generated in :meth:`GSASIIpwd.DoPeakFit`). ''' - import GSASIIpwd as G2pwd if mode == 'useIP': G2pwd.setPeakInstPrmMode(True) elif mode == 'hold': @@ -3975,17 +4098,17 @@ def refine_peaks(self,mode='useIP'): oneCycle=False,controls=controls,dlg=None) peaks['sigDict'] = result[0] return result - + @property def Peaks(self): '''Provides a dict with the Peak List parameters for this histogram. :returns: dict with two elements where item - 'peaks' is a list of peaks where each element is - [pos,pos-ref,area,area-ref,sig,sig-ref,gam,gam-ref], + 'peaks' is a list of peaks where each element is + [pos,pos-ref,area,area-ref,sig,sig-ref,gam,gam-ref], where the -ref items are refinement flags and item - 'sigDict' is a dict with possible items 'Back;#', + 'sigDict' is a dict with possible items 'Back;#', 'pos#', 'int#', 'sig#', 'gam#' ''' return self.data['Peak List'] @@ -3996,17 +4119,17 @@ def PeakList(self): for this histogram. :returns: a list of peaks, where each peak is a list containing - [pos,area,sig,gam] + [pos,area,sig,gam] (position, peak area, Gaussian width, Lorentzian width) - + ''' return [i[::2] for i in self.data['Peak List']['peaks']] - + def Export_peaks(self,filename): '''Write the peaks file. The path is specified by filename extension. - - :param str filename: name of the file, optionally with a path, + + :param str filename: name of the file, optionally with a path, includes an extension :returns: name of file that was written ''' @@ -4017,7 +4140,6 @@ def Export_peaks(self,filename): Inst,Inst2 = self.data['Instrument Parameters'] Type = Inst['Type'][0] if 'T' not in Type: - import GSASIImath as G2mth wave = G2mth.getWave(Inst) else: wave = None @@ -4029,7 +4151,7 @@ def Export_peaks(self,filename): if wave: fp.write('#wavelength = %10.6f\n'%(wave)) if 'T' in Type: - fp.write('#%9s %10s %10s %12s %10s %10s %10s %10s %10s\n'%('pos','dsp','esd','int','alp','bet','sig','gam','FWHM')) + fp.write('#%9s %10s %10s %12s %10s %10s %10s %10s %10s\n'%('pos','dsp','esd','int','alp','bet','sig','gam','FWHM')) else: fp.write('#%9s %10s %10s %12s %10s %10s %10s\n'%('pos','dsp','esd','int','sig','gam','FWHM')) for ip,peak in enumerate(peaks): @@ -4071,7 +4193,7 @@ def SaveProfile(self,filename): G2fil.G2Print ('Instrument parameters saved to: '+filename) def LoadProfile(self,filename,bank=0): - '''Reads a GSAS-II (new style) .instprm file and overwrites the current + '''Reads a GSAS-II (new style) .instprm file and overwrites the current parameters :param str filename: instrument parameter file name, extension ignored if not @@ -4106,27 +4228,27 @@ def LoadProfile(self,filename,bank=0): try: newVals.append(float(val)) except ValueError: - newVals.append(val) - S = File.readline() + newVals.append(val) + S = File.readline() File.close() LoadG2fil() self.data['Instrument Parameters'][0] = G2fil.makeInstDict(newItems,newVals,len(newVals)*[False,]) def EditSimulated(self,Tmin, Tmax, Tstep=None, Npoints=None): - '''Change the parameters for an existing simulated powder histogram. + '''Change the parameters for an existing simulated powder histogram. This will reset the previously computed "observed" pattern. :param float Tmin: Minimum 2theta or TOF (microsec) for dataset to be simulated :param float Tmax: Maximum 2theta or TOF (usec) for dataset to be simulated - :param float Tstep: Step size in 2theta or TOF (usec) for dataset to be simulated + :param float Tstep: Step size in 2theta or TOF (usec) for dataset to be simulated Default is to compute this from Npoints. - :param int Νpoints: the number of data points to be used for computing the + :param int Νpoints: the number of data points to be used for computing the diffraction pattern. Defaults as None, which sets this to 2500. Do not specify both Npoints and Tstep. Due to roundoff the actual nuber of points used may differ by +-1 from Npoints. Must be below 25,000. ''' if not self.data['data'][0]['Dummy']: - raise G2ScriptException("Error: histogram for G2PwdrData.EditSimulated is not simulated") + raise G2ScriptException("Error: histogram for G2PwdrData.EditSimulated is not simulated") if Tmax < Tmin: Tmin,Tmax = Tmax,Tmin if Tstep is not None and Npoints is not None: @@ -4135,7 +4257,7 @@ def EditSimulated(self,Tmin, Tmax, Tstep=None, Npoints=None): Tstep = abs(Tstep) elif Npoints is None: Npoints = 2500 - + if 'T' in self.data['Instrument Parameters'][0]['Type'][0]: if Tmax > 200.: raise G2ScriptException("Error: Tmax is too large") @@ -4175,13 +4297,13 @@ def EditSimulated(self,Tmin, Tmax, Tstep=None, Npoints=None): self.data['Limits'] = limits def getHistEntryList(self, keyname=''): - """Returns a dict with histogram setting values. + """Returns a dict with histogram setting values. :param str keyname: an optional string. When supplied only entries where at least one key contains the specified string are reported. - Case is ignored, so 'sg' will find entries where one of the keys - is 'SGdata', etc. - :returns: a set of histogram dict keys. + Case is ignored, so 'sg' will find entries where one of the keys + is 'SGdata', etc. + :returns: a set of histogram dict keys. See :meth:`G2Phase.getHAPentryList` for a related example. @@ -4193,14 +4315,14 @@ def getHistEntryList(self, keyname=''): return [i for i in dictDive(self.data,keyname) if i[0] != ['Histograms']] def getHistEntryValue(self, keylist): - """Returns the histogram control value associated with a list of keys. - Where the value returned is a list, it may be used as the target of - an assignment (as in - ``getHistEntryValue(...)[...] = val``) - to set a value inside a list. + """Returns the histogram control value associated with a list of keys. + Where the value returned is a list, it may be used as the target of + an assignment (as in + ``getHistEntryValue(...)[...] = val``) + to set a value inside a list. - :param list keylist: a list of dict keys, typically as returned by - :meth:`getHistEntryList`. + :param list keylist: a list of dict keys, typically as returned by + :meth:`getHistEntryList`. :returns: a histogram setting; may be a int, float, bool, list,... @@ -4213,15 +4335,15 @@ def getHistEntryValue(self, keylist): return d def setHistEntryValue(self, keylist, newvalue): - """Sets a histogram control value associated with a list of keys. + """Sets a histogram control value associated with a list of keys. See :meth:`G2Phase.setHAPentryValue` for a related example. - :param list keylist: a list of dict keys, typically as returned by - :meth:`getHistEntryList`. + :param list keylist: a list of dict keys, typically as returned by + :meth:`getHistEntryList`. :param newvalue: a new value for the hist setting. The type must be - the same as the initial value, but if the value is a container + the same as the initial value, but if the value is a container (list, tuple, np.array,...) the elements inside are not checked. """ @@ -4233,19 +4355,19 @@ def setHistEntryValue(self, keylist, newvalue): dlast = d d = d[key] dlast[key] = newvalue - + def calc_autobkg(self,opt=0,logLam=None): - """Sets fixed background points using the pybaselines Whittaker + """Sets fixed background points using the pybaselines Whittaker algorithm. :param int opt: 0 for 'arpls' or 1 for 'iarpls'. Default is 0. - :param float logLam: log_10 of the Lambda value used in the + :param float logLam: log_10 of the Lambda value used in the pybaselines.whittaker.arpls/.iarpls computation. If None (default) - is provided, a guess is taken for an appropriate value based - on the number of points. + is provided, a guess is taken for an appropriate value based + on the number of points. - :returns: the array of computed background points + :returns: the array of computed background points """ bkgDict = self.data['Background'][1] xydata = self.data['data'][1] @@ -4269,18 +4391,18 @@ def calc_autobkg(self,opt=0,logLam=None): bkgdata.data[::npts//100])] bkgDict['autoPrms']['Mode'] = 'fixed' return bkgdata - + class G2Phase(G2ObjectWrapper): """A wrapper object around a given phase. The object contains these class variables: * G2Phase.proj: contains a reference to the :class:`G2Project` - object that contains this phase + object that contains this phase * G2Phase.name: contains the name of the phase * G2Phase.data: contains the phases's associated data in a dict, as documented for the :ref:`Phase Tree items`. - Scripts should not try to create a :class:`G2Phase` object directly as + Scripts should not try to create a :class:`G2Phase` object directly as :meth:`G2Phase.__init__` should be invoked from inside :class:`G2Project`. Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com) @@ -4330,14 +4452,14 @@ def atoms(self): return [G2AtomRecord(atom, ptrs, self.proj) for atom in self.data['Atoms']] def histograms(self): - '''Returns a list of histogram names associated with the current phase ordered + '''Returns a list of histogram names associated with the current phase ordered as they appear in the tree (see :meth:`G2Project.histograms`). ''' return [i.name for i in self.proj.histograms() if i.name in self.data.get('Histograms', {})] - + @property def composition(self): - '''Provides a dict where keys are atom types and values are the number of + '''Provides a dict where keys are atom types and values are the number of atoms of that type in cell (such as {'H': 2.0, 'O': 1.0}) ''' out = {} @@ -4351,10 +4473,9 @@ def composition(self): def mu(self,wave): '''Provides mu values for a phase at the supplied wavelength in A. - Uses GSASIImath.XScattDen which seems to be off by an order of + Uses GSASIImath.XScattDen which seems to be off by an order of magnitude, which has been corrected here. ''' - import GSASIImath as G2mth vol = self.data['General']['Cell'][7] out = {} for typ in self.data['General']['NoAtoms']: @@ -4365,16 +4486,15 @@ def mu(self,wave): out[typ]['Num'] = self.data['General']['NoAtoms'][typ] out[typ]['Z'] = 0 # wrong but not needed return 10*G2mth.XScattDen(out,vol,wave)[1] - + @property def density(self): - '''Provides a scalar with the density of the phase. In case of a + '''Provides a scalar with the density of the phase. In case of a powder this assumes a 100% packing fraction. ''' - import GSASIImath as G2mth density,mattCoeff = G2mth.getDensity(self.data['General']) return density - + @property def ranId(self): return self.data['ranId'] @@ -4406,11 +4526,11 @@ def add_atom(self,x,y,z,element,lbl,occ=1.,uiso=0.01): z = float(z) occ = float(occ) uiso = float(uiso) - + generalData = self.data['General'] atomData = self.data['Atoms'] SGData = generalData['SGData'] - + atId = ran.randint(0,sys.maxsize) Sytsym,Mult = G2spc.SytSym([x,y,z],SGData)[:2] @@ -4430,10 +4550,10 @@ def add_atom(self,x,y,z,element,lbl,occ=1.,uiso=0.01): atomData.append([lbl,element,'',x,y,z,occ,0.,0.,0.,Sytsym,Mult,'I',uiso,0,0,0,0,0,0,atId]) SetupGeneral(self.data, None) - #self.proj.index_ids() # needed? + #self.proj.index_ids() # needed? #self.proj.update_ids() # needed? return self.atom(lbl) - + def get_cell(self): """Returns a dictionary of the cell parameters, with keys: 'length_a', 'length_b', 'length_c', 'angle_alpha', 'angle_beta', 'angle_gamma', 'volume' @@ -4461,7 +4581,6 @@ def get_cell_and_esd(self): """ # translated from GSASIIfiles.ExportBaseclass.GetCell - import GSASIImapvars as G2mv try: pfx = str(self.id) + '::' sgdata = self['General']['SGData'] @@ -4476,7 +4595,7 @@ def get_cell_and_esd(self): sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],covDict['varyList'])) A, sigA = G2stIO.cellFill(pfx, sgdata, parmDict, sigDict) - cellSig = G2stIO.getCellEsd(pfx, sgdata, A, self.proj['Covariance']['data']) + cellSig = G2lat.getCellEsd(pfx, sgdata, A, self.proj['Covariance']['data']) cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),) cellDict, cellSigDict = {}, {} for i, key in enumerate(['length_a', 'length_b', 'length_c', @@ -4496,10 +4615,8 @@ def export_CIF(self, outputname, quickmode=True): :param bool quickmode: Currently ignored. Carryover from exports.G2export_CIF""" # This code is all taken from exports/G2export_CIF.py # Functions copied have the same names - import GSASIImath as G2mth - import GSASIImapvars as G2mv - from exports import G2export_CIF as cif + from GSASII.exports import G2export_CIF as cif # delay as only needed here # CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M") CIFname = os.path.splitext(self.proj.filename)[0] CIFname = os.path.split(CIFname)[1] @@ -4604,7 +4721,7 @@ def clear_refinements(self, refs): """Clears a given set of parameters. :param dict refs: The parameters to clear. - See the :ref:`Phase_parameters_table` table for what can be specified. + See the :ref:`Phase_parameters_table` table for what can be specified. """ for key, value in refs.items(): if key == "Cell": @@ -4631,21 +4748,21 @@ def set_HAP_refinements(self, refs, histograms='all'): the :ref:`HAP_parameters_table` table for a description of this dictionary. :param histograms: Either 'all' (default) or a list of the histograms by index, name - or object. The index number is relative to all histograms in the tree, not to - those in the phase. - Histograms not associated with the current phase will be ignored. + or object. The index number is relative to all histograms in the tree, not to + those in the phase. + Histograms not associated with the current phase will be ignored. whose HAP parameters will be set with this phase. Histogram and phase - must already be associated. + must already be associated. :returns: None """ if not self.data.get('Histograms',[]): G2fil.G2Print("Error likely: Phase {} has no linked histograms".format(self.name)) return - + if histograms == 'all': histograms = self.data['Histograms'].keys() else: - histograms = [self._decodeHist(h) for h in histograms + histograms = [self._decodeHist(h) for h in histograms if self._decodeHist(h) in self.data['Histograms']] if not histograms: G2fil.G2Print("Warning: Skipping HAP set for phase {}, no selected histograms".format(self.name)) @@ -4787,12 +4904,12 @@ def clear_HAP_refinements(self, refs, histograms='all'): the given histograms. :param dict refs: A dictionary of the parameters to be cleared. - See the the :ref:`HAP_parameters_table` table for what can be specified. + See the the :ref:`HAP_parameters_table` table for what can be specified. :param histograms: Either 'all' (default) or a list of the histograms by index, name - or object. - The index number is relative to all histograms in the tree, not to + or object. + The index number is relative to all histograms in the tree, not to those in the phase. - Histograms not associated with the current phase will be ignored. + Histograms not associated with the current phase will be ignored. whose HAP parameters will be set with this phase. Histogram and phase must already be associated :returns: None @@ -4800,8 +4917,8 @@ def clear_HAP_refinements(self, refs, histograms='all'): if histograms == 'all': histograms = self.data['Histograms'].keys() else: - histograms = [self._decodeHist(h) for h in histograms - if self._decodeHist(h) in self.data['Histograms']] + histograms = [self._decodeHist(h) for h in histograms + if self._decodeHist(h) in self.data['Histograms']] for key, val in refs.items(): for h in histograms: @@ -4859,13 +4976,13 @@ def _decodeHist(self,hist): return self.proj.histograms()[hist].name else: raise G2ScriptException("Invalid histogram reference: "+str(hist)) - + def getHAPvalues(self, histname): """Returns a dict with HAP values for the selected histogram :param histogram: is a histogram object (:class:`G2PwdrData`) or a histogram name or the index number of the histogram. - The index number is relative to all histograms in the tree, not to + The index number is relative to all histograms in the tree, not to those in the phase. :returns: HAP value dict """ @@ -4876,23 +4993,23 @@ def copyHAPvalues(self, sourcehist, targethistlist='all', skip=[], use=None): Use skip or use to select specific entries to be copied or not used. :param sourcehist: is a histogram object (:class:`G2PwdrData`) or - a histogram name or the index number of the histogram to copy + a histogram name or the index number of the histogram to copy parameters from. - The index number is relative to all histograms in the tree, not to + The index number is relative to all histograms in the tree, not to those in the phase. :param list targethistlist: a list of histograms where each item in the - list can be a histogram object (:class:`G2PwdrData`), + list can be a histogram object (:class:`G2PwdrData`), a histogram name or the index number of the histogram. - If the string 'all' (default), then all histograms in the phase + If the string 'all' (default), then all histograms in the phase are used. - :param list skip: items in the HAP dict that should not be + :param list skip: items in the HAP dict that should not be copied. The default is an empty list, which causes all items to be copied. To see a list of items in the dict, use :meth:`getHAPvalues`. Don't use with :attr:`use`. - :param list use: specifies the items in the HAP dict should be + :param list use: specifies the items in the HAP dict should be copied. The default is None, which causes all items - to be copied. + to be copied. Don't use with :attr:`skip`. examples:: @@ -4900,16 +5017,16 @@ def copyHAPvalues(self, sourcehist, targethistlist='all', skip=[], use=None): ph0.copyHAPvalues(0,[1,2,3]) ph0.copyHAPvalues(0,use=['HStrain','Size']) - The first example copies all HAP parameters from the first histogram to - the second, third and fourth histograms (as listed in the project tree). - The second example copies only the 'HStrain' (Dij parameters and + The first example copies all HAP parameters from the first histogram to + the second, third and fourth histograms (as listed in the project tree). + The second example copies only the 'HStrain' (Dij parameters and refinement flags) and the 'Size' (crystallite size settings, parameters and refinement flags) from the first histogram to all histograms. """ sourcehist = self._decodeHist(sourcehist) if targethistlist == 'all': targethistlist = self.histograms() - + copydict = copy.deepcopy(self.data['Histograms'][sourcehist]) for item in skip: if item == 'PhaseFraction': item = 'Scale' @@ -4935,25 +5052,25 @@ def copyHAPvalues(self, sourcehist, targethistlist='all', skip=[], use=None): G2fil.G2Print('Unexpected Warning: histogram {} not in phase {}'.format(h,self.name)) continue self.data['Histograms'][h].update(copy.deepcopy(copydict)) - + def setSampleProfile(self, histname, parmType, mode, val1, val2=None, axis=None, LGmix=None): - """Sets sample broadening parameters for a histogram associated with the - current phase. This currently supports isotropic and uniaxial broadening - modes only. + """Sets sample broadening parameters for a histogram associated with the + current phase. This currently supports isotropic and uniaxial broadening + modes only. :param histogram: is a histogram object (:class:`G2PwdrData`) or a histogram name or the index number of the histogram. - The index number is relative to all histograms in the tree, not to + The index number is relative to all histograms in the tree, not to those in the phase. :param str parmType: should be 'size' or 'microstrain' (can be abbreviated to 's' or 'm') :param str mode: should be 'isotropic' or 'uniaxial' (can be abbreviated to 'i' or 'u') - :param float val1: value for isotropic size (in :math:`\\mu m`) or + :param float val1: value for isotropic size (in :math:`\\mu m`) or microstrain (unitless, :math:`\\Delta Q/Q \\times 10^6`) or the equatorial value in the uniaxial case - :param float val2: value for axial size (in :math:`\\mu m`) or - axial microstrain (unitless, :math:`\\Delta Q/Q \\times 10^6`) - in uniaxial case; not used for isotropic + :param float val2: value for axial size (in :math:`\\mu m`) or + axial microstrain (unitless, :math:`\\Delta Q/Q \\times 10^6`) + in uniaxial case; not used for isotropic :param list axis: tuple or list with three values indicating the preferred direction - for uniaxial broadening; not used for isotropic + for uniaxial broadening; not used for isotropic :param float LGmix: value for broadening type (1=Lorentzian, 0=Gaussian or a value between 0 and 1. Default value (None) is ignored. @@ -4961,7 +5078,7 @@ def setSampleProfile(self, histname, parmType, mode, val1, val2=None, axis=None, phase0.setSampleProfile(0,'size','iso',1.2) phase0.setSampleProfile(0,'micro','isotropic',1234) - phase0.setSampleProfile(0,'m','u',1234,4567,[1,1,1],.5) + phase0.setSampleProfile(0,'m','u',1234,4567,[1,1,1],.5) phase0.setSampleProfile(0,'s','uni',1.2,2.3,[0,0,1]) """ if parmType.lower().startswith('s'): @@ -4986,48 +5103,55 @@ def setSampleProfile(self, histname, parmType, mode, val1, val2=None, axis=None, G2fil.G2Print('setSampleProfile Error: value for mode of {} is not isotropic or uniaxial'. format(mode)) raise Exception('Invalid parameter in setSampleProfile') - + d = self.data['Histograms'][self._decodeHist(histname)][key] if iso: d[0] = 'isotropic' d[1][0] = float(val1) if LGmix is not None: d[1][2] = float(LGmix) else: - d[3] = [int(axis[0]),int(axis[1]),int(axis[2])] + d[3] = [int(axis[0]),int(axis[1]),int(axis[2])] d[0] = 'uniaxial' d[1][0] = float(val1) d[1][1] = float(val2) - if LGmix is not None: d[1][2] = float(LGmix) + if LGmix is not None: d[1][2] = float(LGmix) def HAPvalue(self, param=None, newValue=None, targethistlist='all'): - """Retrieves or sets individual HAP parameters for one histogram or + """Retrieves or sets individual HAP parameters for one histogram or multiple histograms. - - :param str param: is a parameter name, which can be 'Scale' or - 'PhaseFraction' (either can be used for phase - fraction), 'Use', 'Extinction' or 'LeBail'. + :param str param: is a parameter name, which can be 'Scale' or + 'PhaseFraction' (either can be used for phase + fraction), 'Use', 'Extinction', 'LeBail', 'PO' + (for Preferred Orientation). If not specified or invalid an exception is generated showing the list of valid parameters. - At present, these HAP parameters cannot be access with this function: - 'Pref.Ori.', 'Size', 'Mustrain', 'HStrain', 'Babinet'. On request this - might be addressed in the future. Some of these values can be set via - :meth:`G2Phase.set_HAP_refinements`. - :param newValue: the value to use when setting the HAP parameter for the + At present, only these HAP parameters cannot be accessed with + this function: 'Size', 'Mustrain', 'HStrain', 'Babinet'. + This might be addressed in the future. + Some of these values can be set via + :meth:`G2Phase.set_HAP_refinements`. + :param newValue: the value to use when setting the HAP parameter for the appropriate histogram(s). Will be converted to the proper type or - an exception will be generated if not possible. If not specified, - and only one histogram is selected, the value is retrieved and - returned. + an exception will be generated if not possible. If not specified, + and only one histogram is selected, the value is retrieved and + returned. + When param='PO' then this value is interpreted as the following: + + if the value is 0 or an even integer, then the preferred + orientation model is set to "Spherical harmonics". If the value is + 1, then "March-Dollase" is used. Any other value generates an error. + :param list targethistlist: a list of histograms where each item in the - list can be a histogram object (:class:`G2PwdrData`), + list can be a histogram object (:class:`G2PwdrData`), a histogram name or the index number of the histogram. - The index number is relative to all histograms in the tree, not to + The index number is relative to all histograms in the tree, not to those in the phase. - If the string 'all' (default), then all histograms in the phase - are used. + If the string 'all' (default), then all histograms in the phase + are used. targethistlist must correspond to a single histogram if a value - is to be returned (when argument newValue is not specified). + is to be returned (i.e. when argument newValue is not specified). :returns: the value of the parameter, when argument newValue is not specified. @@ -5040,10 +5164,10 @@ def HAPvalue(self, param=None, newValue=None, targethistlist='all'): val = ph0.HAPvalue('PhaseFraction',targethistlist=[0]) ph0.HAPvalue('Scale',2.5) - The first command returns the phase fraction if only one histogram - is associated with the current phase, or raises an exception. - The second command returns the phase fraction from the first histogram - associated with the current phase. The third command sets the phase + The first command returns the phase fraction if only one histogram + is associated with the current phase, or raises an exception. + The second command returns the phase fraction from the first histogram + associated with the current phase. The third command sets the phase fraction for all histograms associated with the current phase. """ @@ -5055,18 +5179,21 @@ def HAPvalue(self, param=None, newValue=None, targethistlist='all'): refFloatParam = ('Scale','Extinction') useBool = False useFloat = False + useInt = False if param == 'PhaseFraction': param = 'Scale' if param in boolParam: useBool = True elif param in refFloatParam: useFloat = True + elif param in ['PO']: + useInt = True else: s = '' - for i in boolParam+refFloatParam+['PhaseFraction']: + for i in boolParam+refFloatParam+['PhaseFraction','PO']: if s != '': s += ', ' s += f'"{i}"' - raise G2ScriptException('Invalid parameter. Valid choices are: '+s) - if not doSet and len(targethistlist) > 1: + raise G2ScriptException(f'Invalid parameter. Valid choices are: {s}') + if not doSet and len(targethistlist) > 1: raise G2ScriptException(f'Unable to report value from {len(targethistlist)} histograms') for h in targethistlist: h = self._decodeHist(h) @@ -5074,38 +5201,60 @@ def HAPvalue(self, param=None, newValue=None, targethistlist='all'): G2fil.G2Print('Warning: histogram {} not in phase {}'.format(h,self.name)) continue if not doSet and useBool: - return self.data['Histograms'][h][param] + return self.data['Histograms'][h][param] elif not doSet and useFloat: return self.data['Histograms'][h][param][0] elif useBool: self.data['Histograms'][h][param] = bool(newValue) elif useFloat: self.data['Histograms'][h][param][0] = float(newValue) + elif useInt: + if newValue is None: + return self.data['Histograms'][h]['Pref.Ori.'] + try: + intval = int(newValue) + except: + intval = None + if intval == 1: + self.data['Histograms'][h]['Pref.Ori.'][0] = 'MD' + elif intval is not None and 2*int(intval//2) == intval: + SGData = self.data['General']['SGData'] + cofNames = G2lat.GenSHCoeff(SGData['SGLaue'],'0',intval,False) #cylindrical & no M + + self.data['Histograms'][h]['Pref.Ori.'][0] = 'SH' + self.data['Histograms'][h]['Pref.Ori.'][4] = intval + olddict = self.data['Histograms'][h]['Pref.Ori.'][5] + newdict = dict(zip(cofNames,len(cofNames)*[0.])) + # retain any old values in existing dict + newdict.update({i:olddict[i] for i in olddict if i in newdict}) + self.data['Histograms'][h]['Pref.Ori.'][5] = newdict + else: + raise G2ScriptException(f'Preferred orientation value of {newValue} is invalid') else: print('unexpected action') - + def setHAPvalues(self, HAPdict, targethistlist='all', skip=[], use=None): """Copies HAP parameters for one histogram to a list of other histograms. Use skip or use to select specific entries to be copied or not used. - Note that ``HStrain`` and sometimes ``Mustrain`` values can be specific to - a Laue class and should be copied with care between phases of different + Note that ``HStrain`` and sometimes ``Mustrain`` values can be specific to + a Laue class and should be copied with care between phases of different symmetry. A "sanity check" on the number of Dij terms is made if ``HStrain`` values are copied. - :param dict HAPdict: is a dict returned by :meth:`getHAPvalues` containing + :param dict HAPdict: is a dict returned by :meth:`getHAPvalues` containing HAP parameters. :param list targethistlist: a list of histograms where each item in the - list can be a histogram object (:class:`G2PwdrData`), + list can be a histogram object (:class:`G2PwdrData`), a histogram name or the index number of the histogram. - The index number is relative to all histograms in the tree, not to + The index number is relative to all histograms in the tree, not to those in the phase. - If the string 'all' (default), then all histograms in the phase + If the string 'all' (default), then all histograms in the phase are used. - :param list skip: items in the HAP dict that should not be + :param list skip: items in the HAP dict that should not be copied. The default is an empty list, which causes all items to be copied. To see a list of items in the dict, use :meth:`getHAPvalues`. Don't use with :attr:`use`. - :param list use: specifies the items in the HAP dict should be + :param list use: specifies the items in the HAP dict should be copied. The default is None, which causes all items to be copied. Don't use with :attr:`skip`. @@ -5114,9 +5263,9 @@ def setHAPvalues(self, HAPdict, targethistlist='all', skip=[], use=None): HAPdict = ph0.getHAPvalues(0) ph1.setHAPvalues(HAPdict,use=['HStrain','Size']) - This copies the Dij (hydrostatic strain) HAP parameters and the - crystallite size broadening terms from the first histogram in - phase ``ph0`` to all histograms in phase ``ph1``. + This copies the Dij (hydrostatic strain) HAP parameters and the + crystallite size broadening terms from the first histogram in + phase ``ph0`` to all histograms in phase ``ph1``. """ if targethistlist == 'all': targethistlist = self.histograms() @@ -5149,19 +5298,19 @@ def setHAPvalues(self, HAPdict, targethistlist='all', skip=[], use=None): format(len(copydict['HStrain'][0]), self.name,len(self.data['Histograms'][h]['HStrain'][0]))) raise Exception('HStrain has differing numbers of terms.') - self.data['Histograms'][h].update(copy.deepcopy(copydict)) + self.data['Histograms'][h].update(copy.deepcopy(copydict)) G2fil.G2Print('Copied item(s) {} from dict'.format(list(copydict.keys()))) G2fil.G2Print(' to histogram(s) {}'.format([self._decodeHist(h) for h in targethistlist])) def getPhaseEntryList(self, keyname=''): - """Returns a dict with control values. + """Returns a dict with control values. :param str keyname: an optional string. When supplied only entries where at least one key contains the specified string are reported. - Case is ignored, so 'sg' will find entries where one of the keys - is 'SGdata', etc. - :returns: a set of phase dict keys. Note that HAP items, while - technically part of the phase entries, are not included. + Case is ignored, so 'sg' will find entries where one of the keys + is 'SGdata', etc. + :returns: a set of phase dict keys. Note that HAP items, while + technically part of the phase entries, are not included. See :meth:`getHAPentryList` for a related example. @@ -5173,14 +5322,14 @@ def getPhaseEntryList(self, keyname=''): return [i for i in dictDive(self.data,keyname) if i[0] != ['Histograms']] def getPhaseEntryValue(self, keylist): - """Returns the value associated with a list of keys. - Where the value returned is a list, it may be used as the target of - an assignment (as in - ``getPhaseEntryValue(...)[...] = val``) - to set a value inside a list. + """Returns the value associated with a list of keys. + Where the value returned is a list, it may be used as the target of + an assignment (as in + ``getPhaseEntryValue(...)[...] = val``) + to set a value inside a list. - :param list keylist: a list of dict keys, typically as returned by - :meth:`getPhaseEntryList`. + :param list keylist: a list of dict keys, typically as returned by + :meth:`getPhaseEntryList`. :returns: a phase setting; may be a int, float, bool, list,... @@ -5193,13 +5342,13 @@ def getPhaseEntryValue(self, keylist): return d def setPhaseEntryValue(self, keylist, newvalue): - """Sets a phase control value associated with a list of keys. + """Sets a phase control value associated with a list of keys. - :param list keylist: a list of dict keys, typically as returned by - :meth:`getPhaseEntryList`. + :param list keylist: a list of dict keys, typically as returned by + :meth:`getPhaseEntryList`. :param newvalue: a new value for the phase setting. The type must be - the same as the initial value, but if the value is a container + the same as the initial value, but if the value is a container (list, tuple, np.array,...) the elements inside are not checked. See :meth:`setHAPentryValue` for a related example. @@ -5213,23 +5362,23 @@ def setPhaseEntryValue(self, keylist, newvalue): dlast = d d = d[key] dlast[key] = newvalue - + def getHAPentryList(self, histname=None, keyname=''): - """Returns a dict with HAP values. Optionally a histogram + """Returns a dict with HAP values. Optionally a histogram may be selected. :param histname: is a histogram object (:class:`G2PwdrData`) or a histogram name or the index number of the histogram. - The index number is relative to all histograms in the tree, not to - those in the phase. If no histogram is specified, all histograms - are selected. + The index number is relative to all histograms in the tree, not to + those in the phase. If no histogram is specified, all histograms + are selected. :param str keyname: an optional string. When supplied only entries where at least one key contains the specified string are reported. - Case is ignored, so 'sg' will find entries where one of the keys - is 'SGdata', etc. - :returns: a set of HAP dict keys. + Case is ignored, so 'sg' will find entries where one of the keys + is 'SGdata', etc. + :returns: a set of HAP dict keys. - Example: + Example: >>> p.getHAPentryList(0,'Scale') [(['PWDR test Bank 1', 'Scale'], list, [1.0, False])] @@ -5247,18 +5396,18 @@ def getHAPentryList(self, histname=None, keyname=''): def getHAPentryValue(self, keylist): """Returns the HAP value associated with a list of keys. Where the - value returned is a list, it may be used as the target of - an assignment (as in - ``getHAPentryValue(...)[...] = val``) + value returned is a list, it may be used as the target of + an assignment (as in + ``getHAPentryValue(...)[...] = val``) to set a value inside a list. - :param list keylist: a list of dict keys, typically as returned by - :meth:`getHAPentryList`. Note the first entry is a histogram name. + :param list keylist: a list of dict keys, typically as returned by + :meth:`getHAPentryList`. Note the first entry is a histogram name. Example: ``['PWDR hist1.fxye Bank 1', 'Scale']`` :returns: HAP value - Example: + Example: >>> sclEnt = p.getHAPentryList(0,'Scale')[0] >>> sclEnt @@ -5276,17 +5425,17 @@ def getHAPentryValue(self, keylist): return d def setHAPentryValue(self, keylist, newvalue): - """Sets an HAP value associated with a list of keys. + """Sets an HAP value associated with a list of keys. - :param list keylist: a list of dict keys, typically as returned by - :meth:`getHAPentryList`. Note the first entry is a histogram name. + :param list keylist: a list of dict keys, typically as returned by + :meth:`getHAPentryList`. Note the first entry is a histogram name. Example: ``['PWDR hist1.fxye Bank 1', 'Scale']`` :param newvalue: a new value for the HAP setting. The type must be - the same as the initial value, but if the value is a container + the same as the initial value, but if the value is a container (list, tuple, np.array,...) the elements inside are not checked. - Example: + Example: >>> sclEnt = p.getHAPentryList(0,'Scale')[0] >>> p.getHAPentryValue(sclEnt[0]) @@ -5310,6 +5459,7 @@ def setHAPentryValue(self, keylist, newvalue): def _getBondRest(self,nam): if 'Restraints' not in self.proj.data: raise G2ScriptException(f"{nam} error: Restraints entry not in data tree") + errmsg = '' try: return self.proj.data['Restraints']['data'][self.name]['Bond'] except: @@ -5341,23 +5491,23 @@ def addDistRestraint(self, origin, target, bond, factor=1.1, ESD=0.01): '''Adds bond distance restraint(s) for the selected phase This works by search for interatomic distances between atoms in the - origin list and the target list (the two lists may be the same but most + origin list and the target list (the two lists may be the same but most frequently will not) with a length between bond/factor and bond*factor. If a distance is found in that range, it is added to the restraints if it was not already found. - :param list origin: a list of atoms, each atom may be an atom + :param list origin: a list of atoms, each atom may be an atom object, an index or an atom label - :param list target: a list of atoms, each atom may be an atom + :param list target: a list of atoms, each atom may be an atom object, an index or an atom label :param float bond: the target bond length in A for the located atom - :param float factor: a tolerance factor used when searching for + :param float factor: a tolerance factor used when searching for bonds (defaults to 1.1) :param float ESD: the uncertainty for the bond (defaults to 0.01) :returns: returns the number of new restraints that are found - As an example:: + As an example:: gpx = G2sc.G2Project('restr.gpx') ph = gpx.phases()[0] @@ -5370,19 +5520,18 @@ def addDistRestraint(self, origin, target, bond, factor=1.1, ESD=0.01): gpx.save('restr-mod.gpx') This example locates the first phase in a project file, clears any previous - restraints. Then it places restraints on bonds between Si and O atoms at - 1.64 A. Each restraint is weighted 1000 times in comparison to - (obs-calc)/sigma for a data point. To show how atom selection can - work, the origin atoms are identified here - by atom object while the target atoms are identified by atom index. + restraints. Then it places restraints on bonds between Si and O atoms at + 1.64 A. Each restraint is weighted 1000 times in comparison to + (obs-calc)/sigma for a data point. To show how atom selection can + work, the origin atoms are identified here + by atom object while the target atoms are identified by atom index. The methods are interchangeable. If atom labels are unique, then:: origin = [a.label for a in ph.atoms() if a.element == 'Si'] - - would also work identically. + + would also work identically. ''' - import GSASIImath as G2mth bondRestData = self._getBondRest('addDistRestraint') originList = [] for a in origin: @@ -5414,7 +5563,7 @@ def addDistRestraint(self, origin, target, bond, factor=1.1, ESD=0.01): raise G2ScriptException( f'addDistRestraint error: Target atom input {a} has unknown type ({type(a)})') targetList.append([ao.ranId, ao.element, list(ao.coordinates)]) - + GType = self.data['General']['Type'] SGData = self.data['General']['SGData'] Amat,Bmat = G2lat.cell2AB(self.data['General']['Cell'][1:7]) @@ -5425,21 +5574,18 @@ def addDistRestraint(self, origin, target, bond, factor=1.1, ESD=0.01): count += 1 bondRestData['Bonds'].append(newBond) return count - + class G2SeqRefRes(G2ObjectWrapper): - '''Wrapper for a Sequential Refinement Results tree entry, containing the + '''Wrapper for a Sequential Refinement Results tree entry, containing the results for a refinement - Scripts should not try to create a :class:`G2SeqRefRes` object directly as - this object will be created when a .gpx project file is read. + Scripts should not try to create a :class:`G2SeqRefRes` object directly as + this object will be created when a .gpx project file is read. - As an example:: + As an example:: - from __future__ import division, print_function - import os,sys - sys.path.insert(0,'/Users/toby/software/G2/GSASII') + import os PathWrap = lambda fil: os.path.join('/Users/toby/Scratch/SeqTut2019Mar',fil) - import GSASIIscriptable as G2sc gpx = G2sc.G2Project(PathWrap('scr4.gpx')) seq = gpx.seqref() lbl = ('a','b','c','alpha','beta','gamma','Volume') @@ -5481,15 +5627,15 @@ def get_cell_and_esd(self,phase,hist): :param phase: A phase, which may be specified as a phase object (see :class:`G2Phase`), the phase name (str) or the index number (int) of the phase in the project, numbered starting from 0. - :param hist: Specify a histogram or using the histogram name (str) - or the index number (int) of the histogram in the sequential + :param hist: Specify a histogram or using the histogram name (str) + or the index number (int) of the histogram in the sequential refinement (not the project), numbered as in in the project tree starting from 0. - :returns: cell,cellESD,uniqCellIndx where cell (list) + :returns: cell,cellESD,uniqCellIndx where cell (list) with the unit cell parameters (a,b,c,alpha,beta,gamma,Volume); - cellESD are the standard uncertainties on the 7 unit cell - parameters; and uniqCellIndx is a tuple with indicies for the - unique (non-symmetry determined) unit parameters (e.g. + cellESD are the standard uncertainties on the 7 unit cell + parameters; and uniqCellIndx is a tuple with indicies for the + unique (non-symmetry determined) unit parameters (e.g. [0,2] for a,c in a tetragonal cell) ''' def striphist(var,insChar=''): @@ -5499,8 +5645,7 @@ def striphist(var,insChar=''): if sv[1]: sv[1] = insChar return ':'.join(sv) - import GSASIIstrIO as G2stIO - + uniqCellLookup = [ [['m3','m3m'],(0,)], [['3R','3mR'],(0,3)], @@ -5557,29 +5702,29 @@ def striphist(var,insChar=''): A[i] += seqData['parmDict'][Dvar] # override with fit result if is Dij varied try: - A[i] = seqData['newCellDict'][esdLookUp[var]][1] # get refined value + A[i] = seqData['newCellDict'][esdLookUp[var]][1] # get refined value except KeyError: pass Albls = [pfx+'A'+str(i) for i in range(6)] cellDict = dict(zip(Albls,A)) - + A,zeros = G2stIO.cellFill(pfx,SGdata,cellDict,zeroDict) # convert to direct cell c = G2lat.A2cell(A) vol = G2lat.calc_V(A) - cE = G2stIO.getCellEsd(pfx,SGdata,A,covData) + cE = G2lat.getCellEsd(pfx,SGdata,A,covData) return list(c)+[vol],cE,uniqCellIndx - + def get_VaryList(self,hist): - '''Returns a list of the refined variables in the + '''Returns a list of the refined variables in the last refinement cycle for the selected histogram - :param hist: Specify a histogram or using the histogram name (str) - or the index number (int) of the histogram in the sequential + :param hist: Specify a histogram or using the histogram name (str) + or the index number (int) of the histogram in the sequential refinement (not the project), numbered starting from 0. :returns: a list of variables or None if no refinement has been - performed. + performed. ''' try: seqData,histData = self.RefData(hist) @@ -5588,15 +5733,15 @@ def get_VaryList(self,hist): return def get_ParmList(self,hist): - '''Returns a list of all the parameters defined in the + '''Returns a list of all the parameters defined in the last refinement cycle for the selected histogram - :param hist: Specify a histogram or using the histogram name (str) - or the index number (int) of the histogram in the sequential - refinement (not the project), numbered as in the project tree + :param hist: Specify a histogram or using the histogram name (str) + or the index number (int) of the histogram in the sequential + refinement (not the project), numbered as in the project tree starting from 0. :returns: a list of parameters or None if no refinement has been - performed. + performed. ''' try: seqData,histData = self.RefData(hist) @@ -5605,19 +5750,19 @@ def get_ParmList(self,hist): return def get_Variable(self,hist,var): - '''Returns the value and standard uncertainty (esd) for a variable - parameters, as defined for the selected histogram + '''Returns the value and standard uncertainty (esd) for a variable + parameters, as defined for the selected histogram in the last sequential refinement cycle - :param hist: Specify a histogram or using the histogram name (str) - or the index number (int) of the histogram in the sequential - refinement (not the project), numbered as in the project tree + :param hist: Specify a histogram or using the histogram name (str) + or the index number (int) of the histogram in the sequential + refinement (not the project), numbered as in the project tree starting from 0. - :param str var: a variable name of form '

::', such as + :param str var: a variable name of form '

::', such as ':0:Scale' - :returns: (value,esd) if the parameter is refined or - (value, None) if the variable is in a constraint or is not - refined or None if the parameter is not found. + :returns: (value,esd) if the parameter is refined or + (value, None) if the variable is in a constraint or is not + refined or None if the parameter is not found. ''' try: seqData,histData = self.RefData(hist) @@ -5632,22 +5777,22 @@ def get_Variable(self,hist,var): return (val,None) def get_Covariance(self,hist,varList): - '''Returns the values and covariance matrix for a series of variable - parameters, as defined for the selected histogram + '''Returns the values and covariance matrix for a series of variable + parameters, as defined for the selected histogram in the last sequential refinement cycle - :param hist: Specify a histogram or using the histogram name (str) - or the index number (int) of the histogram in the sequential - refinement (not the project), numbered as in the project tree + :param hist: Specify a histogram or using the histogram name (str) + or the index number (int) of the histogram in the sequential + refinement (not the project), numbered as in the project tree starting from 0. :param tuple varList: a list of variable names of form '

::' :returns: (valueList,CovMatrix) where valueList contains the (n) values - in the same order as varList (also length n) and CovMatrix is a + in the same order as varList (also length n) and CovMatrix is a (n x n) matrix. If any variable name is not found in the varyList - then None is returned. + then None is returned. - Use this code, where sig provides standard uncertainties for - parameters and where covArray provides the correlation between + Use this code, where sig provides standard uncertainties for + parameters and where covArray provides the correlation between off-diagonal terms:: sig = np.sqrt(np.diag(covMatrix)) @@ -5665,38 +5810,38 @@ def get_Covariance(self,hist,varList): G2fil.G2Print('Warning: Variable(s) {} were not found in the varyList'.format(missing)) return None vals = [seqData['parmDict'][i] for i in varList] - import GSASIImath as G2mth cov = G2mth.getVCov(varList,seqData['varyList'],seqData['covMatrix']) return (vals,cov) - + def RefData(self,hist): '''Provides access to the output from a particular histogram - :param hist: Specify a histogram or using the histogram name (str) - or the index number (int) of the histogram in the sequential - refinement (not the project), numbered as in the project tree + :param hist: Specify a histogram or using the histogram name (str) + or the index number (int) of the histogram in the sequential + refinement (not the project), numbered as in the project tree starting from 0. - :returns: a list of dicts where the first element has sequential - refinement results and the second element has the contents of + :returns: a list of dicts where the first element has sequential + refinement results and the second element has the contents of the histogram tree items. ''' + errmsg = '' try: hist = self.data['histNames'][hist] except IndexError: - raise Exception('Histogram #{} is out of range from the Sequential Refinement' - .format(hist)) + errmsg = 'Histogram #{hist} is out of range from the Sequential Refinement' except TypeError: pass + if errmsg: raise Exception(errmsg) if hist not in self.data['histNames']: raise Exception('Histogram {} is not included in the Sequential Refinement' .format(hist)) return self.data[hist],self.proj.histogram(hist).data class G2PDF(G2ObjectWrapper): - """Wrapper for a PDF tree entry, containing the information needed to - compute a PDF and the S(Q), G(r) etc. after the computation is done. - Note that in a GSASIIscriptable script, instances of G2PDF will be created by - calls to :meth:`G2Project.add_PDF` or :meth:`G2Project.pdf`. + """Wrapper for a PDF tree entry, containing the information needed to + compute a PDF and the S(Q), G(r) etc. after the computation is done. + Note that in a GSASIIscriptable script, instances of G2PDF will be created by + calls to :meth:`G2Project.add_PDF` or :meth:`G2Project.pdf`. Scripts should not try to create a :class:`G2PDF` object directly. Example use of :class:`G2PDF`:: @@ -5722,18 +5867,18 @@ def set_background(self,btype,histogram,mult=-1.,refine=False): '''Sets a histogram to be used as the 'Sample Background', the 'Container' or the 'Container Background.' - :param str btype: Type of background to set, must contain - the string 'samp' for Sample Background', 'cont' and 'back' - for the 'Container Background' or only 'cont' for the - 'Container'. Note that capitalization and extra characters - are ignored, so the full strings (such as 'Sample + :param str btype: Type of background to set, must contain + the string 'samp' for Sample Background', 'cont' and 'back' + for the 'Container Background' or only 'cont' for the + 'Container'. Note that capitalization and extra characters + are ignored, so the full strings (such as 'Sample Background' & 'Container Background') can be used. - + :param histogram: A reference to a histogram, which can be reference by object, name, or number. - :param float mult: a multiplier for the histogram; defaults + :param float mult: a multiplier for the histogram; defaults to -1.0 - :param bool refine: a flag to enable refinement (only + :param bool refine: a flag to enable refinement (only implemented for 'Sample Background'); defaults to False ''' if 'samp' in btype.lower(): @@ -5749,7 +5894,7 @@ def set_background(self,btype,histogram,mult=-1.,refine=False): self.data['PDF Controls'][key]['Refine'] = refine def set_formula(self,*args): - '''Set the chemical formula for the PDF computation. + '''Set the chemical formula for the PDF computation. Use pdf.set_formula(['Si',1],['O',2]) for SiO2. :param list item1: The element symbol and number of atoms in formula for first element @@ -5768,30 +5913,30 @@ def set_formula(self,*args): Avol = (4.*np.pi/3.)*ElList[elem]['Drad']**3 sumVol += Avol*ElList[elem]['FormulaNo'] self.data['PDF Controls']['Form Vol'] = max(10.0,sumVol) - + def calculate(self,xydata=None,limits=None,inst=None): '''Compute the PDF using the current parameters. Results are set - in the PDF object arrays (self.data['PDF Controls']['G(R)'] etc.). + in the PDF object arrays (self.data['PDF Controls']['G(R)'] etc.). Note that if ``xydata``, is specified, the background histograms(s) - will not be accessed from the project file associated with the current + will not be accessed from the project file associated with the current PDF entry. If ``limits`` and ``inst`` are both specified, no histograms need be in the current project. However, the self.data['PDF Controls'] - sections ('Sample', 'Sample Bkg.','Container Bkg.') must be + sections ('Sample', 'Sample Bkg.','Container Bkg.') must be non-blank for the corresponding items to be used from``xydata``. - :param dict xydata: an array containing the Sample's I vs Q, and - any or none of the Sample Background, the Container scattering and + :param dict xydata: an array containing the Sample's I vs Q, and + any or none of the Sample Background, the Container scattering and the Container Background. If xydata is None (default), the values are taken from histograms, as named in the PDF's self.data['PDF Controls'] - entries with keys 'Sample', 'Sample Bkg.','Container Bkg.' & + entries with keys 'Sample', 'Sample Bkg.','Container Bkg.' & 'Container'. - :param list limits: upper and lower Q values to be used for PDF + :param list limits: upper and lower Q values to be used for PDF computation. If None (default), the values are - taken from the Sample histogram's .data['Limits'][1] values. + taken from the Sample histogram's .data['Limits'][1] values. :param dict inst: The Sample histogram's instrument parameters to be used for PDF computation. If None (default), the values are taken from the Sample histogram's .data['Instrument Parameters'][0] - values. + values. ''' data = self.data['PDF Controls'] if xydata is None: @@ -5815,32 +5960,32 @@ def calculate(self,xydata=None,limits=None,inst=None): def optimize(self,showFit=True,maxCycles=5, xydata=None,limits=None,inst=None): - '''Optimize the low R portion of G(R) to minimize selected - parameters. Note that this updates the parameters in the settings - (self.data['PDF Controls']) but does not update the PDF object - arrays (self.data['PDF Controls']['G(R)'] etc.) with the computed - values, use :meth:`calculate` after a fit to do that. + '''Optimize the low R portion of G(R) to minimize selected + parameters. Note that this updates the parameters in the settings + (self.data['PDF Controls']) but does not update the PDF object + arrays (self.data['PDF Controls']['G(R)'] etc.) with the computed + values, use :meth:`calculate` after a fit to do that. :param bool showFit: if True (default) the optimized parameters - are shown before and after the fit, as well as the RMS value + are shown before and after the fit, as well as the RMS value in the minimized region. :param int maxCycles: the maximum number of least-squares cycles; - defaults to 5. - :returns: the result from the optimizer as True or False, depending + defaults to 5. + :returns: the result from the optimizer as True or False, depending on if the refinement converged. - :param dict xydata: an array containing the Sample's I vs Q, and - any or none of the Sample Background, the Container scattering and + :param dict xydata: an array containing the Sample's I vs Q, and + any or none of the Sample Background, the Container scattering and the Container Background. If xydata is None (default), the values are taken from histograms, as named in the PDF's self.data['PDF Controls'] - entries with keys 'Sample', 'Sample Bkg.','Container Bkg.' & + entries with keys 'Sample', 'Sample Bkg.','Container Bkg.' & 'Container'. - :param list limits: upper and lower Q values to be used for PDF + :param list limits: upper and lower Q values to be used for PDF computation. If None (default), the values are - taken from the Sample histogram's .data['Limits'][1] values. + taken from the Sample histogram's .data['Limits'][1] values. :param dict inst: The Sample histogram's instrument parameters to be used for PDF computation. If None (default), the values are taken from the Sample histogram's .data['Instrument Parameters'][0] - values. + values. ''' data = self.data['PDF Controls'] if xydata is None: @@ -5858,17 +6003,17 @@ def optimize(self,showFit=True,maxCycles=5, res = G2pwd.OptimizePDF(data,xydata,limits,inst,showFit,maxCycles) return res['success'] - + def export(self,fileroot,formats): '''Write out the PDF-related data (G(r), S(Q),...) into files - :param str fileroot: name of file(s) to be written. The extension - will be ignored and set to .iq, .sq, .fq or .gr depending + :param str fileroot: name of file(s) to be written. The extension + will be ignored and set to .iq, .sq, .fq or .gr depending on the formats selected. :param str formats: string specifying the file format(s) to be written, should contain at least one of the following keywords: I(Q), S(Q), F(Q), G(r) and/or PDFgui (capitalization and - punctuation is ignored). Note that G(r) and PDFgui should not + punctuation is ignored). Note that G(r) and PDFgui should not be specifed together. ''' PDFsaves = 5*[False] @@ -5882,14 +6027,14 @@ def export(self,fileroot,formats): class G2Single(G2ObjectWrapper): """Wrapper for a HKLF tree entry, containing a single crystal histogram - Note that in a GSASIIscriptable script, instances of G2Single will be - created by calls to :meth:`G2Project.histogram`, + Note that in a GSASIIscriptable script, instances of G2Single will be + created by calls to :meth:`G2Project.histogram`, :meth:`G2Project.histograms`, or :meth:`G2Project.add_single_histogram`. Scripts should not try to create a :class:`G2Single` object directly. This object contains these class variables: * G2Single.proj: contains a reference to the :class:`G2Project` - object that contains this histogram + object that contains this histogram * G2Single.name: contains the name of the histogram * G2Single.data: contains the histogram's associated data in a dict, as documented for the :ref:`Single Crystal Tree Item`. @@ -5901,7 +6046,7 @@ class G2Single(G2ObjectWrapper): gpx0.add_single_histogram('HTO_xray/xtal1/xs2555a.hkl',0,fmthint='Shelx HKLF 4') gpx0.save('HTO_scripted.gpx') - This opens an existing GSAS-II project file and adds a single + This opens an existing GSAS-II project file and adds a single crystal dataset that is linked to the first phase and saves it under a new name. @@ -5915,17 +6060,17 @@ def __init__(self, data, proj, name): self.data = data self.name = name self.proj = proj - + @staticmethod def is_valid_refinement_key(key): valid_keys = ["Single xtal"] return key in valid_keys - + def set_refinements(self, refs): - """Sets the HKLF histogram refinement parameter 'key' to the + """Sets the HKLF histogram refinement parameter 'key' to the specification 'value'. - :param dict refs: A dictionary of the parameters to be set. See the + :param dict refs: A dictionary of the parameters to be set. See the :ref:`Histogram_parameters_table` table for a description of what these dictionaries should be. @@ -5947,23 +6092,23 @@ def set_refinements(self, refs): elif key in d['Babinet']: # BabA & BabU d['Babinet'][key][1] = value elif key in d['Extinction'][2]: # Eg, Es, Ep - d['Extinction'][2][key][1] = value + d['Extinction'][2][key][1] = value else: raise ValueError(f'set_refinements: {key} is unknown') - + def clear_refinements(self, refs): """Clears the HKLF refinement parameter 'key' and its associated value. :param dict refs: A dictionary of parameters to clear. - See the :ref:`Histogram_parameters_table` table for what can be specified. + See the :ref:`Histogram_parameters_table` table for what can be specified. Example:: hist.clear_refinements(['Scale','Es','Flack']) hist.clear_refinements({'Scale':True,'Es':False,'Flack':True}) - Note that the two above commands are equivalent: the values specified - in the dict in the second command are ignored. + Note that the two above commands are equivalent: the values specified + in the dict in the second command are ignored. """ # find the phase associated with this histogram (there is at most one) for p in self.proj.phases(): @@ -5982,18 +6127,18 @@ def clear_refinements(self, refs): d['Extinction'][2][key][1] = False else: raise ValueError(f'clear_refinements: {key} is unknown') - + def Export(self,fileroot,extension,fmthint=None): - '''Write the HKLF histogram into a file. The path is specified by + '''Write the HKLF histogram into a file. The path is specified by fileroot and extension. - + :param str fileroot: name of the file, optionally with a path (extension is ignored) :param str extension: includes '.', must match an extension in global exportersByExtension['single'] or a Exception is raised. - :param str fmthint: If specified, the first exporter where the format + :param str fmthint: If specified, the first exporter where the format name (obj.formatName, as shown in Export menu) contains the - supplied string will be used. If not specified, an error + supplied string will be used. If not specified, an error will be generated showing the possible choices. :returns: name of file that was written ''' @@ -6024,42 +6169,42 @@ def Export(self,fileroot,extension,fmthint=None): return fil blkSize = 128 -'''Integration block size; 128 or 256 seems to be optimal for CPU use, but 128 uses -less memory, must be <=1024 (for polymask/histogram3d) +'''Integration block size; 128 or 256 seems to be optimal for CPU use, but 128 uses +less memory, must be <=1024 (for histogram3d) ''' def calcMaskMap(imgprms,mskprms): - '''Computes a set of blocked mask arrays for a set of image controls and mask parameters. + '''Computes a set of blocked mask arrays for a set of image controls and mask parameters. This capability is also provided with :meth:`G2Image.IntMaskMap`. ''' return G2img.MakeUseMask(imgprms,mskprms,blkSize) def calcThetaAzimMap(imgprms): - '''Computes the set of blocked arrays for theta-azimuth mapping from - a set of image controls, which can be cached and reused for - integration of multiple images with the same calibration parameters. + '''Computes the set of blocked arrays for theta-azimuth mapping from + a set of image controls, which can be cached and reused for + integration of multiple images with the same calibration parameters. This capability is also provided with :meth:`G2Image.IntThetaAzMap`. ''' return G2img.MakeUseTA(imgprms,blkSize) class G2Image(G2ObjectWrapper): - '''Wrapper for an IMG tree entry, containing an image and associated metadata. + '''Wrapper for an IMG tree entry, containing an image and associated metadata. - Note that in a GSASIIscriptable script, instances of G2Image will be created by - calls to :meth:`G2Project.add_image` or :meth:`G2Project.images`. - Scripts should not try to create a :class:`G2Image` object directly as + Note that in a GSASIIscriptable script, instances of G2Image will be created by + calls to :meth:`G2Project.add_image` or :meth:`G2Project.images`. + Scripts should not try to create a :class:`G2Image` object directly as :meth:`G2Image.__init__` should be invoked from inside :class:`G2Project`. The object contains these class variables: * G2Image.proj: contains a reference to the :class:`G2Project` - object that contains this image + object that contains this image * G2Image.name: contains the name of the image * G2Image.data: contains the image's associated data in a dict, as documented for the :ref:`Image Data Structure`. - * G2Image.image: optionally contains a cached the image to + * G2Image.image: optionally contains a cached the image to save time in reloading. This is saved only when cacheImage=True - is specified when :meth:`G2Project.add_image` is called. + is specified when :meth:`G2Project.add_image` is called. Example use of G2Image: @@ -6071,8 +6216,8 @@ class G2Image(G2ObjectWrapper): >>> imlst[0].Recalibrate() >>> imlst[0].setControl('outAzimuths',3) >>> pwdrList = imlst[0].Integrate() - - More detailed image processing examples are shown in the + + More detailed image processing examples are shown in the :ref:`ImageProc` section of this chapter. ''' @@ -6092,14 +6237,14 @@ class G2Image(G2ObjectWrapper): 'pixelSize', 'range', 'ring', 'rings', 'size', ], 'dict': ['varyList'], } - '''Defines the items known to exist in the Image Controls tree section + '''Defines the items known to exist in the Image Controls tree section and the item's data types. A few are not included here ('background image', 'dark image', 'Gain map', and 'calibrant') because these items have special set routines, where references to entries are checked to make sure their values are correct. - ''' - + ''' + def __init__(self, data, name, proj, image=None): self.data = data self.name = name @@ -6110,15 +6255,15 @@ def clearImageCache(self): '''Clears a cached image, if one is present ''' self.image = None - + def setControl(self,arg,value): '''Set an Image Controls parameter in the current image. If the parameter is not found an exception is raised. - :param str arg: the name of a parameter (dict entry) in the + :param str arg: the name of a parameter (dict entry) in the image. The parameter must be found in :data:`ControlList` or an exception is raised. - :param value: the value to set the parameter. The value is + :param value: the value to set the parameter. The value is cast as the appropriate type from :data:`ControlList`. ''' for typ in self.ControlList: @@ -6127,6 +6272,7 @@ def setControl(self,arg,value): G2fil.G2Print('Allowed args:\n',[nam for nam,typ in self.findControl('')]) raise Exception('arg {} not defined in G2Image.setControl' .format(arg)) + errmsg = '' try: if typ == 'int': self.data['Image Controls'][arg] = int(value) @@ -6144,15 +6290,16 @@ def setControl(self,arg,value): raise Exception('Unknown type {} for arg {} in G2Image.setControl' .format(typ,arg)) except: - raise Exception('Error formatting value {} as type {} for arg {} in G2Image.setControl' - .format(value,typ,arg)) + errmsg = 'Error formatting value {value} as type {typ} for arg {arg} in G2Image.setControl' + + if errmsg: raise Exception(errmsg) def getControl(self,arg): '''Return an Image Controls parameter in the current image. If the parameter is not found an exception is raised. - :param str arg: the name of a parameter (dict entry) in the - image. + :param str arg: the name of a parameter (dict entry) in the + image. :returns: the value as a int, float, list,... ''' if arg in self.data['Image Controls']: @@ -6162,18 +6309,18 @@ def getControl(self,arg): def findControl(self,arg=''): '''Finds the Image Controls parameter(s) in the current image - that match the string in arg. Default is '' which returns all + that match the string in arg. Default is '' which returns all parameters. - Example: + Example: >>> findControl('calib') [['calibskip', 'int'], ['calibdmin', 'float'], ['calibrant', 'str']] - :param str arg: a string containing part of the name of a - parameter (dict entry) in the image's Image Controls. - :returns: a list of matching entries in form - [['item','type'], ['item','type'],...] where each 'item' string + :param str arg: a string containing part of the name of a + parameter (dict entry) in the image's Image Controls. + :returns: a list of matching entries in form + [['item','type'], ['item','type'],...] where each 'item' string contains the sting in arg. ''' matchList = [] @@ -6190,24 +6337,24 @@ def setCalibrant(self,calib): the entries in file ImageCalibrants.py. This is validated and an error provides a list of valid choices. ''' - import ImageCalibrants as calFile + from . import ImageCalibrants as calFile if calib in calFile.Calibrants.keys(): self.data['Image Controls']['calibrant'] = calib return G2fil.G2Print('Calibrant {} is not valid. Valid calibrants'.format(calib)) for i in calFile.Calibrants.keys(): if i: G2fil.G2Print('\t"{}"'.format(i)) - + def setControlFile(self,typ,imageRef,mult=None): '''Set a image to be used as a background/dark/gain map image :param str typ: specifies image type, which must be one of: 'background image', 'dark image', 'gain map'; N.B. only the first four characters must be specified and case is ignored. - :param imageRef: A reference to the desired image. Either the Image + :param imageRef: A reference to the desired image. Either the Image tree name (str), the image's index (int) or a image object (:class:`G2Image`) - :param float mult: a multiplier to be applied to the image (not used + :param float mult: a multiplier to be applied to the image (not used for 'Gain map'; required for 'background image', 'dark image' ''' if 'back' in typ.lower(): @@ -6232,10 +6379,10 @@ def setControlFile(self,typ,imageRef,mult=None): def loadControls(self,filename=None,imgDict=None): '''load controls from a .imctrl file - :param str filename: specifies a file to be read, which should end - with .imctrl (defaults to None, meaning parameters are input + :param str filename: specifies a file to be read, which should end + with .imctrl (defaults to None, meaning parameters are input with imgDict.) - :param dict imgDict: contains a set of image parameters (defaults to + :param dict imgDict: contains a set of image parameters (defaults to None, meaning parameters are input with filename.) ''' if filename: @@ -6253,7 +6400,7 @@ def loadControls(self,filename=None,imgDict=None): def saveControls(self,filename): '''write current controls values to a .imctrl file - :param str filename: specifies a file to write, which should end + :param str filename: specifies a file to write, which should end with .imctrl ''' G2fil.WriteControls(filename,self.data['Image Controls']) @@ -6274,20 +6421,20 @@ def getControls(self,clean=False): for i in 'range','size','GonioAngles': if i in ImageControls: del ImageControls[i] return ImageControls - + def setControls(self,controlsDict): '''uses dict from :meth:`getControls` to set Image Controls for current image ''' self.data['Image Controls'].update(copy.deepcopy(controlsDict)) - - + + def loadMasks(self,filename,ignoreThreshold=False): '''load masks from a .immask file - :param str filename: specifies a file to be read, which should end + :param str filename: specifies a file to be read, which should end with .immask :param bool ignoreThreshold: If True, masks are loaded with - threshold masks. Default is False which means any Thresholds + threshold masks. Default is False which means any Thresholds in the file are ignored. ''' G2fil.readMasks(filename,self.data['Masks'],ignoreThreshold) @@ -6304,19 +6451,19 @@ def initMasks(self): Imin = max(0.,np.min(ImageZ)) Imax = np.max(ImageZ) self.data['Masks']['Thresholds'] = [(0,Imax),[Imin,Imax]] - + def getMasks(self): '''load masks from an IMG tree entry ''' return self.data['Masks'] - + def setMasks(self,maskDict,resetThresholds=False): '''load masks dict (from :meth:`getMasks`) into current IMG record - :param dict maskDict: specifies a dict with image parameters, + :param dict maskDict: specifies a dict with image parameters, from :meth:`getMasks` - :param bool resetThresholds: If True, Threshold Masks in the - dict are ignored. The default is False which means Threshold + :param bool resetThresholds: If True, Threshold Masks in the + dict are ignored. The default is False which means Threshold Masks are retained. ''' self.data['Masks'] = copy.deepcopy(maskDict) @@ -6327,50 +6474,50 @@ def setMasks(self,maskDict,resetThresholds=False): ImageZ = _getCorrImage(Readers['Image'],self.proj,self) Imin = max(0.,np.min(ImageZ)) Imax = np.max(ImageZ) - self.data['Masks']['Thresholds'] = [(0,Imax),[Imin,Imax]] + self.data['Masks']['Thresholds'] = [(0,Imax),[Imin,Imax]] def IntThetaAzMap(self): - '''Computes the set of blocked arrays for 2theta-azimuth mapping from + '''Computes the set of blocked arrays for 2theta-azimuth mapping from the controls settings of the current image for image integration. - The output from this is optionally supplied as input to - :meth:`~G2Image.Integrate`. Note that if not supplied, image - integration will compute this information as it is needed, but this - is a relatively slow computation so time can be saved by caching and - reusing this computation for other images that have the + The output from this is optionally supplied as input to + :meth:`~G2Image.Integrate`. Note that if not supplied, image + integration will compute this information as it is needed, but this + is a relatively slow computation so time can be saved by caching and + reusing this computation for other images that have the same calibration parameters as the current image. ''' return G2img.MakeUseTA(self.getControls(),blkSize) def IntMaskMap(self): - '''Computes a series of masking arrays for the current image (based on - mask input, but not calibration parameters or the image intensities). - See :meth:`GSASIIimage.MakeMaskMap` for more details. The output from + '''Computes a series of masking arrays for the current image (based on + mask input, but not calibration parameters or the image intensities). + See :meth:`GSASIIimage.MakeMaskMap` for more details. The output from this is optionally supplied as input to :meth:`~G2Image.Integrate`). Note this is not the same as pixel mask searching (:meth:`~G2Image.GeneratePixelMask`). ''' return G2img.MakeUseMask(self.getControls(),self.getMasks(),blkSize) - + def MaskThetaMap(self): - '''Computes the theta mapping matrix from the controls settings + '''Computes the theta mapping matrix from the controls settings of the current image to be used for pixel mask computation in :meth:`~G2Image.GeneratePixelMask`. This is optional, as if not supplied, mask computation will compute this, but this is a relatively slow computation and the - results computed here can be reused for other images that have the + results computed here can be reused for other images that have the same calibration parameters. ''' ImShape = self.getControls()['size'] return G2img.Make2ThetaAzimuthMap(self.getControls(), (0, ImShape[0]), (0, ImShape[1]))[0] - + def MaskFrameMask(self): - '''Computes a Frame mask from map input for the current image to be - used for a pixel mask computation in + '''Computes a Frame mask from map input for the current image to be + used for a pixel mask computation in :meth:`~G2Image.GeneratePixelMask`. This is optional, as if not supplied, mask computation will compute this, but this is a relatively slow computation and the - results computed here can be reused for other images that have the + results computed here can be reused for other images that have the same calibration parameters. ''' Controls = self.getControls() @@ -6384,14 +6531,14 @@ def MaskFrameMask(self): if frame: tam = ma.mask_or(tam,ma.make_mask(np.abs(G2img.polymask(Controls,frame)-255))) return tam - + def getVary(self,*args): - '''Return the refinement flag(s) for calibration of + '''Return the refinement flag(s) for calibration of Image Controls parameter(s) in the current image. If the parameter is not found, an exception is raised. - :param str arg: the name of a refinement parameter in the - varyList for the image. The name should be one of + :param str arg: the name of a refinement parameter in the + varyList for the image. The name should be one of 'dep', 'det-X', 'det-Y', 'dist', 'phi', 'tilt', or 'wave' :param str arg1: the name of a parameter (dict entry) as before, optional @@ -6405,18 +6552,18 @@ def getVary(self,*args): else: raise Exception('arg {} not defined in G2Image.getVary'.format(arg)) return res - + def setVary(self,arg,value): - '''Set a refinement flag for Image Controls parameter in the + '''Set a refinement flag for Image Controls parameter in the current image that is used for fitting calibration parameters. If the parameter is not '*' or found, an exception is raised. - :param str arg: the name of a refinement parameter in the - varyList for the image. The name should be one of + :param str arg: the name of a refinement parameter in the + varyList for the image. The name should be one of 'dep', 'det-X', 'det-Y', 'dist', 'phi', 'tilt', or 'wave', - or it may be a list or tuple of names, + or it may be a list or tuple of names, or it may be '*' in which all parameters are set accordingly. - :param value: the value to set the parameter. The value is + :param value: the value to set the parameter. The value is cast as bool. ''' if arg == '*': @@ -6432,8 +6579,8 @@ def setVary(self,arg,value): raise Exception('arg {} not defined in G2Image.setVary'.format(a)) def Recalibrate(self): - '''Invokes a recalibration fit (same as Image Controls/Calibration/Recalibrate - menu command). Note that for this to work properly, the calibration + '''Invokes a recalibration fit (same as Image Controls/Calibration/Recalibrate + menu command). Note that for this to work properly, the calibration coefficients (center, wavelength, distance & tilts) must be fairly close. This may produce a better result if run more than once. ''' @@ -6447,18 +6594,18 @@ def Recalibrate(self): def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): '''Invokes an image integration (same as Image Controls/Integration/Integrate menu command). All parameters will have previously been set with Image Controls - so no input is needed here. However, the optional parameters MaskMap + so no input is needed here. However, the optional parameters MaskMap and ThetaAzimMap may be supplied to save computing these items more than - once, speeding integration of multiple images with the same + once, speeding integration of multiple images with the same image/mask parameters. - Note that if integration is performed on an + Note that if integration is performed on an image more than once, histogram entries may be overwritten. Use the name - parameter to prevent this if desired. + parameter to prevent this if desired. - :param str name: base name for created histogram(s). If None (default), - the histogram name is taken from the image name. - :param list MaskMap: from :func:`IntMaskMap` + :param str name: base name for created histogram(s). If None (default), + the histogram name is taken from the image name. + :param list MaskMap: from :func:`IntMaskMap` :param list ThetaAzimMap: from :meth:`G2Image.IntThetaAzMap` :returns: a list of created histogram (:class:`G2PwdrData` or :class:`G2SmallAngle`) objects. ''' @@ -6489,13 +6636,13 @@ def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): name = self.name.replace('IMG ',data['type']+' ') if 'PWDR' in name: if 'target' in data: - names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] + names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] codes = [0 for i in range(14)] else: - names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] + names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] codes = [0 for i in range(12)] elif 'SASD' in name: - names = ['Type','Lam','Zero','Azimuth'] + names = ['Type','Lam','Zero','Azimuth'] codes = [0 for i in range(4)] X = 4.*np.pi*npsind(X/2.)/data['wavelength'] #convert to q Xminmax = [X[0],X[-1]] @@ -6507,7 +6654,7 @@ def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): for i,azm in enumerate(azms[:-1]): if azm > 360. and azms[i+1] > 360.: Azms.append(G2img.meanAzm(azm%360.,azms[i+1]%360.)) - else: + else: Azms.append(G2img.meanAzm(azm,azms[i+1])) dazm = np.min(np.abs(np.diff(azms)))/2. # pull out integration results and make histograms for each @@ -6522,7 +6669,7 @@ def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): Sample['Omega'] = data['GonioAngles'][0] Sample['Chi'] = data['GonioAngles'][1] Sample['Phi'] = data['GonioAngles'][2] - Sample['Azimuth'] = (azm+dazm)%360. #put here as bin center + Sample['Azimuth'] = (azm+dazm)%360. #put here as bin center polariz = 0.99 #set default polarization for synchrotron radiation! for item in Comments: if 'polariz' in item: @@ -6590,7 +6737,7 @@ def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): section = 'Reflection Lists' histItems += [section] HistDict[section] = {} - elif 'SASD' in Aname: + elif 'SASD' in Aname: section = 'Substances' histItems += [section] HistDict[section] = G2pwd.SetDefaultSubstances() @@ -6605,7 +6752,7 @@ def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): 'refOffset':-0.1*Ymax,'refDelt':0.1*Ymax,'Yminmax':[Ymin,Ymax]} # if Aname is already in the project replace it for j in self.proj.names: - if j[0] == Aname: + if j[0] == Aname: G2fil.G2Print('Replacing "{}" in project'.format(Aname)) break else: @@ -6623,66 +6770,66 @@ def Integrate(self,name=None,MaskMap=None,ThetaAzimMap=None): return IntgOutList def TestFastPixelMask(self): - '''Tests to see if the fast (C) code for pixel masking is installed. + '''Tests to see if the fast (C) code for pixel masking is installed. - :returns: A value of True is returned if fast pixel masking is - available. Otherwise False is returned. + :returns: A value of True is returned if fast pixel masking is + available. Otherwise False is returned. ''' return G2img.TestFastPixelMask() def GeneratePixelMask(self,esdMul=3.0,ttmin=0.,ttmax=180., FrameMask=None,ThetaMap=None, fastmode=True,combineMasks=False): - '''Generate a Pixel mask with True at the location of pixels that are - statistical outliers (in comparison with others with the same 2theta - value.) The process for this is that a median is computed for pixels - within a small 2theta window and then the median difference is computed + '''Generate a Pixel mask with True at the location of pixels that are + statistical outliers (in comparison with others with the same 2theta + value.) The process for this is that a median is computed for pixels + within a small 2theta window and then the median difference is computed from magnitude of the difference for those pixels from that median. The - medians are used for this rather than a standard deviation as the - computation used here is less sensitive to outliers. - (See :func:`GSASIIimage.AutoPixelMask` and + medians are used for this rather than a standard deviation as the + computation used here is less sensitive to outliers. + (See :func:`GSASIIimage.AutoPixelMask` and :func:`scipy.stats.median_abs_deviation` for more details.) - Mask is placed into the G2image object where it will be - accessed during integration. Note that this increases the .gpx file - size significantly; use :meth:`~G2Image.clearPixelMask` to delete - this if it need not be saved. + Mask is placed into the G2image object where it will be + accessed during integration. Note that this increases the .gpx file + size significantly; use :meth:`~G2Image.clearPixelMask` to delete + this, if it need not be saved. This code is based on :func:`GSASIIimage.FastAutoPixelMask` - but has been modified to recycle expensive computations + but has been modified to recycle expensive computations where possible. - :param float esdMul: Significance threshold applied to remove - outliers. Default is 3. The larger this number, the fewer - "glitches" that will be removed. - :param float ttmin: A lower 2theta limit to be used for pixel + :param float esdMul: Significance threshold applied to remove + outliers. Default is 3. The larger this number, the fewer + "glitches" that will be removed. + :param float ttmin: A lower 2theta limit to be used for pixel searching. Pixels outside this region may be considered for establishing the medians, but only pixels with 2theta >= :attr:`ttmin` are masked. Default is 0. - :param float ttmax: An upper 2theta limit to be used for pixel + :param float ttmax: An upper 2theta limit to be used for pixel searching. Pixels outside this region may be considered for establishing the medians, but only pixels with 2theta < :attr:`ttmax` are masked. Default is 180. - :param np.array FrameMask: An optional precomputed Frame mask - (from :func:`~G2Image.MaskFrameMask`). Compute this once for - a series of similar images to reduce computational time. - :param np.array ThetaMap: An optional precomputed array that - defines 2theta for each pixel, computed in - :func:`~G2Image.MaskThetaMap`. Compute this once for - a series of similar images to reduce computational time. - :param bool fastmode: If True (default) fast Pixel map - searching is done if the C module is available. If the + :param np.array FrameMask: An optional precomputed Frame mask + (from :func:`~G2Image.MaskFrameMask`). Compute this once for + a series of similar images to reduce computational time. + :param np.array ThetaMap: An optional precomputed array that + defines 2theta for each pixel, computed in + :func:`~G2Image.MaskThetaMap`. Compute this once for + a series of similar images to reduce computational time. + :param bool fastmode: If True (default) fast Pixel map + searching is done if the C module is available. If the module is not available or this is False, the pure Python - implementatruion is used. It is not clear why False is - ever needed. + implementatruion is used. It is not clear why False is + ever needed. :param bool combineMasks: When True, the current Pixel mask will be combined with any previous Pixel map. If False (the - default), the Pixel map from the current search will - replace any previous ones. The reason for use of this as - True would be where different :attr:`esdMul` values are - used for different regions of the image (by setting - :attr:`ttmin` & :attr:`ttmax`) so that the outlier level - can be tuned by combining different searches. + default), the Pixel map from the current search will + replace any previous ones. The reason for use of this as + True would be where different :attr:`esdMul` values are + used for different regions of the image (by setting + :attr:`ttmin` & :attr:`ttmax`) so that the outlier level + can be tuned by combining different searches. ''' import math sind = lambda x: math.sin(x*math.pi/180.) @@ -6713,16 +6860,22 @@ def GeneratePixelMask(self,esdMul=3.0,ttmin=0.,ttmax=180., raise Exception numChans = int(1000*(x1-x0)/Controls['pixelSize'][0])//2 if G2img.TestFastPixelMask() and fastmode: - import fmask + if GSASIIpath.binaryPath: + import fmask + else: + from . import fmask G2fil.G2Print(f'Fast mask: Spots greater or less than {esdMul:.1f} of median abs deviation are masked') outMask = np.zeros_like(tam,dtype=bool).ravel() TThs = np.linspace(LUtth[0], LUtth[1], numChans, False) + errmsg = '' try: fmask.mask(esdMul, tam.ravel(), TA.ravel(), Image.ravel(), TThs, outMask, ttmin, ttmax) except Exception as msg: - print('Exception in fmask.mask\n\t',msg) - raise Exception(msg) + errmsg = msg + if errmsg: + print('Exception in fmask.mask\n\t',errmsg) + raise Exception(errmsg) outMask = outMask.reshape(Image.shape) else: # slow search, no sense using cache to save time Masks['SpotMask']['SearchMin'] = ttmin @@ -6734,17 +6887,17 @@ def GeneratePixelMask(self,esdMul=3.0,ttmin=0.,ttmax=180., Masks['SpotMask']['spotMask'] = outMask def clearPixelMask(self): - '''Removes a pixel map from an image, to reduce the .gpx file + '''Removes a pixel map from an image, to reduce the .gpx file size & memory use ''' self.getMasks()['SpotMask']['spotMask'] = None class G2SmallAngle(G2ObjectWrapper): - """Wrapper for SASD histograms (and hopefully, in the future, other + """Wrapper for SASD histograms (and hopefully, in the future, other small angle histogram types). - - Note that in a GSASIIscriptable script, instances of G2SmallAngle will be - created by calls to + + Note that in a GSASIIscriptable script, instances of G2SmallAngle will be + created by calls to :meth:`~G2Project.SAS`, :meth:`~G2Project.SASs`, or by :meth:`G2Project.Integrate`. Also, someday :meth:`G2Project.add_SAS`. @@ -6752,11 +6905,11 @@ class G2SmallAngle(G2ObjectWrapper): This object contains these class variables: * G2SmallAngle.proj: contains a reference to the :class:`G2Project` - object that contains this histogram + object that contains this histogram * G2SmallAngle.name: contains the name of the histogram - * G2SmallAngle.data: contains the histogram's associated data - in a dict with keys 'Comments', 'Limits', 'Instrument Parameters', - 'Substances', 'Sample Parameters' and 'Models'. + * G2SmallAngle.data: contains the histogram's associated data + in a dict with keys 'Comments', 'Limits', 'Instrument Parameters', + 'Substances', 'Sample Parameters' and 'Models'. Further documentation on SASD entries needs to be written. .. seealso:: @@ -6787,7 +6940,7 @@ def create(args): [-i IPARAMS [IPARAMS ...]] [-p PHASES [PHASES ...]] filename - + positional arguments:: filename the project file to create. should end in .gpx @@ -6813,7 +6966,7 @@ def create(args): G2fil.G2Print("Adding histogram from",h,"with instparm ",i) hist_objs.append(proj.add_powder_histogram(h, i)) - if args.phases: + if args.phases: for p in args.phases: G2fil.G2Print("Adding phase from",p) proj.add_phase(p, histograms=hist_objs) @@ -6859,7 +7012,7 @@ def add(args): list of histgram indices to associate with added phases. If not specified, phases are associated with all previously loaded histograms. Example: -l 2 3 4 - + """ proj = G2Project(args.filename) @@ -6868,7 +7021,7 @@ def add(args): G2fil.G2Print("Adding histogram from",h,"with instparm ",i) proj.add_powder_histogram(h, i, fmthint=args.histogramformat) - if args.phases: + if args.phases: if not args.histlist: histlist = proj.histograms() else: @@ -6877,7 +7030,7 @@ def add(args): for p in args.phases: G2fil.G2Print("Adding phase from",p) proj.add_phase(p, histograms=histlist, fmthint=args.phaseformat) - + if not args.histlist: G2fil.G2Print('Linking phase(s) to all histogram(s)') else: @@ -6903,7 +7056,7 @@ def dump(args): -d, --histograms list histograms in files, overrides --raw -p, --phases list phases in files, overrides --raw -r, --raw dump raw file contents, default - + """ if not args.histograms and not args.phases: args.raw = True @@ -6971,7 +7124,7 @@ def refine(args): optional arguments:: -h, --help show this help message and exit - + """ proj = G2Project(args.gpxfile) if args.refinements is None: @@ -7025,7 +7178,7 @@ def _args_kwargs(*args, **kwargs): _args_kwargs('-d', '--histograms', nargs='+', help='list of datafiles to add as histograms'), - + _args_kwargs('-i', '--iparams', nargs='+', help='instrument parameter file, must be one' @@ -7043,13 +7196,13 @@ def _args_kwargs(*args, **kwargs): _args_kwargs('-d', '--histograms', nargs='+', help='list of datafiles to add as histograms'), - + _args_kwargs('-i', '--iparams', nargs='+', help='instrument parameter file, must be one' ' for every histogram' ), - + _args_kwargs('-hf', '--histogramformat', help='format hint for histogram import. Applies to all' ' histograms' @@ -7065,14 +7218,14 @@ def _args_kwargs(*args, **kwargs): help='format hint for phase import. Applies to all' ' phases. Example: -pf CIF' ), - + _args_kwargs('-l', '--histlist', nargs='+', help='list of histgram indices to associate with added' ' phases. If not specified, phases are' ' associated with all previously loaded' ' histograms. Example: -l 2 3 4')]), - + "dump": (dump, [_args_kwargs('-d', '--histograms', action='store_true', help='list histograms in files, overrides --raw'), @@ -7146,8 +7299,8 @@ def main(): print('Use {} -h or --help'.format(sys.argv[0])) def dictDive(d,search="",keylist=[],firstcall=True,l=None): - '''Recursive routine to scan a nested dict. Reports a list of keys - and the associated type and value for that key. + '''Recursive routine to scan a nested dict. Reports a list of keys + and the associated type and value for that key. :param dict d: a dict that will be scanned :param str search: an optional search string. If non-blank, @@ -7160,25 +7313,25 @@ def dictDive(d,search="",keylist=[],firstcall=True,l=None): in form [([keylist], type, value),...] where if keylist is ['a','b','c'] then d[['a']['b']['c'] will have the value. - This routine can be called in a number of ways, as are shown in a few - examples: + This routine can be called in a number of ways, as are shown in a few + examples: >>> for i in G2sc.dictDive(p.data['General'],'paw'): print(i) - ... + ... (['Pawley dmin'], , 1.0) (['doPawley'], , False) (['Pawley dmax'], , 100.0) (['Pawley neg wt'], , 0.0) >>> >>> for i in G2sc.dictDive(p.data,'paw',['General']): print(i) - ... + ... (['General', 'Pawley dmin'], , 1.0) (['General', 'doPawley'], , False) (['General', 'Pawley dmax'], , 100.0) (['General', 'Pawley neg wt'], , 0.0) >>> >>> for i in G2sc.dictDive(p.data,'',['General','doPawley']): print(i) - ... + ... (['General', 'doPawley'], , False) ''' @@ -7199,7 +7352,7 @@ def dictDive(d,search="",keylist=[],firstcall=True,l=None): l.append((keylist,type(d),d)) if firstcall: return l - + if __name__ == '__main__': #fname='/tmp/corundum-template.gpx' #prj = G2Project(fname) diff --git a/GSASII/GSASIIseqGUI.py b/GSASII/GSASIIseqGUI.py index 7e16db6c6..bbe1abb6e 100644 --- a/GSASII/GSASIIseqGUI.py +++ b/GSASII/GSASIIseqGUI.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- #GSASIIseqGUI - Sequential Results Display routines ''' -Routines for Sequential Results & Cluster Analysis dataframes follow. +Routines for Sequential Results & Cluster Analysis dataframes follow. ''' from __future__ import division, print_function import platform @@ -16,29 +16,29 @@ import wx.grid as wg except ImportError: pass -import GSASIIpath -import GSASIImath as G2mth -import GSASIImiscGUI as G2IO -import GSASIIdataGUI as G2gd -import GSASIIstrIO as G2stIO -import GSASIIlattice as G2lat -import GSASIIplot as G2plt -import GSASIIpwdplot as G2pwpl -import GSASIImapvars as G2mv -import GSASIIobj as G2obj -import GSASIIexprGUI as G2exG -import GSASIIctrlGUI as G2G +from . import GSASIIpath +from . import GSASIImath as G2mth +from . import GSASIImiscGUI as G2IO +from . import GSASIIdataGUI as G2gd +from . import GSASIIstrIO as G2stIO +from . import GSASIIlattice as G2lat +from . import GSASIIplot as G2plt +from . import GSASIIpwdplot as G2pwpl +from . import GSASIImapvars as G2mv +from . import GSASIIobj as G2obj +from . import GSASIIexprGUI as G2exG +from . import GSASIIctrlGUI as G2G WACV = wx.ALIGN_CENTER_VERTICAL ##### Display of Sequential Results ########################################## def UpdateSeqResults(G2frame,data,prevSize=None): """ - Called when any data tree entry is selected that has 'Sequential' in the name + Called when any data tree entry is selected that has 'Sequential' in the name to show results from any sequential analysis. - + :param wx.Frame G2frame: main GSAS-II data tree windows - :param dict data: a dictionary containing the following items: + :param dict data: a dictionary containing the following items: * 'histNames' - list of histogram names in order as processed by Sequential Refinement * 'varyList' - list of variables - identical over all refinements in sequence @@ -105,16 +105,16 @@ def GetColumnInfo(col): plotName = variableLabels.get(colName,colName) plotName = plotSpCharFix(plotName) return plotName,G2frame.colList[col],G2frame.colSigs[col] - + def PlotSelectedColRow(calltyp='',event=None): '''Called to plot a selected column or row by clicking on a row or column label. N.B. This is called for LB click after the event is processed so that the column or row has been selected. For RB clicks, the event is processed here - :param str calltyp: ='left'/'right', specifies if this was - a left- or right-click, where a left click on row - plots histogram; Right click on row plots V-C matrix; + :param str calltyp: ='left'/'right', specifies if this was + a left- or right-click, where a left click on row + plots histogram; Right click on row plots V-C matrix; Left or right click on column: plots values in column :param obj event: from wx.EVT_GRID_LABEL_RIGHT_CLICK ''' @@ -145,7 +145,7 @@ def PlotSelectedColRow(calltyp='',event=None): G2frame.PickId = G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name) PFdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PickId,'PDF Controls')) G2plt.PlotISFG(G2frame,PFdata,plotType='G(R)') - G2frame.PickId = pickId + G2frame.PickId = pickId else: return elif rows and calltyp == 'right': @@ -160,7 +160,7 @@ def PlotSelectedColRow(calltyp='',event=None): 'Select row or columns', 'Nothing selected in table. Click on column or row label(s) to plot. N.B. Grid selection can be a bit funky.' ) - + def SetLabelString(col): '''Define or edit the label for a column in the table, to be used as a tooltip and for plotting @@ -183,11 +183,11 @@ def PlotLeftSelect(event): 'Called by a left MB click on a row or column label. ' event.Skip() wx.CallAfter(PlotSelectedColRow,'left') - + def PlotRightSelect(event): 'Called by a right MB click on a row or column label' PlotSelectedColRow('right',event) - + def OnPlotSelSeq(event): 'plot the selected columns or row from menu command' cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order @@ -200,7 +200,7 @@ def OnPlotSelSeq(event): else: G2frame.ErrorDialog('Select columns', 'No columns or rows selected in table. Click on row or column labels to select fields for plotting.') - + def OnAveSelSeq(event): 'average the selected columns from menu command' cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order @@ -216,7 +216,7 @@ def OnAveSelSeq(event): else: G2frame.ErrorDialog('Select columns', 'No columns selected in table. Click on column labels to select fields for averaging.') - + def OnSelectUse(event): dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select rows to use','Select rows',histNames) sellist = [i for i,item in enumerate(G2frame.colList[1]) if item] @@ -231,7 +231,7 @@ def OnSelectUse(event): G2frame.colList[1][row] = True G2frame.dataDisplay.ForceRefresh() dlg.Destroy() - + def OnRenameSelSeq(event): cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order colNames = [G2frame.SeqTable.GetColLabelValue(c) for c in cols] @@ -245,21 +245,21 @@ def OnRenameSelSeq(event): return dlg = G2G.MultiStringDialog(G2frame.dataDisplay,'Set column names',colNames,newNames) if dlg.Show(): - newNames = dlg.GetValues() + newNames = dlg.GetValues() variableLabels.update(dict(zip(colNames,newNames))) - data['variableLabels'] = variableLabels + data['variableLabels'] = variableLabels dlg.Destroy() UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis) - + def OnSaveSelSeqCSV(event): 'export the selected columns to a .csv file from menu command' OnSaveSelSeq(event,csv=True) - + def OnSaveSeqCSV(event): 'export all columns to a .csv file from menu command' OnSaveSelSeq(event,csv=True,allcols=True) - + def OnSaveSelSeq(event,csv=False,allcols=False): 'export the selected columns to a .txt or .csv file from menu command' def WriteLine(line): @@ -267,7 +267,7 @@ def WriteLine(line): SeqFile.write(G2obj.StripUnicode(line)) else: SeqFile.write(line) - + def WriteCSV(): def WriteList(headerItems): line = '' @@ -297,7 +297,7 @@ def WriteList(headerItems): else: if col in havesig: line += ','+str(saveData[col][row])+','+str(saveSigs[col][row]) - else: + else: line += ','+str(saveData[col][row]) WriteLine(line+'\n') def WriteSeq(): @@ -362,12 +362,12 @@ def WriteSeq(): pth = G2G.GetExportPath(G2frame) dlg = wx.FileDialog( G2frame, - 'Choose text output file for your selection', pth, '', + 'Choose text output file for your selection', pth, '', wild,wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) try: if dlg.ShowModal() == wx.ID_OK: SeqTextFile = dlg.GetPath() - SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) + SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) SeqFile = open(SeqTextFile,'w') if csv: WriteCSV() @@ -376,7 +376,7 @@ def WriteSeq(): SeqFile.close() finally: dlg.Destroy() - + def striphist(var,insChar=''): 'strip a histogram number from a var name' sv = var.split(':') @@ -384,7 +384,7 @@ def striphist(var,insChar=''): if sv[1]: sv[1] = insChar return ':'.join(sv) - + def plotSpCharFix(lbl): 'Change selected unicode characters to their matplotlib equivalent' for u,p in [ @@ -395,7 +395,7 @@ def plotSpCharFix(lbl): ]: lbl = lbl.replace(u,p) return lbl - + def SelectXaxis(): 'returns a selected column number (or None) as the X-axis selection' ncols = G2frame.SeqTable.GetNumberCols() @@ -413,7 +413,7 @@ def SelectXaxis(): finally: dlg.Destroy() return col - + def EnablePseudoVarMenus(): 'Enables or disables the PseudoVar menu items that require existing defs' if data['SeqPseudoVars']: @@ -461,7 +461,7 @@ def EditPseudoVar(event): del data['SeqPseudoVars'][choices[selected]] data['SeqPseudoVars'][calcobj.eObj.expression] = newobj UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables - + def AddNewPseudoVar(event): 'Create a new pseudo var expression' dlg = G2exG.ExpressionDialog(G2frame.dataDisplay,PSvarDict, @@ -473,7 +473,7 @@ def AddNewPseudoVar(event): calcobj = G2obj.ExpressionCalcObj(obj) data['SeqPseudoVars'][calcobj.eObj.expression] = obj UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables - + def AddNewDistPseudoVar(event): obj = None dlg = G2exG.BondDialog( @@ -506,7 +506,7 @@ def AddNewDistPseudoVar(event): obj.expression = 'Dist(%s,\n%s)'%(Oatom,Tatom.split(' d=')[0].replace(' ','')) obj.distance_dict = {'pId':pId,'SGData':SGData,'symNo':symNo,'cellNo':cellNo} obj.distance_atoms = [oId,tId] - else: + else: dlg.Destroy() return dlg.Destroy() @@ -524,14 +524,14 @@ def AddNewAnglePseudoVar(event): pName,Oatom,Tatoms = dlg.GetSelection() if Tatoms: obj = G2obj.makeAngleObj(Phases[pName],Oatom,Tatoms) - else: + else: dlg.Destroy() return dlg.Destroy() if obj: data['SeqPseudoVars'][obj.expression] = obj UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables - + def UpdateParmDict(parmDict): '''generate the atom positions and the direct & reciprocal cell values, because they might be needed to evaluate the pseudovar @@ -542,7 +542,7 @@ def UpdateParmDict(parmDict): ) delList = [] phaselist = [] - for item in parmDict: + for item in parmDict: if ':' not in item: continue key = item.split(':') if len(key) < 3: continue @@ -556,7 +556,7 @@ def UpdateParmDict(parmDict): parmDict[akey] += parmDict[item] delList.append(item) for item in delList: - del parmDict[item] + del parmDict[item] for i in phaselist: pId = int(i) # apply cell symmetry @@ -583,7 +583,7 @@ def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD): GSAS-II variable name. Note this likely could be faster if the loop over calcobjs were done - inside after the Dict was created. + inside after the Dict was created. ''' if not ESD: return 0. @@ -595,7 +595,7 @@ def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD): phaselist = [] VparmDict = sampleDict.copy() for incr in step,-step: - VparmDict.update(parmDict.copy()) + VparmDict.update(parmDict.copy()) # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]' # values are not zeroed: fix that! VparmDict.update({item:0.0 for item in parmDict if 'dA' in item}) @@ -605,7 +605,7 @@ def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD): # needed to evaluate the pseudovar for item in VparmDict: if item in sampleDict: - continue + continue if ':' not in item: continue key = item.split(':') if len(key) < 3: continue @@ -638,7 +638,7 @@ def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD): if None in results: return None return (results[0] - results[1]) / (2.*step) - + def EnableParFitEqMenus(): 'Enables or disables the Parametric Fit menu items that require existing defs' if data['SeqParFitEqList']: @@ -692,7 +692,7 @@ def DoParEqFit(event,eqObj=None): calcobj = G2obj.ExpressionCalcObj(obj) # prepare a dict of needed independent vars for this expression indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars} - calcobj.SetupCalc(indepVarDict) + calcobj.SetupCalc(indepVarDict) # values and sigs for current value of dependent var if row[indx] is None: continue calcobj.depVal = row[indx] @@ -743,7 +743,7 @@ def DoParEqFit(event,eqObj=None): # lookup dependent var position indx = colLabels.index(obj.GetDepVar()) # assemble a list of the independent variables - indepVars = obj.GetIndependentVars() + indepVars = obj.GetIndependentVars() # loop over each datapoint fitvals = [] for j,row in enumerate(zip(*G2frame.colList)): @@ -766,7 +766,7 @@ def DelParFitEq(event): data['SeqParFitEqList'] = [obj for i,obj in enumerate(data['SeqParFitEqList']) if i not in selected] EnableParFitEqMenus() if data['SeqParFitEqList']: DoParEqFit(event) - + def EditParFitEq(event): 'Edit an existing parametric equation' txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in data['SeqParFitEqList']] @@ -807,7 +807,7 @@ def AddNewParFitEq(event): data['SeqParFitEqList'].append(obj) EnableParFitEqMenus() if data['SeqParFitEqList']: DoParEqFit(event) - + def CopyParFitEq(event): 'Copy an existing parametric equation to be fit to sequential results' # compile the variable names used in previous freevars to avoid accidental name collisions @@ -837,7 +837,7 @@ def CopyParFitEq(event): data['SeqParFitEqList'].append(newobj) EnableParFitEqMenus() if data['SeqParFitEqList']: DoParEqFit(event) - + def GridSetToolTip(row,col): '''Routine to show standard uncertainties for each element in table as a tooltip @@ -846,7 +846,7 @@ def GridSetToolTip(row,col): if G2frame.colSigs[col][row] == -0.1: return 'frozen' return u'\u03c3 = '+str(G2frame.colSigs[col][row]) return '' - + def GridColLblToolTip(col): '''Define a tooltip for a column. This will be the user-entered value (from data['variableLabels']) or the default name @@ -856,7 +856,7 @@ def GridColLblToolTip(col): return var = colLabels[col] return variableLabels.get(var,G2obj.fmtVarDescr(var)) - + def DoSequentialExport(event): '''Event handler for all Sequential Export menu items ''' @@ -882,7 +882,7 @@ def onSelectSeqVars(event): UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables else: dlg.Destroy() - + def OnCellChange(event): c = event.GetCol() if c != 1: return @@ -890,9 +890,9 @@ def OnCellChange(event): val = G2frame.SeqTable.GetValue(r,c) data['Use'][r] = val G2frame.SeqTable.SetValue(r,c, val) - + def OnSelectUpdate(event): - '''Update all phase parameters from a selected column in the Sequential Table. + '''Update all phase parameters from a selected column in the Sequential Table. If no histogram is selected (or more than one), ask the user to make a selection. Loosely based on :func:`GSASIIstrIO.SetPhaseData` @@ -925,7 +925,7 @@ def OnSelectUpdate(event): A,sigA = G2stIO.cellFill(pfx,SGData,parmDict,{}) cell[1:7] = G2lat.A2cell(A) cell[7] = G2lat.calc_V(A) - textureData = General['SH Texture'] + textureData = General['SH Texture'] if textureData['Order']: for name in ['omega','chi','phi']: aname = pfx+'SH '+name @@ -994,8 +994,8 @@ def OnSelectUpdate(event): 'MXcos:'+stiw,'MYcos:'+stiw,'MZcos:'+stiw] for iname,name in enumerate(names): AtomSS[Stype][iw][0][iname] = parmDict[pfx+name] - - def OnEditSelectPhaseVars(event): + + def OnEditSelectPhaseVars(event): '''Select phase parameters in a selected histogram in a sequential fit. This allows the user to set their value(s) ''' @@ -1019,7 +1019,7 @@ def OnEditSelectPhaseVars(event): lbl = "\nin {} ".format(histNames[selRows[0]]) else: lbl = "\nin {} histograms".format(len(selRows)) - dlg = G2G.G2MultiChoiceDialog(G2frame, 'Choose phase parmDict item(s) to set'+lbl, + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Choose phase parmDict item(s) to set'+lbl, 'Choose items to edit', phaseKeys) if dlg.ShowModal() == wx.ID_OK: select = dlg.GetSelections() @@ -1045,8 +1045,8 @@ def OnEditSelectPhaseVars(event): parmDict.update(d) # update values used in next fit wx.CallAfter(UpdateSeqResults,G2frame,data) # redisplay variables return - -#---- UpdateSeqResults: start processing sequential results here ########## + +#---- UpdateSeqResults: start processing sequential results here ########## # lookup table for unique cell parameters by symmetry if not data: print ('No sequential refinement results') @@ -1059,7 +1059,7 @@ def OnEditSelectPhaseVars(event): histNames = [name for name in data['histNames']] Controls = {} ifPWDR = False - else: + else: Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Controls')) # create a place to store Pseudo Vars & Parametric Fit functions, if not present if 'SeqPseudoVars' not in data: data['SeqPseudoVars'] = {} @@ -1131,17 +1131,17 @@ def OnEditSelectPhaseVars(event): combinedVaryList = [] # list of all varied parameters ; used for column headings atomsVaryList = {} # dict of atom coords varied in any histogram, includes dependent params # key is atom param name, value is esd parm name - firstValueDict = {} # first value for each parameter; used to create VarDict for parametric fit pseudo vars GUI + firstValueDict = {} # first value for each parameter; used to create VarDict for parametric fit pseudo vars GUI foundHistNames = [] # histograms to be used in sequential table maxPWL = 5 # number of Pawley vars to show missing = 0 newCellDict = {} - PSvarDict = {} # contains 1st value for each parameter in parmDict + PSvarDict = {} # contains 1st value for each parameter in parmDict # needed for creating & editing pseudovars PSvarDict.update(sampleParms) - # scan through histograms to see what are available and to make a - # list of all varied parameters; also create a dict that has the + # scan through histograms to see what are available and to make a + # list of all varied parameters; also create a dict that has the for i,name in enumerate(histNames): if name not in data: if missing < 5: @@ -1160,7 +1160,7 @@ def OnEditSelectPhaseVars(event): # add variables to list as they appear combinedVaryList.append(svar) firstValueDict[svar] = (val,sig) - if '::dA' in var: + if '::dA' in var: atomsVaryList[var.replace('::dA','::A')] = var atomsVaryList.update({i.replace('::dA','::A'):i for i in data[name]['depParmDict'] if '::dA' in i}) # get all refined cell terms @@ -1170,7 +1170,7 @@ def OnEditSelectPhaseVars(event): tmp = {striphist(var,'*'):tmp[var] for var in tmp} # replace histogram #s with "*" tmp.update(PSvarDict) PSvarDict = tmp - + if missing: print (' Warning: Total of %d data sets missing from sequential results'%(missing)) @@ -1191,7 +1191,7 @@ def OnEditSelectPhaseVars(event): histNames = foundHistNames # prevVaryList = [] posdict = {} # defines position for each entry in table; for inner - # dict key is column number & value is parameter name + # dict key is column number & value is parameter name histNumList = [] for i,name in enumerate(histNames): if name in Histograms: @@ -1251,8 +1251,8 @@ def OnEditSelectPhaseVars(event): Types += [wg.GRID_VALUE_FLOAT,] sampleDict = {} for i,name in enumerate(histNames): - sampleDict[name] = dict(zip(sampleParms.keys(),[sampleParms[key][i] for key in sampleParms.keys()])) - # add unique cell parameters + sampleDict[name] = dict(zip(sampleParms.keys(),[sampleParms[key][i] for key in sampleParms.keys()])) + # add unique cell parameters if Controls.get('ShowCell',False) and len(newCellDict): phaseLookup = {Phases[phase]['pId']:phase for phase in Phases} for pId in sorted(RecpCellTerms): @@ -1288,7 +1288,7 @@ def OnEditSelectPhaseVars(event): # override with fit result if is Dij varied if var in cellAlist: try: - A[i] = data[name]['newCellDict'][esdLookUp[var]][1] # get refined value + A[i] = data[name]['newCellDict'][esdLookUp[var]][1] # get refined value except KeyError: pass # apply symmetry @@ -1297,7 +1297,7 @@ def OnEditSelectPhaseVars(event): A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId]) c = G2lat.A2cell(A) vol = G2lat.calc_V(A) - cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData) + cE = G2lat.getCellEsd(pfx,SGdata[pId],A,covData) except: c = 6*[None] cE = 6*[None] @@ -1373,7 +1373,7 @@ def OnEditSelectPhaseVars(event): Types += [wg.GRID_VALUE_FLOAT+':10,5',] G2frame.colSigs += [sigs] G2frame.colList += [vals] - + # tabulate dependent parameters from constraints, removing histogram numbers from # parm label, if needed. Add the dependent variables to the table depValDict = {} @@ -1508,7 +1508,7 @@ def OnEditSelectPhaseVars(event): for i,l in enumerate(colLabels): if l in variableLabels: displayLabels[i] = variableLabels[l] - + G2frame.dataWindow.ClearData() G2frame.dataWindow.currentGrids = [] G2frame.dataDisplay = G2G.GSGrid(parent=G2frame.dataWindow) @@ -1547,7 +1547,7 @@ def OnEditSelectPhaseVars(event): G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0])) #pretty arbitrary 8 G2frame.dataDisplay.SetMargins(0,0) G2frame.dataDisplay.AutoSizeColumns(False) - # highlight unconverged shifts + # highlight unconverged shifts if histNames[0][:4] not in ['SASD','IMG ','REFD',] and deltaChiCol is not None: for row,name in enumerate(histNames): if name not in Controls.get('Seq Data',{}): @@ -1564,7 +1564,7 @@ def OnEditSelectPhaseVars(event): #G2frame.dataDisplay.SendSizeEvent() # resize needed on mac #G2frame.dataDisplay.Refresh() # shows colored text on mac G2frame.dataWindow.SetDataSize() - + ############################################################################################################### #UpdateClusterAnalysis: results ############################################################################################################### @@ -1582,18 +1582,11 @@ def UpdateClusterAnalysis(G2frame,ClusData,shoNum=-1): ClusData['SKLearn'] = True except: ClusData['SKLearn'] = False - - SKLearnCite = '''If you use Scikit-Learn Cluster Analysis, please cite: - 'Scikit-learn: Machine Learning in Python', Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., - Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J., - Passos, A., Cournapeau, D., Brucher, M., Perrot, M. and Duchesnay, E., - Journal of Machine Learning Research (2011) 12, 2825-2830. -''' def FileSizer(): - + def OnSelectData(event): - + def GetCaLimits(names): ''' scan through data selected for cluster analysis to find highest lower & lowest upper limits param: data dict: Cluster analysis info @@ -1607,8 +1600,8 @@ def GetCaLimits(names): PDFControls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls')) x = PDFControls['G(R)'][1][0] limits = [max(np.min(x),limits[0]),min(np.max(x),limits[1])] - return limits - + return limits + choices = G2gd.GetGPXtreeDataNames(G2frame,['PWDR','PDF ']) if len(choices) == 0: G2G.G2MessageBox(G2frame,'No PWDR or PDF histograms found for cluster analysis.','No Histograms') @@ -1633,7 +1626,7 @@ def GetCaLimits(names): G2G.G2MessageBox(G2frame,'Histogram types not all the same; revise selection','Histogram type mismatch') return names.append(choices[sel]) - ClusData['Files'] = names + ClusData['Files'] = names limits = GetCaLimits(names) ClusData['Limits'] = [copy.copy(limits),limits] ClusData['DataMatrix'] = [] @@ -1641,12 +1634,12 @@ def GetCaLimits(names): ClusData['CLuZ'] = None ClusData['codes'] = None ClusData['plots'] = 'All' - + dlg.Destroy() G2frame.SetTitleByGPX() - + wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + fileSizer = wx.BoxSizer(wx.HORIZONTAL) Type = 'PWDR' if len(ClusData['Files']): @@ -1657,13 +1650,13 @@ def GetCaLimits(names): else: lbl = 'No data selected for Cluster Analysis' fileSizer.Add(wx.StaticText(G2frame.dataWindow,label=lbl),0,WACV) - selSeqData = wx.Button(G2frame.dataWindow,label=' Select datasets') + selSeqData = wx.Button(G2frame.dataWindow,label=' Select datasets') selSeqData.Bind(wx.EVT_BUTTON,OnSelectData) fileSizer.Add(selSeqData,0,WACV) return fileSizer - + def LimitSizer(): - + def CheckLimits(invalid,value,tc): #TODO this needs a check on ultimate size of data array; loop over names & count points? @@ -1672,9 +1665,9 @@ def CheckLimits(invalid,value,tc): ClusData['DataMatrix'] = [] ClusData['ConDistMat'] = [] ClusData['CLuZ'] = None - + wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + limitSizer = wx.BoxSizer(wx.HORIZONTAL) limitSizer.Add(wx.StaticText(G2frame.dataWindow,label='Enter cluster analysis data limits: '),0,WACV) limitSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,ClusData['Limits'][1],0,nDig=(10,3), @@ -1682,7 +1675,7 @@ def CheckLimits(invalid,value,tc): limitSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,ClusData['Limits'][1],1,nDig=(10,3), xmin=ClusData['Limits'][0][0],xmax=ClusData['Limits'][0][1],OnLeave=CheckLimits),0,WACV) return limitSizer - + def GetYMatSize(): nData = 0 for name in ClusData['Files']: @@ -1729,30 +1722,30 @@ def OnMakeArray(event): ClusData['ConDistMat'] = [] ClusData['CLuZ'] = None wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + def MethodSizer(): - + def OnClusterMethod(event): ClusData['Method'] = method.GetValue() ClusData['ConDistMat'] = [] ClusData['CLuZ'] = None OnCompute(event) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + def OnCompute(event): if 'minkowski' in ClusData['Method']: ClusData['ConDistMat'] = SSD.pdist(ClusData['DataMatrix'],ClusData['Method'],p=int(ClusData['MinkP'])) else: ClusData['ConDistMat'] = SSD.pdist(ClusData['DataMatrix'],ClusData['Method']) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + def OnExponent(event): ClusData['MinkP'] = minp.GetValue() ClusData['ConDistMat'] = [] ClusData['CLuZ'] = None OnCompute(event) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + choice = ['braycurtis', 'canberra', 'chebyshev', 'cityblock', 'correlation', 'cosine', \ 'euclidean', 'jensenshannon', 'minkowski', 'seuclidean', 'sqeuclidean'] methsizer = wx.BoxSizer(wx.HORIZONTAL) @@ -1772,21 +1765,21 @@ def OnExponent(event): compute.Bind(wx.EVT_BUTTON,OnCompute) methsizer.Add(compute,0,WACV) return methsizer - + def HierSizer(): - + def OnLinkMethod(event): ClusData['LinkMethod'] = method.GetValue() OnCompute(event) - + def OnOptOrd(event): ClusData['Opt Order'] = not ClusData['Opt Order'] OnCompute(event) - + def OnCompute(event): ClusData['CLuZ'] = SCH.linkage(ClusData['ConDistMat'],method=ClusData['LinkMethod'],optimal_ordering=ClusData['Opt Order']) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + hierSizer = wx.BoxSizer(wx.HORIZONTAL) hierSizer.Add(wx.StaticText(G2frame.dataWindow,label='Hierarchical clustering: Select linkage method: '),0,WACV) choice = ['single','complete','average','weighted','centroid','median','ward',] @@ -1802,19 +1795,19 @@ def OnCompute(event): compute.Bind(wx.EVT_BUTTON,OnCompute) hierSizer.Add(compute,0,WACV) return hierSizer - + def kmeanSizer(): - + def OnClusNum(event): ClusData['NumClust'] = int(numclust.GetValue()) OnCompute(event) - + def OnCompute(event): whitMat = SCV.whiten(ClusData['DataMatrix']) codebook,dist = SCV.kmeans2(whitMat,ClusData['NumClust']) #use K-means++ ClusData['codes'],ClusData['dists'] = SCV.vq(whitMat,codebook) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + kmeanssizer = wx.BoxSizer(wx.HORIZONTAL) choice = [str(i) for i in range(2,16)] kmeanssizer.Add(wx.StaticText(G2frame.dataWindow,label='K-means clustering: select number of clusters (2-15): '),0,WACV) @@ -1826,24 +1819,24 @@ def OnCompute(event): compute.Bind(wx.EVT_BUTTON,OnCompute) kmeanssizer.Add(compute) return kmeanssizer - + def OnPlotSel(event): ClusData['plots'] = plotsel.GetValue() if ClusData['plots'] == 'Suprise': G2plt.PlotClusterXYZ(G2frame,None,None,ClusData,PlotName='Suprise') else: G2plt.PlotClusterXYZ(G2frame,YM,XYZ,ClusData,PlotName=ClusData['Method'],Title=ClusData['Method']) - + def ScikitSizer(): - + def OnClusMethod(event): ClusData['Scikit'] = clusMethod.GetValue() OnCompute(event) - + def OnClusNum(event): ClusData['NumClust'] = int(numclust.GetValue()) OnCompute(event) - + def OnCompute(event): whitMat = SCV.whiten(ClusData['DataMatrix']) if ClusData['Scikit'] == 'K-Means': @@ -1860,11 +1853,11 @@ def OnCompute(event): elif ClusData['Scikit'] == 'Agglomerative clustering': result = SKC.AgglomerativeClustering(n_clusters=ClusData['NumClust'], affinity='precomputed',linkage='average').fit(SSD.squareform(ClusData['ConDistMat'])) - + ClusData['codes'] = result.labels_ ClusData['Metrics'] = Metrics(whitMat,result) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData) - + def Metrics(whitMat,result): if np.max(result.labels_) >= 1: Scoeff = SKM.silhouette_score(whitMat,result.labels_,metric='euclidean') @@ -1877,9 +1870,13 @@ def Metrics(whitMat,result): else: print('number of clusters found must be > 1 for metrics to be determined') return None - + scikitSizer = wx.BoxSizer(wx.VERTICAL) - scikitSizer.Add(wx.StaticText(G2frame.dataWindow,label=SKLearnCite)) + txt = wx.StaticText(G2frame.dataWindow,label= + 'If you use Scikit-Learn Cluster Analysis, please cite: '+ + G2G.GetCite('Scikit-Learn')) + txt.Wrap(500) + scikitSizer.Add(txt) choice = ['K-Means','Affinity propagation','Mean-shift','Spectral clustering','Agglomerative clustering'] clusSizer = wx.BoxSizer(wx.HORIZONTAL) clusSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select clustering method: '),0,WACV) @@ -1907,13 +1904,13 @@ def Metrics(whitMat,result): scikitSizer.Add(wx.StaticText(G2frame.dataWindow, label='Metrics: Silhoutte: %.3f, Variance: %.3f, Davies-Bouldin: %.3f'%(metrics[0],metrics[1],metrics[2]))) return scikitSizer - + def memberSizer(): - + def OnClusNum(event): shoNum = int(numclust.GetValue()) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData,shoNum) - + def OnSelection(event): name = cluslist.GetStringSelection() item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name) @@ -1923,15 +1920,15 @@ def OnSelection(event): else: #PDF data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls')) G2plt.PlotISFG(G2frame,data,plotType='G(R)') - - #need 15 colors; values adjusted to match xkcs/PCA plot colors. NB: RGB reverse order from xkcd values. - Colors = [['xkcd:blue',0xff0000],['xkcd:red',0x0000ff],['xkcd:green',0x00a000],['xkcd:cyan',0xd0d000], + + #need 15 colors; values adjusted to match xkcs/PCA plot colors. NB: RGB reverse order from xkcd values. + Colors = [['xkcd:blue',0xff0000],['xkcd:red',0x0000ff],['xkcd:green',0x00a000],['xkcd:cyan',0xd0d000], ['xkcd:magenta',0xa000a0],['xkcd:black',0x000000],['xkcd:pink',0xb469ff],['xkcd:brown',0x13458b], ['xkcd:teal',0x808000],['xkcd:orange',0x008cff],['xkcd:grey',0x808080],['xkcd:violet',0xe22b8a], - ['xkcd:aqua',0xaaaa00],['xkcd:blueberry',0xcd5a6a],['xkcd:bordeaux',0x00008b]] + ['xkcd:aqua',0xaaaa00],['xkcd:blueberry',0xcd5a6a],['xkcd:bordeaux',0x00008b]] NClust = np.max(ClusData['codes']) memSizer = wx.BoxSizer(wx.VERTICAL) - memSizer.Add(wx.StaticText(G2frame.dataWindow,label='Cluster populations (colors refer to cluster colors in PCA plot):')) + memSizer.Add(wx.StaticText(G2frame.dataWindow,label='Cluster populations (colors refer to cluster colors in PCA plot):')) for i in range(NClust+1): nPop= len(ClusData['codes'])-np.count_nonzero(ClusData['codes']-i) txt = wx.StaticText(G2frame.dataWindow,label='Cluster #%d has %d members'%(i,nPop)) @@ -1940,20 +1937,20 @@ def OnSelection(event): txt.SetBackgroundColour(wx.Colour(50,50,50)) else: txt.SetBackgroundColour(wx.Colour(200,200,200)) - memSizer.Add(txt) + memSizer.Add(txt) headSizer = wx.BoxSizer(wx.HORIZONTAL) - headSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select cluster to list members: '),0,WACV) + headSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select cluster to list members: '),0,WACV) choice = [str(i) for i in range(NClust+1)] numclust = wx.ComboBox(G2frame.dataWindow,choices=choice,value=str(shoNum),style=wx.CB_READONLY|wx.CB_DROPDOWN) numclust.Bind(wx.EVT_COMBOBOX,OnClusNum) headSizer.Add(numclust,0,WACV) - memSizer.Add(headSizer) + memSizer.Add(headSizer) if shoNum >= 0: memSizer.Add(wx.StaticText(G2frame.dataWindow,label='Members of cluster %d (select to show data plot):'%shoNum)) ClusList = [] for i,item in enumerate(ClusData['Files']): if ClusData['codes'][i] == shoNum: - ClusList.append(item) + ClusList.append(item) cluslist = wx.ListBox(G2frame.dataWindow, choices=ClusList) cluslist.SetForegroundColour(wx.Colour(Colors[shoNum][1])) if wx.Colour(Colors[shoNum][1]).GetLuminance() > 0.5: @@ -1963,13 +1960,13 @@ def OnSelection(event): cluslist.Bind(wx.EVT_LISTBOX,OnSelection) memSizer.Add(cluslist) return memSizer - + def outlierSizer(): - + def OnOutSel(event): ClusData['OutMethod'] = outsel.GetValue() OnCompute(event) - + def OnCompute(event): if ClusData['OutMethod'] == 'One-Class SVM': ClusData['codes'] = SKVM.OneClassSVM().fit_predict(ClusData['DataMatrix']) #codes = 1 or -1 @@ -1978,7 +1975,7 @@ def OnCompute(event): elif ClusData['OutMethod'] == 'Local Outlier Factor': ClusData['codes'] = SKN.LocalOutlierFactor().fit_predict(ClusData['DataMatrix']) wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData,shoNum) - + outSizer = wx.BoxSizer(wx.VERTICAL) outSizer.Add(wx.StaticText(G2frame.dataWindow,label='Outlier (bad data) analysis with Scikit-learn:')) choice = ['One-Class SVM','Isolation Forest','Local Outlier Factor'] @@ -1993,7 +1990,7 @@ def OnCompute(event): outline.Add(compute,0,WACV) outSizer.Add(outline) return outSizer - + def OnSelection(event): name = outlist.GetStringSelection() item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name) @@ -2012,7 +2009,7 @@ def OnSelection(event): ClusData['OutMethod'] = ClusData.get('OutMethod','Isolation Forest') ClusData['MinkP'] = ClusData.get('MinkP','2') #end patch - + G2frame.dataWindow.ClearData() topSizer = G2frame.dataWindow.topBox topSizer.Clear(True) @@ -2024,7 +2021,7 @@ def OnSelection(event): bigSizer = wx.BoxSizer(wx.HORIZONTAL) mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add((5,5),0) - + mainSizer.Add(FileSizer()) if len(ClusData['Files']): mainSizer.Add(LimitSizer()) @@ -2034,7 +2031,7 @@ def OnSelection(event): makeArray = wx.Button(G2frame.dataWindow,label='Make Cluster Analysis data array') makeArray.Bind(wx.EVT_BUTTON,OnMakeArray) mainSizer.Add(makeArray) - if len(ClusData['DataMatrix']): + if len(ClusData['DataMatrix']): G2G.HorizontalLine(mainSizer,G2frame.dataWindow) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='Distance Cluster Analysis:')) @@ -2051,7 +2048,7 @@ def OnSelection(event): G2G.HorizontalLine(mainSizer,G2frame.dataWindow) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='Hierarchical Cluster Analysis:')) mainSizer.Add(HierSizer()) - + G2G.HorizontalLine(mainSizer,G2frame.dataWindow) mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='K-means Cluster Analysis:')) mainSizer.Add(kmeanSizer()) @@ -2074,16 +2071,16 @@ def OnSelection(event): plotsel.Bind(wx.EVT_COMBOBOX,OnPlotSel) plotSizer.Add(plotsel,0,WACV) mainSizer.Add(plotSizer) - + if ClusData['SKLearn'] and len(ClusData['ConDistMat']): G2G.HorizontalLine(mainSizer,G2frame.dataWindow) subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) - subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Scikit-Learn Cluster Analysis: '),0,WACV) + subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Scikit-Learn Cluster Analysis: '),0,WACV) subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer,0,wx.EXPAND) mainSizer.Add(ScikitSizer()) - + if ClusData['SKLearn'] and len(ClusData['DataMatrix']) > 15: G2G.HorizontalLine(mainSizer,G2frame.dataWindow) mainSizer.Add(outlierSizer()) @@ -2096,18 +2093,16 @@ def OnSelection(event): OutList = [] for i,item in enumerate(ClusData['Files']): if ClusData['codes'][i] < 0: - OutList.append(item) + OutList.append(item) outlist = wx.ListBox(G2frame.dataWindow, choices=OutList) outlist.Bind(wx.EVT_LISTBOX,OnSelection) - mainSizer.Add(outlist) + mainSizer.Add(outlist) else: mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='No outlier data found')) - + bigSizer.Add(mainSizer) bigSizer.Layout() bigSizer.FitInside(G2frame.dataWindow) G2frame.dataWindow.SetSizer(bigSizer) G2frame.dataWindow.SetDataSize() G2frame.SendSizeEvent() - - diff --git a/GSASII/GSASIIspc.py b/GSASII/GSASIIspc.py index 1bf1e4374..60dbefe84 100644 --- a/GSASII/GSASIIspc.py +++ b/GSASII/GSASIIspc.py @@ -9,13 +9,23 @@ import copy import os.path as ospath -import GSASIIpath +from . import GSASIIpath +GSASIIpath.SetBinaryPath() +from . import GSASIIlattice as G2lat + +try: + if GSASIIpath.binaryPath: + import pyspg + else: + from . import pyspg +except ImportError: + print('binary load error: pyspg not found') npsind = lambda x: np.sin(x*np.pi/180.) npcosd = lambda x: np.cos(x*np.pi/180.) nxs = np.newaxis DEBUG = False - + ################################################################################ #### Space group codes ################################################################################ @@ -26,10 +36,10 @@ def SpcGroup(SGSymbol): :param SGSymbol: space group symbol (string) with spaces between axial fields :returns: (SGError,SGData) - + * SGError = 0 for no errors; >0 for errors (see :func:`SGErrors` for details) * SGData - is a dict (see :ref:`Space Group object`) with entries: - + * 'SpGrp': space group symbol, slightly cleaned up * 'SGFixed': True if space group data can not be changed, e.g. from magnetic cif; otherwise False * 'SGGray': True if 1' in symbol - gray group for mag. incommensurate phases @@ -46,7 +56,7 @@ def SpcGroup(SGSymbol): 'xyz', '111' for arbitrary axes * 'SGPtGrp': one of 32 point group symbols (with some permutations), which is filled by SGPtGroup, is external (KE) part of supersymmetry point group - * 'SSGKl': default internal (Kl) part of supersymmetry point group; modified + * 'SSGKl': default internal (Kl) part of supersymmetry point group; modified in supersymmetry stuff depending on chosen modulation vector for Mono & Ortho * 'BNSlattsym': BNS lattice symbol & cenering op - used for magnetic structures @@ -69,7 +79,6 @@ def SpcGroup(SGSymbol): SGSymbol = SGSymbol.replace('-2','m') if SGSymbol.split()[1] =='3/m': SGSymbol = SGSymbol.replace('3/m','-6') - import pyspg SGInfo = pyspg.sgforpy(SGSymbol) SGData['SpGrp'] = SGSymbol.strip().lower().capitalize() SGData['SGLaue'] = LaueSym[SGInfo[0]-1] @@ -161,7 +170,7 @@ def SpcGroup(SGSymbol): if not SGData['SGInv']: gen1 = 31 else: - gen1 ^= Ibarx + gen1 ^= Ibarx SGData['SGGen'][i] = gen1 if SGData['SGInv']: if gen1 < 128: @@ -195,7 +204,7 @@ def SpcGroup(SGSymbol): SGData['SGSpin'] = 4*[1,] elif SGData['SGPtGrp'] in ['-3m',]: SGData['SGSpin'] = 5*[1,] - + else: if SGData['SGPtGrp'] in ['1','3','23',]: SGData['SGSpin'] = lattSpin+[1,] @@ -211,7 +220,7 @@ def SpcGroup(SGSymbol): def SGErrors(IErr): ''' Interprets the error message code from SpcGroup. Used in SpaceGroup. - + :param IErr: see SGError in :func:`SpcGroup` :returns: ErrString - a string with the error message or "Unknown error" @@ -263,12 +272,12 @@ def SGpolar(SGData): if M[1][2] > 0: NPZ[1] = 0 NPol = (NP[0]+NP[1]+NP[2]+NPZ[0]*NPZ[1])*(1-int(SGData['SGInv'])) return POL[NPol] - + def SGPtGroup(SGData): ''' Determine point group of the space group - done after space group symbol has been evaluated by SpcGroup. Only short symbols are allowed - + :param SGData: from :func:`SpcGroup` :returns: SSGPtGrp & SSGKl (only defaults for Mono & Ortho) ''' @@ -313,7 +322,7 @@ def SGPtGroup(SGData): if '2' in Flds[2]: return '-42m',[-1,-1,1] else: - return '-4m2',[-1,1,-1] + return '-4m2',[-1,1,-1] elif '2' in Flds[2:]: return '422',[1,-1,-1] else: @@ -358,15 +367,15 @@ def SGPtGroup(SGData): if '2' in Flds[2]: return '-62m',[-1,-1,1] else: - return '-6m2',[-1,1,-1] + return '-6m2',[-1,1,-1] elif '2' in Flds[2:]: return '622',[1,-1,-1] else: - return '6mm',[1,1,1] + return '6mm',[1,1,1] elif SGData['SGLaue'] == 'm3': #cubic - no (3+1) supersymmetry if '2' in Flds[1]: return '23',[] - else: + else: return 'm3',[] elif SGData['SGLaue'] == 'm3m': if '4' in Flds[1]: @@ -376,7 +385,7 @@ def SGPtGroup(SGData): return '432',[] else: return 'm3m',[] - + def SGPrint(SGData,AddInv=False): ''' Print the output of SpcGroup in a nicely formatted way. Used in SpaceGroup @@ -395,7 +404,7 @@ def SGPrint(SGData,AddInv=False): SGCen = list(SGData['SGCen']) if SGData.get('SGGray',False): SGText[-1] += " 1'" - if SGData.get('SGFixed',False): + if SGData.get('SGFixed',False): Mult //= 2 else: SGCen += list(SGData['SGCen']+[0,0,0]) @@ -406,7 +415,7 @@ def SGPrint(SGData,AddInv=False): if SGData['SGLatt'] in 'ABCIFR': SGText.append(' The lattice is '+CentStr+' '+SGData['SGLatt']+'-centered '+SGData['SGSys'].lower()) else: - SGText.append(' The lattice is '+CentStr+' '+'primitive '+SGData['SGSys'].lower()) + SGText.append(' The lattice is '+CentStr+' '+'primitive '+SGData['SGSys'].lower()) SGText.append(' The Laue symmetry is '+SGData['SGLaue']) if 'SGPtGrp' in SGData: #patch SGText.append(' The lattice point group is '+SGData['SGPtGrp']) @@ -420,7 +429,7 @@ def SGPrint(SGData,AddInv=False): SGText.append(' ') if len(SGData['SGCen']) == 1: SGText.append(' The equivalent positions are:\n') - else: + else: SGText.append(' The equivalent positions are:\n') SGText.append(' ('+Latt2text(SGCen)+')+\n') SGTable = [] @@ -429,14 +438,14 @@ def SGPrint(SGData,AddInv=False): if AddInv and SGData['SGInv']: for i,Opr in enumerate(SGData['SGOps']): IOpr = [-Opr[0],-Opr[1]] - SGTable.append('(%2d) %s'%(i+1,MT2text(IOpr))) + SGTable.append('(%2d) %s'%(i+1,MT2text(IOpr))) return SGText,SGTable def AllOps(SGData): ''' Returns a list of all operators for a space group, including those for centering and a center of symmetry - + :param SGData: from :func:`SpcGroup` :returns: (SGTextList,offsetList,symOpList,G2oprList) where @@ -446,7 +455,7 @@ def AllOps(SGData): symmetry operation to the operator in SGTextList and symOpList. these dx (etc.) values are added to the GSAS-II generated positions to provide the positions that are generated - by the normalized symmetry operators. + by the normalized symmetry operators. * symOpList: a list of tuples with the normalized symmetry operations as (M,T) values (see ``SGOps`` in the :ref:`Space Group object`) @@ -455,7 +464,7 @@ def AllOps(SGData): (0.5,0.5,0.5),...; where mult is 1 or -1 for the center of symmetry where opnum is the number for the symmetry operation, in ``SGOps`` (starting with 0) and opcode is mult*(100*icen+j+1). - * G2opcodes: a list with the name that GSAS-II uses for each symmetry + * G2opcodes: a list with the name that GSAS-II uses for each symmetry operation (same as opcode, above) ''' SGTextList = [] @@ -551,7 +560,7 @@ def GetOprNames(SGData): if SGData['SGInv']: OprNames += [GetOprPtrName(str(-irtx)) for irtx in PackRot(SGData['SGOps'])] return OprNames - + def MT2text(Opr,reverse=False): "From space group matrix/translation operator returns text version" XYZ = ('-Z','-Y','-X','X-Y','ERR','Y-X','X','Y','Z') @@ -576,7 +585,7 @@ def MT2text(Opr,reverse=False): Fld += XYZ[IJ].rjust(5) if j != 2: Fld += ', ' return Fld - + def Latt2text(Cen): "From lattice centering vectors returns ';' delimited cell centering vectors" lattTxt = '' @@ -611,7 +620,7 @@ def Latt2text(Cen): def SpaceGroup(SGSymbol): ''' - Print the output of SpcGroup in a nicely formatted way. + Print the output of SpcGroup in a nicely formatted way. :param SGSymbol: space group symbol (string) with spaces between axial fields :returns: nothing @@ -660,7 +669,7 @@ def SplitMagSpSG(MSpSg): Ib = If MSpcGp = Psym+' '+' '.join(Axf) return MSpcGp - + def SetMagnetic(SGData): GenSym,GenFlg,BNSsym = GetGenSym(SGData) SGData['GenSym'] = GenSym @@ -675,18 +684,18 @@ def GetGenSym(SGData): :param SGData: from :func:`SpcGroup` LaueSym = ('-1','2/m','mmm','4/m','4/mmm','3R','3mR','3','3m1','31m','6/m','6/mmm','m3','m3m') LattSym = ('P','A','B','C','I','F','R') - + ''' BNSsym = {} OprNames = [GetOprPtrName(str(irtx)) for irtx in PackRot(SGData['SGOps'])] if SGData['SGInv']: OprNames += [GetOprPtrName(str(-irtx)) for irtx in PackRot(SGData['SGOps'])] - # for oprname in OprNames: + # for oprname in OprNames: # print(oprname) Nsyms = len(SGData['SGOps']) if SGData['SGInv'] and not SGData['SGFixed']: Nsyms *= 2 UsymOp = ['1',] - OprFlg = [0,] + OprFlg = [0,] if Nsyms == 2: #Centric triclinic or acentric monoclinic UsymOp.append(OprNames[1]) OprFlg.append(SGData['SGGen'][1]) @@ -805,10 +814,10 @@ def GetGenSym(SGData): OprFlg.append(4) UsymOp.append(' m110 ') OprFlg.append(24) - + if 'P' in SGData['SGLatt']: if SGData['SGSys'] == 'triclinic': - BNSsym = {'P_a':[.5,0,0],'P_b':[0,.5,0],'P_c':[0,0,.5]} + BNSsym = {'P_a':[.5,0,0],'P_b':[0,.5,0],'P_c':[0,0,.5]} elif SGData['SGSys'] == 'monoclinic': BNSsym = {'P_a':[.5,0,0],'P_b':[0,.5,0],'P_c':[0,0,.5],'P_I':[.5,.5,.5]} if SGData['SGUniq'] == 'a': @@ -821,12 +830,12 @@ def GetGenSym(SGData): BNSsym = {'P_a':[.5,0,0],'P_b':[0,.5,0],'P_c':[0,0,.5], 'P_A':[0,.5,.5],'P_B':[.5,0,.5],'P_C':[.5,.5,0],'P_I':[.5,.5,.5]} elif SGData['SGSys'] == 'tetragonal': - BNSsym = {'P_c':[0,0,.5],'P_C':[.5,.5,0],'P_I':[.5,.5,.5]} + BNSsym = {'P_c':[0,0,.5],'P_C':[.5,.5,0],'P_I':[.5,.5,.5]} elif SGData['SGSys'] in ['trigonal','hexagonal']: - BNSsym = {'P_c':[0,0,.5]} + BNSsym = {'P_c':[0,0,.5]} elif SGData['SGSys'] == 'cubic': - BNSsym = {'P_I':[.5,.5,.5]} - + BNSsym = {'P_I':[.5,.5,.5]} + elif 'A' in SGData['SGLatt']: if SGData['SGSys'] == 'monoclinic': BNSsym = {} @@ -836,10 +845,10 @@ def GetGenSym(SGData): BNSsym.update({'A_a':[.5,0,0],'A_b':[0,.5,0]}) elif SGData['SGSys'] == 'orthorhombic': BNSsym = {'A_a':[.5,0,0],'A_b':[0,.5,0],'A_c':[0,0,.5], - 'A_B':[.5,0,.5],'A_C':[.5,.5,0]} + 'A_B':[.5,0,.5],'A_C':[.5,.5,0]} elif SGData['SGSys'] == 'triclinic': - BNSsym = {'A_a':[.5,0,0],'A_b':[0,.5,0],'A_c':[0,0,.5]} - + BNSsym = {'A_a':[.5,0,0],'A_b':[0,.5,0],'A_c':[0,0,.5]} + elif 'B' in SGData['SGLatt']: if SGData['SGSys'] == 'monoclinic': BNSsym = {} @@ -849,10 +858,10 @@ def GetGenSym(SGData): BNSsym.update({'B_a':[.5,0,0],'B_b':[0,.5,0]}) elif SGData['SGSys'] == 'orthorhombic': BNSsym = {'B_a':[.5,0,0],'B_b':[0,.5,0],'B_c':[0,0,.5], - 'B_A':[0,.5,.5],'B_C':[.5,.5,0]} + 'B_A':[0,.5,.5],'B_C':[.5,.5,0]} elif SGData['SGSys'] == 'triclinic': - BNSsym = {'B_a':[.5,0,0],'B_b':[0,.5,0],'B_c':[0,0,.5]} - + BNSsym = {'B_a':[.5,0,0],'B_b':[0,.5,0],'B_c':[0,0,.5]} + elif 'C' in SGData['SGLatt']: if SGData['SGSys'] == 'monoclinic': BNSsym = {} @@ -862,29 +871,29 @@ def GetGenSym(SGData): BNSsym.update({'C_a':[.5,0,0],'C_c':[0,0,.5],'C_B':[.5,0.,.5]}) elif SGData['SGSys'] == 'orthorhombic': BNSsym = {'C_a':[.5,0,0],'C_b':[0,.5,0],'C_c':[0,0,.5], - 'C_A':[0,.5,.5],'C_B':[.5,0,.5]} + 'C_A':[0,.5,.5],'C_B':[.5,0,.5]} elif SGData['SGSys'] == 'triclinic': - BNSsym = {'C_a':[.5,0,0],'C_b':[0,.5,0],'C_c':[0,0,.5]} - + BNSsym = {'C_a':[.5,0,0],'C_b':[0,.5,0],'C_c':[0,0,.5]} + elif 'I' in SGData['SGLatt']: if SGData['SGSys'] in ['monoclinic','orthorhombic','triclinic']: BNSsym = {'I_a':[.5,0,0],'I_b':[0,.5,0],'I_c':[0,0,.5]} elif SGData['SGSys'] == 'tetragonal': BNSsym = {'I_c':[0,0,.5]} elif SGData['SGSys'] == 'cubic': - BNSsym = {} - + BNSsym = {} + elif 'F' in SGData['SGLatt']: if SGData['SGSys'] in ['monoclinic','orthorhombic','cubic','triclinic']: BNSsym = {'F_S':[.5,.5,.5]} - + elif 'R' in SGData['SGLatt']: BNSsym = {'R_I':[0,0,.5]} - + if SGData['SGGray']: for bns in BNSsym: BNSsym[bns].append(0.5) - + return UsymOp,OprFlg,BNSsym def ApplyBNSlatt(SGData,BNSlatt): @@ -924,7 +933,7 @@ def ApplyBNSlatt(SGData,BNSlatt): C = SGCen+A[:3] SGData['SGCen'] = np.vstack((SGCen,C))%1. return Tmat - + def CheckSpin(isym,SGData): ''' Check for exceptions in spin rules ''' @@ -971,7 +980,7 @@ def MagSGSym(SGData): #needs to use SGPtGrp not SGLaue! Ptsym[i] += "'" else: for i in range(len(GenSym)): - if SpnFlp[i+1] < 0: + if SpnFlp[i+1] < 0: sym[i] += "'" Ptsym[i] += "'" SGData['MagPtGp'] = '/'.join(Ptsym) @@ -1010,8 +1019,8 @@ def MagSGSym(SGData): #needs to use SGPtGrp not SGLaue! sym[1] += "'" Ptsym[0] += "'" if SpnFlp[2]*SpnFlp[3] < 0: - sym[0] += "'" - Ptsym[0] += "'" + sym[0] += "'" + Ptsym[0] += "'" magSym[1] = '/'.join(sym) magPtGp[0] = '/'.join(Ptsym) SGData['MagPtGp'] = ''.join(magPtGp) @@ -1043,8 +1052,8 @@ def MagSGSym(SGData): #needs to use SGPtGrp not SGLaue! sym[1] += "'" Ptsym[1] += "'" if SpnFlp[2]*SpnFlp[3] < 0: - sym[0] += "'" - Ptsym[0] += "'" + sym[0] += "'" + Ptsym[0] += "'" magSym[1] = '/'.join(sym) magPtGp[0] = '/'.join(Ptsym) else: @@ -1054,7 +1063,7 @@ def MagSGSym(SGData): #needs to use SGPtGrp not SGLaue! if SpnFlp[1]*SpnFlp[2] < 0: magSym[1] += "'" SGData['MagPtGp'] = ''.join(magPtGp) - elif SGLaue in ['3','3m1','31m']: #ok + elif SGLaue in ['3','3m1','31m']: #ok if '-' in SGPtGrp: Ptsym = list(SGPtGrp[1:]) Ptsym[0] = '-'+Ptsym[0] @@ -1222,7 +1231,7 @@ def Text2MT(mcifOpr,CIF=True): T.append(0.) M.append(XYZ[opMT[0].lower()]) return np.array(M),np.array(T) - + def MagText2MTS(mcifOpr,CIF=True): "From magnetic space group cif text returns matrix/translation + spin flip" XYZ = {'x':[1,0,0],'+x':[1,0,0],'-x':[-1,0,0],'y':[0,1,0],'+y':[0,1,0],'-y':[0,-1,0], @@ -1252,7 +1261,7 @@ def MagText2MTS(mcifOpr,CIF=True): if '-1' in ops[3]: spnflp = -1 return np.array(M),np.array(T),spnflp - + def MagSSText2MTS(Opr,G2=False): "From magnetic super space group cif text returns matrix/translation + spin flip" XYZ = {'x1':[1,0,0,0],'-x1':[-1,0,0,0], @@ -1272,7 +1281,7 @@ def MagSSText2MTS(Opr,G2=False): 'x-t':[1,0,0,-1],'+x-t':[1,0,0,-1],'-x+t':[-1,0,0,1], 'y-t':[0,1,0,-1],'+y-t':[0,1,0,-1],'-y+t':[0,-1,0,1], 'x+y-t':[1,1,0,-1],'+x+y-t':[1,1,0,-1],'-x-y+t':[-1,-1,0,1]} - + ops = Opr.split(",") M = [] T = [] @@ -1281,7 +1290,7 @@ def MagSSText2MTS(Opr,G2=False): if '/' in op: ip = op.index('/') if G2: - T.append(eval(op[:ip+2])) + T.append(eval(op[:ip+2])) M.append(XYZ[op[ip+2:]]) else: T.append(eval(op[ip-2:])) @@ -1334,7 +1343,7 @@ def GetSGSpin(SGData,MSgSym): for i in [1,2]: if "'" in mFlds[i+1]: mSpn[i+1] = -1 - elif SGLaue in ['3','3m1','31m']: #ok + elif SGLaue in ['3','3m1','31m']: #ok if len(GenSym) == 1: #all ok Id = 2 if (len(mFlds) == 4) and (mFlds[2] == '1'): @@ -1352,7 +1361,7 @@ def GetSGSpin(SGData,MSgSym): if mFlds[2] == '1': i,j = [1,3] if "'" in mFlds[i]: - mSpn[1:3] = [1,-1] + mSpn[1:3] = [1,-1] elif "'" in mFlds[i]: mSpn[1:3] = [-1,-1] elif "'" in mFlds[i] and "'" in mFlds[i]: @@ -1361,7 +1370,7 @@ def GetSGSpin(SGData,MSgSym): if 'c' not in mFlds[2]: i,j = [1,2] if "'" in mFlds[i]: - mSpn[1:3] = [1,-1] + mSpn[1:3] = [1,-1] elif "'" in mFlds[i]: mSpn[1:3] = [-1,-1] elif "'" in mFlds[i] and "'" in mFlds[i]: @@ -1418,7 +1427,7 @@ def GenMagOps(SGData): print('index error: ',Nsym,ieqv,Nfl,iunq) FlpSpn = FlpSpn+[1,] SpnFlp[ieqv] *= FlpSpn[iunq] - if SGData['SGLaue'] == '6/m': # treat special as algorithm above fails + if SGData['SGLaue'] == '6/m': # treat special as algorithm above fails if SGData['SGSpin'] == [1,-1,1]: SpnFlp = [1,-1,1,-1,1,-1,1,-1,1,-1,1,-1] elif SGData['SGSpin'] == [1,1,-1]: @@ -1434,11 +1443,11 @@ def GenMagOps(SGData): SpnFlp = np.concatenate((SpnFlp,SpnFlp[:Nsym]*FlpSpn[Nfl+incv-1])) if SGData['SGGray']: SpnFlp = np.concatenate((SpnFlp,-SpnFlp)) - detMs = 2*detMs + detMs = 2*detMs MagMom = SpnFlp*np.array(detMs) #duplicate for no. centerings SGData['MagMom'] = MagMom return OprNames,SpnFlp - + def GetOpNum(Opr,SGData): Nops = len(SGData['SGOps']) opNum = abs(Opr)%100 @@ -1449,11 +1458,11 @@ def GetOpNum(Opr,SGData): Nops *= 2 opNum += cent*Nops return opNum - + ################################################################################ #### Superspace group codes ################################################################################ - + def SSpcGroup(SGData,SSymbol): """ Determines supersymmetry information from superspace group name; currently only for (3+1) superlattices @@ -1461,16 +1470,16 @@ def SSpcGroup(SGData,SSymbol): :param SGData: space group data structure as defined in SpcGroup above (see :ref:`SGData`). :param SSymbol: superspace group symbol extension (string) defining modulation direction & generator info. :returns: (SSGError,SSGData) - + * SGError = 0 for no errors; >0 for errors (see SGErrors below for details) * SSGData - is a dict (see :ref:`Superspace Group object`) with entries: - + * 'SSpGrp': full superspace group symbol, accidental spaces removed; for display only * 'SSGCen': 4D cell centering vectors [0,0,0,0] at least * 'SSGOps': 4D symmetry operations as [M,T] so that M*x+T = x' """ - + def fixMonoOrtho(): mod = ''.join(modsym).replace('1/2','0').replace('1','0') if SGData['SGPtGrp'] in ['2','m']: #OK @@ -1496,7 +1505,7 @@ def fixMonoOrtho(): return [-SSGKl[i] if mod[i] in ['a','b','g'] else SSGKl[i] for i in range(3)] else: return [SSGKl[i] for i in range(3)] - + def extendSSGOps(SSGOps): for OpA in SSGOps: OpAtxt = SSMT2text(OpA) @@ -1513,7 +1522,7 @@ def extendSSGOps(SSGOps): for k,OpD in enumerate(SSGOps): OpDtxt = SSMT2text(OpD) OpDtxt2 = '' - if SGData['SGGray']: + if SGData['SGGray']: OpDtxt2 = SSMT2text([OpD[0],OpD[1]+np.array([0.,0.,0.,.5])]) # print ' ('+OpCtxt.replace(' ','')+' = ? '+OpDtxt.replace(' ','')+')' if OpCtxt == OpDtxt: @@ -1532,12 +1541,12 @@ def extendSSGOps(SSGOps): # print (Txt) return False,Txt return True,SSGOps - + def findMod(modSym): for a in ['a','b','g']: if a in modSym: return a - + def genSSGOps(): SSGOps = SSGData['SSGOps'][:] iFrac = {} @@ -1563,7 +1572,7 @@ def genSSGOps(): SSGOps[1][1][3] = 0.5 for i in iFrac: SSGOps[1][0][3,i] = SSGKl[0] - + # orthorhombic - all OK not fully checked elif SGData['SGPtGrp'] in ['222','mm2','m2m','2mm']: #OK if SGData['SGPtGrp'] == '222': @@ -1586,7 +1595,7 @@ def genSSGOps(): if not E: return E,SSGOps elif SGData['SGPtGrp'] == 'mmm': #OK - OrOps = {'g':{0:[1,3],1:[2,3]},'a':{1:[2,1],2:[3,1]},'b':{0:[1,2],2:[3,2]}} + OrOps = {'g':{0:[1,3],1:[2,3]},'a':{1:[2,1],2:[3,1]},'b':{0:[1,2],2:[3,2]}} a = findMod(SSGData['modSymb']) if a == 'g': SSkl = [1,1,1] @@ -1603,7 +1612,7 @@ def genSSGOps(): SSGOps[i+1][1][3] = genQ[i] E,SSGOps = extendSSGOps(SSGOps) if not E: - return E,SSGOps + return E,SSGOps # tetragonal - all done & checked elif SGData['SGPtGrp'] == '4': #OK SSGOps[1][0][3,3] = SSGKl[0] @@ -1654,7 +1663,7 @@ def genSSGOps(): return E,Result else: SSGOps = Result - + # trigonal - all done & checked elif SGData['SGPtGrp'] == '3': #OK SSGOps[1][0][3,3] = SSGKl[0] @@ -1672,7 +1681,7 @@ def genSSGOps(): for i,j in enumerate([1,5]): if SGData['SGPtGrp'] in ['3m','-3m']: SSGOps[j][0][3,3] = 1 - else: + else: SSGOps[j][0][3,3] = SSGKl[i+1] if genQ[i]: SSGOps[j][1][3] = genQ[i] @@ -1691,7 +1700,7 @@ def genSSGOps(): SSGOps[j][0][3,3] = 1 if genQ[i+1]: SSGOps[j][1][3] = genQ[i+1] - + # hexagonal all done & checked elif SGData['SGPtGrp'] == '6': #OK SSGOps[1][0][3,3] = SSGKl[0] @@ -1708,7 +1717,7 @@ def genSSGOps(): if genQ[i]: SSGOps[j][1][3] = -genQ[i] E,SSGOps = extendSSGOps(SSGOps) - + elif SGData['SGPtGrp'] in ['6mm','-62m','-6m2',]: #OK for i,j in enumerate([1,6,7]): SSGOps[j][0][3,3] = SSGKl[i] @@ -1725,7 +1734,7 @@ def genSSGOps(): return True,SSGOps E,SSGOps = extendSSGOps(SSGOps) return E,SSGOps - + def specialGen(gensym,modsym): sym = ''.join(gensym) if SGData['SGPtGrp'] in ['2/m',] and 'n' in SGData['SpGrp']: @@ -1757,7 +1766,7 @@ def specialGen(gensym,modsym): elif sym == 's00': gensym = 'ss0' return gensym - + Fracs = {'1/2':0.5,'1/3':1./3,'1':1.0,'0':0.,'s':.5,'t':1./3,'q':.25,'h':-1./6,'a':0.,'b':0.,'g':0.} if SGData['SGLaue'] in ['m3','m3m']: return '(3+1) superlattices not defined for cubic space groups',None @@ -1806,24 +1815,24 @@ def specialGen(gensym,modsym): print ('Super spacegroup operators for '+SSGData['SSpGrp']) for Op in Result: print (SSMT2text(Op).replace(' ','')) - if SGData['SGInv']: + if SGData['SGInv']: for Op in Result: Op = [-Op[0],-Op[1]%1.] - print (SSMT2text(Op).replace(' ','')) + print (SSMT2text(Op).replace(' ','')) return None,SSGData else: return Result+'\nOperator conflict - incorrect superspace symbol',None - + def SSChoice(SGData): ''' Gets the unique set of possible super space groups for a given space group ''' ptgpSS = {'1':['(abg)',],'-1':['(abg)',], - + '2':['(a0g)','(a1/2g)','(0b0)','(1/2b0)','(0b1/2)','(1/2b1/2)'], 'm':['(a0g)','(a1/2g)','(0b0)','(1/2b0)','(0b1/2)','(1/2b1/2)'], '2/m':['(a0g)','(a1/2g)','(0b0)','(1/2b0)','(0b1/2)','(1/2b1/2)'], - + '222':['(00g)','(1/20g)','(01/2g)','(1/21/2g)','(10g)','(01g)', '(a00)','(a1/20)','(a01/2)','(a1/21/2)','(a10)','(a01)', '(0b0)','(1/2b0)','(0b1/2)','(1/2b1/2)','(1b0)','(0b1)',], @@ -1839,50 +1848,50 @@ def SSChoice(SGData): 'mmm':['(00g)','(1/20g)','(01/2g)','(1/21/2g)','(10g)','(01g)', '(a00)','(a1/20)','(a01/2)','(a1/21/2)','(a10)','(a01)', '(0b0)','(1/2b0)','(0b1/2)','(1/2b1/2)','(1b0)','(0b1)',], - + '4':['(00g)','(1/21/2g)'],'4mm':['(00g)','(1/21/2g)'], '4/m':['(00g)','(1/21/2g)'], '422':['(00g)','(1/21/2g)'],'-4m2':['(00g)','(1/21/2g)'],'-42m':['(00g)','(1/21/2g)'], '4/mmm':['(00g)','(1/21/2g)'], - + '3':['(00g)','(1/31/3g)'],'-3':['(00g)','(1/31/3g)'], '32':['(00g)'],'3m':['(00g)'],'-3m':['(00g)'], '321':['(00g)'],'3m1':['(00g)'],'-3m1':['(00g)'], '312':['(00g)','(1/31/3g)'],'31m':['(00g)','(1/31/3g)'],'-31m':['(00g)','(1/31/3g)'], - + '6':['(00g)',],'6/m':['(00g)',],'-62m':['(00g)',],'-6m2':['(00g)',], '622':['(00g)',],'6/mmm':['(00g)',],'6mm':['(00g)',], - + '23':['',],'m3':['',],'432':['',],'-43m':['',],'m3m':['',]} - + ptgpTS = {'1':['0',],'-1':['0',], - + '2':['0','s'],'m':['0','s'], '2/m':['00','0s','ss','s0'], - + '222':['000','s00','0s0','00s',], 'mm2':['000','s00','0s0','00s','ss0','s0s','0ss','q00','0q0','00q','0qq','q0q','qq0'], 'm2m':['000','s00','0s0','00s','ss0','s0s','0ss','q00','0q0','00q','0qq','q0q','qq0'], '2mm':['000','s00','0s0','00s','ss0','s0s','0ss','q00','0q0','00q','0qq','q0q','qq0'], 'mmm':['000','s00','0s0','00s','ss0','s0s','0ss','q00','0q0','00q','0qq','q0q','qq0'], - + '4':['0','q','s'],'4mm':['000','q00','s00','s0s','ss0','0ss','qq0','qqs'], '4/m':['00','s0'],'-4m2':['000','0s0','0q0'],'-42m':['000','00s'], '422':['000','q00','s00','s0s','ss0','0ss','qq0','qqs','0q0'], '4/mmm':['0000','s0s0','00ss','s00s','ss00','0ss0','0s0s'], - + '3':['0','t'],'-3':['0','t'], '32':['00','t0'],'3m':['00','0s'],'-3m':['00','0s'], '321':['000','t00'],'3m1':['000','0s0'],'-3m1':['000','0s0'], '312':['000','t00'],'31m':['000','00s'],'-31m':['000','00s'], - + '6':['0','h','t','s'], '6/m':['00','s0'],'-62m':['000','00s'],'-6m2':['000','0s0'], '622':['000','h00','t00','s00',],'6mm':['000','ss0','s0s','0ss',], '6/mmm':['0000','s0s0','00ss','s00s','ss00','0ss0','0s0s'], - + '23':['',],'m3':['',],'432':['',],'-43m':['',],'m3m':['',]} - + ptgp = SGData['SGPtGrp'] SSChoice = [] for ax in ptgpSS[ptgp]: @@ -1899,7 +1908,7 @@ def SSChoice(SGData): ssHash.append(sshash) ssChoice.append(item) return ssChoice - + def splitSSsym(SSymbol): ''' Splits supersymmetry symbol into two lists of strings @@ -1939,7 +1948,7 @@ def splitSSsym(SSymbol): modsym = [modsym[0],modsym[1:4],modsym[4:]] gensym = list(gensym) return modsym,gensym - + def SSGPrint(SGData,SSGData,AddInv=False): ''' Print the output of SSpcGroup in a nicely formatted way. Used in SSpaceGroup @@ -1959,7 +1968,7 @@ def SSGPrint(SGData,SSGData,AddInv=False): SSsymb = SGData['BNSlattsym'][0]+SSsymb[1:] SSGCen = list(SSGData['SSGCen']) if SGData.get('SGGray',False): - if SGData.get('SGFixed',False): + if SGData.get('SGFixed',False): Mult //= 2 else: SSGCen += list(SSGData['SSGCen']+[0,0,0,0.5]) @@ -2003,11 +2012,11 @@ def SSGPrint(SGData,SSGData,AddInv=False): if AddInv and SGData['SGInv']: for i,Opr in enumerate(SSGData['SSGOps']): IOpr = [-Opr[0],-Opr[1]] - SSGTable.append('(%2d) %s'%(i+1+len(SSGData['SSGOps']),SSMT2text(IOpr))) + SSGTable.append('(%2d) %s'%(i+1+len(SSGData['SSGOps']),SSMT2text(IOpr))) return SSGText,SSGTable - + def SSGModCheck(Vec,modSymb,newMod=True): - ''' Checks modulation vector compatibility with supersymmetry space group symbol. + ''' Checks modulation vector compatibility with supersymmetry space group symbol. if newMod: Superspace group symbol takes precidence & the vector will be modified accordingly ''' Fracs = {'1/2':0.5,'1/3':1./3,'1':1.0,'0':0.,'a':0.,'b':0.,'g':0.} @@ -2050,7 +2059,7 @@ def SSMT2text(Opr): Fld += IJ.rjust(8) if j != 3: Fld += ', ' return Fld - + def SSLatt2text(SSGCen): "Lattice centering vectors to text" lattTxt = '' @@ -2063,10 +2072,10 @@ def SSLatt2text(SSGCen): lattTxt += ';' lattTxt = lattTxt.rstrip(';').lstrip(' ') return lattTxt - + def SSpaceGroup(SGSymbol,SSymbol): ''' - Print the output of SSpcGroup in a nicely formatted way. + Print the output of SSpcGroup in a nicely formatted way. :param SGSymbol: space group symbol with spaces between axial fields. :param SSymbol: superspace group symbol extension (string). @@ -2077,16 +2086,16 @@ def SSpaceGroup(SGSymbol,SSymbol): if E > 0: print (SGErrors(E)) return - E,B = SSpcGroup(A,SSymbol) + E,B = SSpcGroup(A,SSymbol) if E > 0: print (E) return for l in SSGPrint(B): print (l) - + def SGProd(OpA,OpB): ''' - Form space group operator product. OpA & OpB are [M,V] pairs; + Form space group operator product. OpA & OpB are [M,V] pairs; both must be of same dimension (3 or 4). Returns [M,V] pair ''' A,U = OpA @@ -2094,10 +2103,10 @@ def SGProd(OpA,OpB): M = np.inner(B,A.T) W = np.inner(B,U)+V return M,W - + def GetLittleGrpOps(SGData,vec): ''' Find rotation part of operators that leave vec unchanged - + :param SGData: space group data structure as defined in SpcGroup above. :param vec: a numpy array of fractional vector coordinates :returns: Little - list of operators [M,T] that form the little gropu @@ -2111,10 +2120,10 @@ def GetLittleGrpOps(SGData,vec): if np.allclose(tvec,vec%1.): Little.append([M,T]) return Little - + def MoveToUnitCell(xyz): ''' - Translates a set of coordinates so that all values are >=0 and < 1 + Translates a set of coordinates so that all values are >=0 and < 1 :param xyz: a list or numpy array of fractional coordinates :returns: XYZ - numpy array of new coordinates now 0 or greater and less than 1 @@ -2122,11 +2131,11 @@ def MoveToUnitCell(xyz): XYZ = np.array(xyz)%1. cell = np.asarray(np.rint(XYZ-xyz),dtype=np.int32) return XYZ,cell - + def Opposite(XYZ,toler=0.0002): ''' - Gives opposite corner, edge or face of unit cell for position within tolerance. - Result may be just outside the cell within tolerance + Gives opposite corner, edge or face of unit cell for position within tolerance. + Result may be just outside the cell within tolerance :param XYZ: 0 >= np.array[x,y,z] > 1 as by MoveToUnitCell :param toler: unit cell fraction tolerance making opposite @@ -2142,7 +2151,7 @@ def Opposite(XYZ,toler=0.0002): for key in D: new[key] = np.array(D[key])+np.array(XYZ) return new - + def GenAtom(XYZ,SGData,All=False,Uij=[],Move=True): ''' Generates the equivalent positions for a specified coordinate and space group @@ -2153,7 +2162,7 @@ def GenAtom(XYZ,SGData,All=False,Uij=[],Move=True): False return only unique positions :param Uij: [U11,U22,U33,U12,U13,U23] or [] if no Uij :param Move: True move generated atom positions to be inside cell - False do not move atoms + False do not move atoms :return: [[XYZEquiv],Idup,[UijEquiv],spnflp] * [XYZEquiv] is list of equivalent positions (XYZ is first entry) @@ -2162,7 +2171,7 @@ def GenAtom(XYZ,SGData,All=False,Uij=[],Move=True): Cell = unit cell translations needed to put new positions inside cell [UijEquiv] - equivalent Uij; absent if no Uij given * +1/-1 for spin inversion of operator - empty if not magnetic - + ''' XYZEquiv = [] UijEquiv = [] @@ -2215,14 +2224,14 @@ def GenAtom(XYZ,SGData,All=False,Uij=[],Move=True): return zip(XYZEquiv,UijEquiv,Idup,Cell,spnflp) else: return zip(XYZEquiv,Idup,Cell,spnflp) - + def GenHKL(HKL,SGData): ''' Generates all equivlent reflections including Friedel pairs :param HKL: [h,k,l] must be integral values :param SGData: space group data obtained from SpcGroup :returns: array Uniq: equivalent reflections ''' - + Ops = SGData['SGOps'] OpM = np.array([op[0] for op in Ops]) Uniq = np.inner(OpM,HKL) @@ -2248,14 +2257,13 @@ def GenHKLf(HKL,SGData): OpM = np.array([op[0] for op in Ops],order='F') OpT = np.array([op[1] for op in Ops]) Cen = np.array([cen for cen in SGData['SGCen']],order='F') - - import pyspg + Nuniq,Uniq,iabsnt,mulp = pyspg.genhklpy(hklf,len(Ops),OpM,OpT,SGData['SGInv'],len(Cen),Cen) h,k,l,f = Uniq Uniq=np.array(list(zip(h[:Nuniq],k[:Nuniq],l[:Nuniq]))) phi = f[:Nuniq] return iabsnt,mulp,Uniq,phi - + def checkSSLaue(HKL,SGData,SSGData): #Laue check here - Toss HKL if outside unique Laue part h,k,l,m = HKL @@ -2302,12 +2310,12 @@ def checkSSLaue(HKL,SGData,SSGData): return False else: return True - + def checkHKLextc(HKL,SGData): ''' Checks if reflection extinct - does not check centering - :param HKL: [h,k,l] + :param HKL: [h,k,l] :param SGData: space group data obtained from SpcGroup :returns: True if extinct; False if allowed @@ -2331,7 +2339,7 @@ def checkMagextc(HKL,SGData): Checks if reflection magnetically extinct; does fullcheck (centering, too) uses algorthm from Gallego, et al., J. Appl. Cryst. 45, 1236-1247 (2012) - :param HKL: [h,k,l] + :param HKL: [h,k,l] :param SGData: space group data obtained from SpcGroup; must have magnetic symmetry SpnFlp data :returns: True if magnetically extinct; False if allowed (to match GenHKLf) @@ -2369,7 +2377,7 @@ def checkMagextc(HKL,SGData): if np.abs(np.inner(HKL,Psum)) > 1.e-3: return True return False - + def checkSSextc(HKL,SSGData): Ops = SSGData['SSGOps'] OpM = np.array([op[0] for op in Ops]) @@ -2384,11 +2392,11 @@ def checkSSextc(HKL,SSGData): if phkl%1.: return False return True - + ################################################################################ #### Site symmetry tables ################################################################################ - + OprName = { '-6643': ['-1',1],'6479' : ['2(z)',2],'-6479': ['m(z)',3], '6481' : ['m(y)',4],'-6481': ['2(y)',5],'6641' : ['m(x)',6], @@ -2412,7 +2420,7 @@ def checkSSextc(HKL,SSGData): '-6666': ['-6(z)5',55],'-6538': ['-6(z)1',56],'-2223':['-3(+++)2',57], '-975' :['-3(+++)1',58],'-6456': ['-3(z)1',59],'-483' :['-3(-+-)1',60], '969' :['-3(--+)1',61],'-6584': ['-3(z)2',62],'2169' :['-3(--+)2',63], - '-2151':['-3(+--)2',64], } + '-2151':['-3(+--)2',64], } KNsym = { '0' :' 1 ','1' :' -1 ','64' :' 2(x)','32' :' m(x)', @@ -2495,7 +2503,7 @@ def checkSSextc(HKL,SSGData): '2(100)' :(12,25,12,26),'2(010)' :(13,28,13,27),'2(110)' :( 6,19, 6,18),'2(120)' :(15,27,15,24), '2(210)' :(16,26,16,25),'2(+-0)' :( 7,20, 7,17),'-1' :( 1,29,28, 0) } - + CSxinel = [[], # 0th empty - indices are Fortran style [[0,0,0],[ 0.0, 0.0, 0.0],[0,0,0]], #1 0 0 0 [[1,1,1],[ 1.0, 1.0, 1.0],[1,1,1]], #2 X X X @@ -2558,11 +2566,11 @@ def checkSSextc(HKL,SSGData): [[1,2,3,1,4,4],[ 1.0, 1.0, 1.0, 0.5, 1.0, 0.5],[1,1,1,0,1,0],[1.0,1.0,1.0,0.5,0.0,0.0]], #28 A B C A/2 E E/2 [[1,2,3,4,5,6],[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],[1,1,1,1,1,1],[1.0,1.0,1.0,0.0,0.0,0.0]], #29 A B C D E F ] - + ################################################################################ #### Site symmetry routines ################################################################################ - + def GetOprPtrName(key): 'Needs a doc string' try: @@ -2590,22 +2598,22 @@ def GetKNsym(key): return 'sp' def GetNXUPQsym(siteSym): - ''' - The codes XUPQ are for lookup of symmetry constraints for position(X), thermal parm(U) & magnetic moments (P & Q) + ''' + The codes XUPQ are for lookup of symmetry constraints for position(X), thermal parm(U) & magnetic moments (P & Q) ''' return NXUPQsym[siteSym] -def GetCSxinel(siteSym): +def GetCSxinel(siteSym): "returns Xyz terms, multipliers, GUI flags" indx = GetNXUPQsym(siteSym.strip()) return CSxinel[indx[0]] - + def GetCSuinel(siteSym): "returns Uij terms, multipliers, GUI flags & Uiso2Uij multipliers" indx = GetNXUPQsym(siteSym.strip()) return CSuinel[indx[1]] - -def GetCSpqinel(SpnFlp,dupDir): + +def GetCSpqinel(SpnFlp,dupDir): "returns Mxyz terms, multipliers, GUI flags" CSI = [[1,2,3],[1.0,1.0,1.0]] for sopr in dupDir: @@ -2637,7 +2645,7 @@ def GetCSpqinel(SpnFlp,dupDir): CSI[1][kcs] = csi[1][kcs] # print(CSI) return CSI - + def getTauT(tau,sop,ssop,XYZ,wave=np.zeros(3)): phase = np.sum(XYZ*wave) ssopinv = nl.inv(ssop[0]) @@ -2654,7 +2662,7 @@ def getTauT(tau,sop,ssop,XYZ,wave=np.zeros(3)): dT = np.tan(np.pi*sumdtau) tauT = np.inner(mst,XYZ-sop[1])+epsinv*(tau-ssop[1][3]+phase) return sdet,ssdet,dtau,dT,tauT - + def OpsfromStringOps(A,SGData,SSGData): SGOps = SGData['SGOps'] SSGOps = SSGData['SSGOps'] @@ -2670,9 +2678,9 @@ def OpsfromStringOps(A,SGData,SSGData): if len(Ax) > 1: unit = eval('['+Ax[1]+']') return SGOps[nA],SSGOps[nA],iC,SGData['SGCen'][nC],unit - + def GetSSfxuinel(waveType,Stype,nH,XYZ,SGData,SSGData,debug=False): - + def orderParms(CSI): parms = [0,] for csi in CSI: @@ -2683,25 +2691,25 @@ def orderParms(CSI): for i in [0,1,2]: csi[i] = parms.index(csi[i]) return CSI - + def fracCrenel(tau,Toff,Twid): Tau = (tau-Toff[:,nxs])%1. A = np.where(Tau 0.01,Sum,0.01) Sum = np.sqrt(Sum) return Sum*xyz - + PHI = np.linspace(0.,360.,30,True) PSI = np.linspace(0.,180.,30,True) X = np.outer(npcosd(PHI),npsind(PSI)) @@ -3308,12 +3316,11 @@ def genMustrain(xyz,Shkl): XYZ = np.dstack((X,Y,Z)) XYZ = np.nan_to_num(np.apply_along_axis(genMustrain,2,XYZ,Shkl)) return np.sqrt(np.sum(XYZ**2)/900.) - + def Muiso2Shkl(muiso,SGData,cell): "this is to convert isotropic mustrain to generalized Shkls" - import GSASIIlattice as G2lat A = G2lat.cell2AB(cell)[0] - + def minMus(Shkl,muiso,H,SGData,A): U = np.inner(A.T,H) S = np.array(MustrainCoeff(U,SGData)) @@ -3321,7 +3328,7 @@ def minMus(Shkl,muiso,H,SGData,A): Sum = np.sqrt(np.sum(np.multiply(S,Shkl[:nS,nxs]),axis=0)) rad = np.sqrt(np.sum((Sum[:,nxs]*H)**2,axis=1)) return (muiso-rad)**2 - + laue = SGData['SGLaue'] PHI = np.linspace(0.,360.,60,True) PSI = np.linspace(0.,180.,60,True) @@ -3344,13 +3351,13 @@ def minMus(Shkl,muiso,H,SGData,A): elif laue in ['2/m']: S0 = [1000.,1000.,1000.,0.,0.,0.,0.,0.,0.] else: - S0 = [1000.,1000.,1000.,1000.,1000., 1000.,1000.,1000.,1000.,1000., + S0 = [1000.,1000.,1000.,1000.,1000., 1000.,1000.,1000.,1000.,1000., 1000.,1000.,0.,0.,0.] S0 = np.array(S0) HKL = np.reshape(HKL,(-1,3)) result = so.leastsq(minMus,S0,(np.ones(HKL.shape[0])*muiso,HKL,SGData,A)) return result[0] - + def PackRot(SGOps): IRT = [] for ops in SGOps: @@ -3361,8 +3368,8 @@ def PackRot(SGOps): irt *= 3 irt += M[k][j] IRT.append(int(irt)) - return IRT - + return IRT + def SytSym(XYZ,SGData): ''' Generates the number of equivalent positions and a site symmetry code for a specified coordinate and space group @@ -3412,7 +3419,7 @@ def SytSym(XYZ,SGData): px = px.split('(') px[0] += "'" px = '('.join(px) - else: + else: px += "'" dupDir[px] = L Isym += 2**(jx-1) @@ -3421,17 +3428,17 @@ def SytSym(XYZ,SGData): Mult = len(SGData['SGOps'])*Ncen*inv//Jdup except: # patch because Jdup is not getting incremented for most atoms! Mult = 0 - + return GetKNsym(str(Isym)),Mult,Ndup,dupDir def AtomDxSymFix(Dx,SytSym,CSIX): - ''' Applies site symmetry restrictions to atom position shifts. 1st parameter value + ''' Applies site symmetry restrictions to atom position shifts. 1st parameter value of each kind encountered is assumed to be the independent one. Needed for ISODISTORT mode shifts. - + ''' if SytSym == '1': - return Dx + return Dx newDx = [] for ix in [0,1,2]: cx = CSIX[2][ix] @@ -3440,9 +3447,9 @@ def AtomDxSymFix(Dx,SytSym,CSIX): else: newDx.append(0.0) return np.array(newDx) - - - + + + def MagSytSym(SytSym,dupDir,SGData): ''' site sym operations: 1,-1,2,3,-3,4,-4,6,-6,m need to be marked if spin inversion @@ -3465,14 +3472,14 @@ def MagSytSym(SytSym,dupDir,SGData): return MagSytSym if len(dupDir) == 1: return list(dupDir.keys())[0] - - + + if '2/m' in SytSym: #done I think; last 2wo might be not needed ops = {'(x)':['2(x)','m(x)'],'(y)':['2(y)','m(y)'],'(z)':['2(z)','m(z)'], '(100)':['2(100)','m(100)'],'(010)':['2(010)','m(010)'],'(001)':['2(001)','m(001)'], '(120)':['2(120)','m(120)'],'(210)':['2(210)','m(210)'],'(+-0)':['2(+-0)','m(+-0)'], '(110)':['2(110)','m(110)']} - + elif '4/mmm' in SytSym: ops = {'(x)':['4(x)','m(x)','m(y)','m(0+-)'], #m(0+-) for cubic m3m? '(y)':['4(y)','m(y)','m(z)','m(+0-)'], #m(+0-) @@ -3510,7 +3517,7 @@ def MagSytSym(SytSym,dupDir,SGData): '(100)':['m(z)','m(100)','m(120)',],'(010)':['m(z)','m(010)','m(210)',], '(110)':['m(z)','m(110)','m(+-0)',], '(x)':['m(x)','m(y)','m(z)'],'(y)':['m(x)','m(y)','m(z)'],'(z)':['m(x)','m(y)','m(z)'],} - + elif '32' in SytSym: ops = {'(120)':['3','2(120)',],'(100)':['3','2(100)'],'(111)':['3(111)','2(x)']} elif '23' in SytSym: @@ -3521,7 +3528,7 @@ def MagSytSym(SytSym,dupDir,SGData): ops = {'(111)':['3(111)','m(+-0)',],'(+--)':['3(+--)','m(0+-)',], '(-+-)':['3(-+-)','m(+0-)',],'(--+)':['3(--+)','m(+-0)',], '(100)':['3','m(100)'],'(120)':['3','m(210)',]} - + if SytSym.split('(')[0] in ['6/m','6mm','-6m2','622','-6','-3','-3m','-43m',]: #not simple cases MagSytSym = SytSym if "-1'" in dupDir: @@ -3558,12 +3565,12 @@ def MagSytSym(SytSym,dupDir,SGData): MagSytSym += '/' if '-3/m' in SytSym: MagSytSym = '-'+MagSytSym - + MagSytSym += axis -# some exceptions & special rules +# some exceptions & special rules if MagSytSym == "4'/m'm'm'": MagSytSym = "4/m'm'm'" return MagSytSym - + # if len(GenSym) == 3: # if SGSpin[1] < 0: # if 'mm2' in SytSym: @@ -3580,7 +3587,7 @@ def MagSytSym(SytSym,dupDir,SGData): # if "-6'"+'('+SplitSytSym[1] in dupDir: # MagSytSym = MagSytSym.replace('-6',"-6'") # return MagSytSym -# +# return SytSym def UpdateSytSym(Phase): @@ -3600,9 +3607,9 @@ def UpdateSytSym(Phase): atom[cs] = magSytSym atom[cs+1] = Mult return - + def ElemPosition(SGData): - ''' Under development. + ''' Under development. Object here is to return a list of symmetry element types and locations suitable for say drawing them. So far I have the element type... getting all possible locations without lookup may be impossible! @@ -3648,9 +3655,9 @@ def ElemPosition(SGData): print ('rotation',Es) X = [-1,-1,-1] SymElements.append([Es,X]) - + return SymElements - + def ApplyStringOps(A,SGData,X,Uij=[]): 'Needs a doc string' SGOps = SGData['SGOps'] @@ -3678,7 +3685,7 @@ def ApplyStringOps(A,SGData,X,Uij=[]): return [newX,newUij] else: return newX - + def ApplyStringOpsMom(A,SGData,SSGData,Mom): '''Applies string operations to modulated magnetic moment components used in drawing Drawing matches Bilbao MVISUALIZE @@ -3701,11 +3708,11 @@ def ApplyStringOpsMom(A,SGData,SSGData,Mom): if SSGData['SSGCen'][iAx//100][3]: #flip spin for BNS centered atoms newMom *= -1. return newMom - + def StringOpsProd(A,B,SGData): """ Find A*B where A & B are in strings '-' + '100*c+n' + '+ijk' - where '-' indicates inversion, c(>0) is the cell centering operator, + where '-' indicates inversion, c(>0) is the cell centering operator, n is operator number from SgOps and ijk are unit cell translations (each may be <0). Should return resultant string - C. SGData - dictionary using entries: @@ -3748,11 +3755,11 @@ def StringOpsProd(A,B,SGData): C = str(((nC+1)+(100*cC))*(1-2*iC))+'+'+ \ str(int(cellC[0]))+','+str(int(cellC[1]))+','+str(int(cellC[2])) return C - + def U2Uij(U): #returns the UIJ vector U11,U22,U33,U12,U13,U23 from tensor U return [U[0][0],U[1][1],U[2][2],U[0][1],U[0][2],U[1][2]] - + def Uij2U(Uij): #returns the thermal motion tensor U from Uij as numpy array return np.array([[Uij[0],Uij[3],Uij[4]],[Uij[3],Uij[1],Uij[5]],[Uij[4],Uij[5],Uij[2]]]) @@ -3794,9 +3801,9 @@ def StandardizeSpcName(spcgroup): else: # not found return '' - + def fullHM2shortHM(SpcGp): - ''' Accepts a full H-M space group symbol and returns a short H-M symbol that the space group + ''' Accepts a full H-M space group symbol and returns a short H-M symbol that the space group interpreter can translate ''' fields = SpcGp.split() @@ -3828,29 +3835,29 @@ def fullHM2shortHM(SpcGp): fields[3] = fields[3].split('/')[1] return ' '.join(fields) return SpcGp - + def offsetNorm(x): - '''Translate a coordinate (or vector of symmetry offsets) into + '''Translate a coordinate (or vector of symmetry offsets) into the range -1/6 < x <= 5/6, matching GSAS-II use. ''' negoff = 1/6 # negative offset return np.round(((x + negoff - 1e-9) % 1) - negoff + 1e-9, 8) - + def ParseXYZ(sym): - '''Parse a set of space group operations, such as '-x,-y,-z' - or 'x-y,z,-x' etc. into an algebraic form returning a - 3x3 matrix, M, and a length 3 vector, O, where the symmetry - operation can be applied as + '''Parse a set of space group operations, such as '-x,-y,-z' + or 'x-y,z,-x' etc. into an algebraic form returning a + 3x3 matrix, M, and a length 3 vector, O, where the symmetry + operation can be applied as - M . (x,y,z) + O + M . (x,y,z) + O - where M is a 3x3 matrix and O is a displacement vector. - This is the same form generated by :func:`SpcGroup`. + where M is a 3x3 matrix and O is a displacement vector. + This is the same form generated by :func:`SpcGroup`. - Note that this code will process offsets either before the coordinate + Note that this code will process offsets either before the coordinate multipliers or after, so that either of these - 1/2-y,1/2+x,z or + 1/2-y,1/2+x,z or -y+1/2,x-0.5,z will both be parsed properly. Returns None if a symbol cannot be parsed. @@ -3887,11 +3894,11 @@ def CompareSym(symList,sgName=None,SGData=None): with a list of operators from some other source. :param list symList: a list of symmetry operations (such as 'x,y,z' - or '-Y,X,Z'). The code is fairly forgiving in that 1/2-X or X-1/2 or - even -X+0.5 can all be accepted. This is typically read from a CIF. - :param str sgName: a space group name. Need not follow GSAS-II + or '-Y,X,Z'). The code is fairly forgiving in that 1/2-X or X-1/2 or + even -X+0.5 can all be accepted. This is typically read from a CIF. + :param str sgName: a space group name. Need not follow GSAS-II convention for spaces, etc (see :func:`GSASIIspc.StandardizeSpcName`) - :param SGData: a GSAS-II symmetry objectspace group name. Need not follow GSAS-II + :param SGData: a GSAS-II symmetry objectspace group name. Need not follow GSAS-II convention for spaces, etc (see :func:`GSASIIspc.StandardizeSpcName`) ''' if sgName: @@ -3904,7 +3911,11 @@ def CompareSym(symList,sgName=None,SGData=None): raise Exception('CompareSym called without symmetry input') for sym in symList: - syMat = ParseXYZ(sym) + try: + syMat = ParseXYZ(sym) + except: + print(f'Ignoring unparsable sym op {sym}') + continue for i,mat in enumerate(matList): if np.allclose(mat[0],syMat[0]) and np.allclose(offsetNorm(mat[1]),offsetNorm(syMat[1])): @@ -3917,7 +3928,7 @@ def CompareSym(symList,sgName=None,SGData=None): def SpaceGroupNumber(spcgroup): '''Determine the space group number for a space group from H-M name. - Will work for non-standard groups insofar as we can normalize -- far + Will work for non-standard groups insofar as we can normalize -- far from perfect. ''' SGNo = -1 @@ -3931,8 +3942,8 @@ def SpaceGroupNumber(spcgroup): return SGNo def GetHallSpaceGroup(SGData): - '''Determine the Hall space group symbol for a GSAS-II space group object, - if it exists (it will not if non-standard centering is used, perhaps also + '''Determine the Hall space group symbol for a GSAS-II space group object, + if it exists (it will not if non-standard centering is used, perhaps also for other cases). Will return None if not found. ''' try: @@ -3954,7 +3965,7 @@ def GetHallSpaceGroup(SGData): spgbyNum = [] '''Space groups indexed by number''' -if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter +if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter spgbyNum = [None, 'P 1','P -1', #1-2 'P 2','P 21','C 2','P m','P c','C m','C c','P 2/m','P 21/m', @@ -4013,7 +4024,7 @@ def GetHallSpaceGroup(SGData): altSettingOrtho = {} ''' A dictionary of alternate settings for orthorhombic unit cells ''' -if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter +if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter altSettingOrtho = { 'P 2 2 2' :{'abc':'P 2 2 2','cab':'P 2 2 2','bca':'P 2 2 2','acb':'P 2 2 2','bac':'P 2 2 2','cba':'P 2 2 2'}, 'P 2 2 21' :{'abc':'P 2 2 21','cab':'P 21 2 2','bca':'P 2 21 2','acb':'P 2 21 2','bac':'P 2 2 21','cba':'P 21 2 2'}, @@ -4076,10 +4087,10 @@ def GetHallSpaceGroup(SGData): 'I m m a':{'abc':'I m m a','cab':'I b m m','bca':'I m c m','acb':'I m a m','bac':'I m m b','cba':'I c m m'}, } spg2origins = {} -''' A dictionary of all spacegroups that have 2nd settings; the value is the +''' A dictionary of all spacegroups that have 2nd settings; the value is the 1st --> 2nd setting transformation vector as X(2nd) = X(1st)-V, nonstandard ones are included. ''' -if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter +if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter spg2origins = { 'P n n n':[-.25,-.25,-.25], 'P b a n':[-.25,-.25,0],'P n c b':[0,-.25,-.25],'P c n a':[-.25,0,-.25], @@ -4096,13 +4107,13 @@ def GetHallSpaceGroup(SGData): 'p n 3':[-.25,-.25,-.25],'F d 3':[-.125,-.125,-.125],'P n 3 n':[-.25,-.25,-.25], 'P n 3 m':[-.25,-.25,-.25],'F d 3 m':[-.125,-.125,-.125],'F d - c':[-.375,-.375,-.375]} spglist = {} -'''A dictionary of space groups as ordered and named in the pre-2002 International -Tables Volume A, except that spaces are used following the GSAS convention to +'''A dictionary of space groups as ordered and named in the pre-2002 International +Tables Volume A, except that spaces are used following the GSAS convention to separate the different crystallographic directions. -Note that the symmetry codes here will recognize many non-standard space group +Note that the symmetry codes here will recognize many non-standard space group symbols with different settings. They are ordered by Laue group ''' -if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter +if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter spglist = { 'P1' : ('P 1','P -1',), # 1-2 'C1' : ('C 1','C -1',), @@ -4205,7 +4216,7 @@ def GetHallSpaceGroup(SGData): ''' A dictionary of orthorhombic space groups that were renamed in the 2002 Volume A, along with the pre-2002 name. The e designates a double glide-plane ''' -if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter +if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter sgequiv_2002_orthorhombic = { 'AEM2':'A b m 2','B2EM':'B 2 c m','CM2E':'C m 2 a', 'AE2M':'A c 2 m','BME2':'B m a 2','C2ME':'C 2 m b', @@ -4217,18 +4228,18 @@ def GetHallSpaceGroup(SGData): 'CCCE':'C c c a','AEAA':'A b a a','BBEB':'B b c b'} spgHall = [] -'''Hall space group symbols indexed by GSAS-II space group name. This is indexed -by a key which is generated from the name used in GSAS-II with spaces removed -and all letters in lowercase. The value associated with the key consists of a +'''Hall space group symbols indexed by GSAS-II space group name. This is indexed +by a key which is generated from the name used in GSAS-II with spaces removed +and all letters in lowercase. The value associated with the key consists of a list of two values: the first is the Hall name and the second is a -Hermann-Mauguin name. Note that there may be several names used by +Hermann-Mauguin name. Note that there may be several names used by GSAS-II that map to the same symmetry operators and the same Hall symbol (for example "P 21" and "P 1 21 1" and "P 63/m" and "P 63/m H"). There are also space group names that GSAS-II will accept that do not have Hall -symbols (such as "I -1" or "F 21 21 21"). +symbols (such as "I -1" or "F 21 21 21"). ''' # generated from https://cci.lbl.gov/sginfo/hall_symbols.html by Sydney R. Hall & Ralf W. Grosse-Kunstleve -if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter +if 'sphinx' not in sys.modules: # skip if generating sphinx docs to unclutter spgHall = { 'p1':('P 1', 'P 1', 1), # P 1 'p-1':('-P 1', 'P -1', 2), # P -1 @@ -4770,203 +4781,13 @@ def GetHallSpaceGroup(SGData): 'im-3m':('-I 4 2 3', 'I m -3 m', 229), # I m -3 m 'ia-3d':('-I 4bd 2c 3', 'I a -3 d', 230), # I a -3 d } - + #'A few non-standard space groups for test use' -nonstandard_sglist = ('P 21 1 1','P 1 21 1','P 1 1 21','R 3 r','R 3 2 h', +nonstandard_sglist = ('P 21 1 1','P 1 21 1','P 1 1 21','R 3 r','R 3 2 h', 'R -3 r', 'R 3 2 r','R 3 m h', 'R 3 m r', 'R 3 c r','R -3 c r','R -3 m r',), -#Use the space groups types in this order to list the symbols in the +#Use the space groups types in this order to list the symbols in the #order they are listed in the International Tables, vol. A''' -symtypelist = ('triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', +symtypelist = ('triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'trigonal', 'hexagonal', 'cubic') - -# self-test materials follow. Requires files in directory testinp -selftestlist = [] -'''Defines a list of self-tests''' -selftestquiet = True -def _ReportTest(): - 'Report name and doc string of current routine when ``selftestquiet`` is False' - if not selftestquiet: - import inspect - caller = inspect.stack()[1][3] - doc = eval(caller).__doc__ - if doc is not None: - print(f'testing {__file__} with {caller} ({doc})') - else: - print(f'testing {__file__} with {caller}') -def test0(): - '''self-test #0: exercise MoveToUnitCell''' - _ReportTest() - msg = "MoveToUnitCell failed" - assert (MoveToUnitCell([1,2,3])[0] == [0,0,0]).all, msg - assert (MoveToUnitCell([2,-1,-2])[0] == [0,0,0]).all, msg - assert abs(MoveToUnitCell(np.array([-.1]))[0]-0.9)[0] < 1e-6, msg - assert abs(MoveToUnitCell(np.array([.1]))[0]-0.1)[0] < 1e-6, msg -selftestlist.append(test0) - -def test1(): - '''self-test #1: SpcGroup against previous results''' - #'''self-test #1: SpcGroup and SGPrint against previous results''' - _ReportTest() - testdir = ospath.join(ospath.split(ospath.abspath( __file__ ))[0],'testinp') - if ospath.exists(testdir): - if testdir not in sys.path: sys.path.insert(0,testdir) - import spctestinp - def CompareSpcGroup(spc, referr, refdict, reflist): - 'Compare output from GSASIIspc.SpcGroup with results from a previous run' - msg0 = "CompareSpcGroup failed on space group %s" % spc - result = SpcGroup(spc) - if result[0] == referr and referr > 0: return True - for key in refdict: - if key == 'SGCen' or key == 'SGOps': - assert len(refdict[key])==len(result[1][key]),f'{msg0}, {key} length' - else: - assert refdict[key]==result[1][key],f'{msg0}, key={key}' - key = 'SGCen' - indices = list(range(len(result[1][key]))) - for item in refdict[key]: - for i,j in enumerate(indices): - if np.allclose(result[1][key][j],item): - indices.pop(i) - break - else: - assert False,f'{msg0} no {key} matches center {item}' - key = 'SGOps' - indices = list(range(len(result[1][key]))) - for k,item in enumerate(refdict[key]): - for i,j in enumerate(indices): - if np.allclose(result[1][key][j][0],item[0]) and np.allclose(result[1][key][j][1],item[1]): - indices.pop(i) - break - else: - assert False,f'{msg0} no {key} matches sym op #{k}' - for spc in spctestinp.SGdat: - CompareSpcGroup(spc, 0, spctestinp.SGdat[spc], spctestinp.SGlist[spc] ) -selftestlist.append(test1) - -def test2(): - '''self-test #2: SpcGroup against cctbx (sgtbx) computations''' - _ReportTest() - testdir = ospath.join(ospath.split(ospath.abspath( __file__ ))[0],'testinp') - if ospath.exists(testdir): - if testdir not in sys.path: sys.path.insert(0,testdir) - import sgtbxtestinp - def CompareWcctbx(spcname, cctbx_in, debug=0): - 'Compare output from GSASIIspc.SpcGroup with results from cctbx.sgtbx' - cctbx = cctbx_in[:] # make copy so we don't delete from the original - spc = (SpcGroup(spcname))[1] - if debug: print (spc['SpGrp']) - if debug: print (spc['SGCen']) - latticetype = spcname.strip().upper()[0] - # lattice type of R implies Hexagonal centering", fix the rhombohedral settings - if latticetype == "R" and len(spc['SGCen']) == 1: latticetype = 'P' - assert latticetype == spc['SGLatt'], "Failed: %s does not match Lattice: %s" % (spcname, spc['SGLatt']) - onebar = [1] - if spc['SGInv']: onebar.append(-1) - for (op,off) in spc['SGOps']: - for inv in onebar: - for cen in spc['SGCen']: - noff = off + cen - noff = MoveToUnitCell(noff)[0] - mult = tuple((op*inv).ravel().tolist()) - if debug: print ("\n%s: %s + %s" % (spcname,mult,noff)) - for refop in cctbx: - if debug: print (refop) - # check the transform - if refop[:9] != mult: continue - if debug: print ("mult match") - # check the translation - reftrans = list(refop[-3:]) - reftrans = MoveToUnitCell(reftrans)[0] - if all(abs(noff - reftrans) < 1.e-5): - cctbx.remove(refop) - break - else: - assert False, "failed on %s:\n\t %s + %s" % (spcname,mult,noff) - for key in sgtbxtestinp.sgtbx: - CompareWcctbx(key, sgtbxtestinp.sgtbx[key]) -selftestlist.append(test2) - -def test3(): - '''self-test #3: exercise SytSym (includes GetOprPtrName, GenAtom, GetKNsym) - for selected space groups against info in IT Volume A ''' - _ReportTest() - def ExerciseSiteSym (spc, crdlist): - 'compare site symmetries and multiplicities for a specified space group' - msg = f"failed on site sym test for {spc}" - (E,S) = SpcGroup(spc) - assert not E, msg - for t in crdlist: - symb, m, n, od = SytSym(t[0],S) - if symb.strip() != t[2].strip(): - #GSASIIpath.IPyBreak_base() - print(f'for {spc} @ {t[0]}, site sym mismatch {symb} != {t[2]} (warning)') - assert m == t[1],f'{msg}: multiplicity @ {t[0]}' - #assert symb.strip() == t[2].strip() - - ExerciseSiteSym('p 1',[ - ((0.13,0.22,0.31),1,'1'), - ((0,0,0),1,'1'), - ]) - ExerciseSiteSym('p -1',[ - ((0.13,0.22,0.31),2,'1'), - ((0,0.5,0),1,'-1'), - ]) - ExerciseSiteSym('C 2/c',[ - ((0.13,0.22,0.31),8,'1'), - ((0.0,.31,0.25),4,'2(y)'), - ((0.25,.25,0.5),4,'-1'), - ((0,0.5,0),4,'-1'), - ]) - ExerciseSiteSym('p 2 2 2',[ - ((0.13,0.22,0.31),4,'1'), - ((0,0.5,.31),2,'2(z)'), - ((0.5,.31,0.5),2,'2(y)'), - ((.11,0,0),2,'2(x)'), - ((0,0.5,0),1,'222'), - ]) - ExerciseSiteSym('p 4/n',[ - ((0.13,0.22,0.31),8,'1'), - ((0.25,0.75,.31),4,'2(z)'), - ((0.5,0.5,0.5),4,'-1'), - ((0,0.5,0),4,'-1'), - ((0.25,0.25,.31),2,'4(z)'), - ((0.25,.75,0.5),2,'-4(z)'), - ((0.25,.75,0.0),2,'-4(z)'), - ]) - ExerciseSiteSym('p 31 2 1',[ - ((0.13,0.22,0.31),6,'1'), - ((0.13,0.0,0.833333333),3,'2(100)'), - ((0.13,0.13,0.),3,'2(110)'), - ]) - ExerciseSiteSym('R 3 c',[ - ((0.13,0.22,0.31),18,'1'), - ((0.0,0.0,0.31),6,'3'), - ]) - ExerciseSiteSym('R 3 c R',[ - ((0.13,0.22,0.31),6,'1'), - ((0.31,0.31,0.31),2,'3(111)'), - ]) - ExerciseSiteSym('P 63 m c',[ - ((0.13,0.22,0.31),12,'1'), - ((0.11,0.22,0.31),6,'m(100)'), - ((0.333333,0.6666667,0.31),2,'3m(100)'), - ((0,0,0.31),2,'3m(100)'), - ]) - ExerciseSiteSym('I a -3',[ - ((0.13,0.22,0.31),48,'1'), - ((0.11,0,0.25),24,'2(x)'), - ((0.11,0.11,0.11),16,'3(111)'), - ((0,0,0),8,'-3(111)'), - ]) -selftestlist.append(test3) - -if __name__ == '__main__': - import GSASIIpath - GSASIIpath.SetBinaryPath() - # run self-tests - selftestquiet = False - for test in selftestlist: - test() - print ("OK") diff --git a/GSASII/GSASIIstrIO.py b/GSASII/GSASIIstrIO.py index fa81e9c04..8a910d6b7 100644 --- a/GSASII/GSASIIstrIO.py +++ b/GSASII/GSASIIstrIO.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- ''' -:mod:`GSASIIstrIO` routines, used for refinement to -read from GPX files and print to the .LST file. -Used for refinements and in G2scriptable. +:mod:`GSASIIstrIO` routines, used for refinement to +read from GPX files and print to the .LST file. +Used for refinements and in G2scriptable. -This file should not contain any wxpython references as this -must be used in non-GUI settings. +This file should not contain any wxpython references as this +must be used in non-GUI settings. ''' from __future__ import division, print_function import re @@ -14,19 +14,20 @@ import time import math import random as rand +import shutil import copy -import pickle as cPickle +import pickle import numpy as np import numpy.ma as ma -import GSASIIpath -import GSASIIElem as G2el -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIobj as G2obj -import GSASIImapvars as G2mv -import GSASIImath as G2mth -import GSASIIstrMath as G2stMth -import GSASIIfiles as G2fil +from . import GSASIIpath +from . import GSASIIElem as G2el +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIobj as G2obj +from . import GSASIImapvars as G2mv +from . import GSASIImath as G2mth +from . import GSASIIstrMath as G2stMth +from . import GSASIIfiles as G2fil sind = lambda x: np.sin(x*np.pi/180.) cosd = lambda x: np.cos(x*np.pi/180.) @@ -34,47 +35,47 @@ asind = lambda x: 180.*np.arcsin(x)/np.pi acosd = lambda x: 180.*np.arccos(x)/np.pi atan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi - + ateln2 = 8.0*math.log(2.0) #=============================================================================== # Support for GPX file reading #=============================================================================== -def cPickleLoad(fp): - return cPickle.load(fp,encoding='latin-1') +def pickleLoad(fp): + return pickle.load(fp,encoding='latin-1') gpxIndex = {}; gpxNamelist = []; gpxSize = -1 -'''Global variables used in :func:`IndexGPX` to see if file has changed +'''Global variables used in :func:`IndexGPX` to see if file has changed (gpxSize) and to index where to find each 1st-level tree item in the file. ''' def GetFullGPX(GPXfile): - ''' Returns complete contents of GSASII gpx file. + ''' Returns complete contents of GSASII gpx file. Used in :func:`GSASIIscriptable.LoadDictFromProjFile`. :param str GPXfile: full .gpx file name :returns: Project,nameList, where - * Project (dict) is a representation of gpx file following the GSAS-II - tree structure for each item: key = tree name (e.g. 'Controls', - 'Restraints', etc.), data is dict + * Project (dict) is a representation of gpx file following the GSAS-II + tree structure for each item: key = tree name (e.g. 'Controls', + 'Restraints', etc.), data is dict * nameList (list) has names of main tree entries & subentries used to reconstruct project file ''' return IndexGPX(GPXfile,read=True) def IndexGPX(GPXfile,read=False): '''Create an index to a GPX file, optionally the file into memory. - The byte size of the GPX file is saved. If this routine is called + The byte size of the GPX file is saved. If this routine is called again, and if this size does not change, indexing is not repeated since it is assumed the file has not changed (this can be overriden - by setting read=True). + by setting read=True). :param str GPXfile: full .gpx file name :returns: Project,nameList if read=, where - * Project (dict) is a representation of gpx file following the GSAS-II - tree structure for each item: key = tree name (e.g. 'Controls', - 'Restraints', etc.), data is dict + * Project (dict) is a representation of gpx file following the GSAS-II + tree structure for each item: key = tree name (e.g. 'Controls', + 'Restraints', etc.), data is dict * nameList (list) has names of main tree entries & subentries used to reconstruct project file ''' global gpxSize @@ -91,7 +92,7 @@ def IndexGPX(GPXfile,read=False): try: while True: pos = fp.tell() - data = cPickleLoad(fp) + data = pickleLoad(fp) datum = data[0] gpxIndex[datum[0]] = pos if read: Project[datum[0]] = {'data':datum[1]} @@ -107,8 +108,8 @@ def IndexGPX(GPXfile,read=False): raise Exception("Error reading file "+str(GPXfile)+". This is not a GSAS-II .gpx file") finally: fp.close() - if read: return Project,gpxNamelist - + if read: return Project,gpxNamelist + def GetControls(GPXfile): ''' Returns dictionary of control items found in GSASII gpx file @@ -123,7 +124,7 @@ def GetControls(GPXfile): return Controls fp = open(GPXfile,'rb') fp.seek(pos) - datum = cPickleLoad(fp)[0] + datum = pickleLoad(fp)[0] fp.close() Controls.update(datum[1]) return Controls @@ -132,7 +133,7 @@ def ReadConstraints(GPXfile, seqHist=None): '''Read the constraints from the GPX file and interpret them called in :func:`ReadCheckConstraints`, :func:`GSASIIstrMain.Refine` - and :func:`GSASIIstrMain.SeqRefine`. + and :func:`GSASIIstrMain.SeqRefine`. ''' IndexGPX(GPXfile) fl = open(GPXfile,'rb') @@ -140,7 +141,7 @@ def ReadConstraints(GPXfile, seqHist=None): if pos is None: raise Exception("No constraints in GPX file") fl.seek(pos) - ConstraintsItem = cPickleLoad(fl)[0] + ConstraintsItem = pickleLoad(fl)[0] seqmode = 'use-all' if seqHist is not None: seqmode = ConstraintsItem[1].get('_seqmode','wildcards-only') @@ -153,17 +154,17 @@ def ReadConstraints(GPXfile, seqHist=None): #if ignored: # G2fil.G2Print ('Warning: {} Constraints were rejected. Was a constrained phase, histogram or atom deleted?'.format(ignored)) return constrDict,fixedList - + def ReadCheckConstraints(GPXfile, seqHist=None,Histograms=None,Phases=None): '''Load constraints and related info and return any error or warning messages This is done from the GPX file rather than the tree. :param str GPXfile: specifies the path to a .gpx file. - :param str seqHist: specifies a histogram to be loaded for + :param str seqHist: specifies a histogram to be loaded for a sequential refinement. If None (default) all are loaded. :param dict Histograms: output from :func:`GetUsedHistogramsAndPhases`, can optionally be supplied to save time for sequential refinements - :param dict Phases: output from :func:`GetUsedHistogramsAndPhases`, can + :param dict Phases: output from :func:`GetUsedHistogramsAndPhases`, can optionally be supplied to save time for sequential refinements ''' G2mv.InitVars() # init constraints @@ -200,7 +201,7 @@ def ReadCheckConstraints(GPXfile, seqHist=None,Histograms=None,Phases=None): errmsg,warnmsg,groups,parmlist = G2mv.GenerateConstraints(varyList,constrDict,fixedList,parmDict,seqHistNum=hId) G2mv.Map2Dict(parmDict,varyList) # changes varyList return errmsg, warnmsg - + def makeTwinFrConstr(Phases,Histograms,hapVary): TwConstr = [] TwFixed = [] @@ -216,8 +217,8 @@ def makeTwinFrConstr(Phases,Histograms,hapVary): TwConstr.append({phfx+'TwinFr:'+str(i):'1.0' for i in range(nTwin)}) except KeyError: #unused histograms? pass - return TwConstr,TwFixed - + return TwConstr,TwFixed + def GetRestraints(GPXfile): '''Read the restraints from the GPX file. Throws an exception if not found in the .GPX file @@ -228,10 +229,10 @@ def GetRestraints(GPXfile): if pos is None: raise Exception("No Restraints in GPX file") fl.seek(pos) - datum = cPickleLoad(fl)[0] + datum = pickleLoad(fl)[0] fl.close() return datum[1] - + def GetRigidBodies(GPXfile): '''Read the rigid body models from the GPX file ''' @@ -241,10 +242,10 @@ def GetRigidBodies(GPXfile): if pos is None: raise Exception("No Rigid bodies in GPX file") fl.seek(pos) - datum = cPickleLoad(fl)[0] + datum = pickleLoad(fl)[0] fl.close() return datum[1] - + def GetFprime(controlDict,Histograms): 'Needs a doc string' FFtables = controlDict['FFtables'] @@ -268,7 +269,7 @@ def GetFprime(controlDict,Histograms): FP,FPP,Mu = G2el.FPcalc(Orbs, keV) FFtables[El][hfx+'FP'] = FP FFtables[El][hfx+'FPP'] = FPP - + def PrintFprime(FFtables,pfx,pFile): pFile.write('\n Resonant form factors:(ref: D.T. Cromer & D.A. Liberman (1981), Acta Cryst. A37, 267-268.)\n') Elstr = ' Element:' @@ -282,7 +283,7 @@ def PrintFprime(FFtables,pfx,pFile): pFile.write(Elstr+'\n') pFile.write(FPstr+'\n') pFile.write(FPPstr+'\n') - + def PrintBlength(BLtables,wave,pFile): pFile.write('\n Resonant neutron scattering lengths:\n') Elstr = ' Element:' @@ -291,9 +292,9 @@ def PrintBlength(BLtables,wave,pFile): for El in BLtables: if 'Q' not in El: BP,BPP = G2el.BlenResCW([El,],BLtables,wave) - Elstr += ' %8s'%(El) - FPstr += ' %8.3f'%(BP) - FPPstr += ' %8.3f'%(BPP) + Elstr += f' {El:>8}' + FPstr += f' {BP[0]:8.3f}' + FPPstr += f' {BPP[0]:8.3f}' pFile.write(Elstr+'\n') pFile.write(FPstr+'\n') pFile.write(FPPstr+'\n') @@ -307,7 +308,7 @@ def PrintISOmodes(pFile,Phases,parmDict,sigDict): data = Phases[phase] ISO = data['ISODISTORT'] atNames = [atom[0] for atom in data['Atoms']] - + if 'G2VarList' in ISO: deltaList = [] notfound = [] @@ -337,7 +338,7 @@ def PrintISOmodes(pFile,Phases,parmDict,sigDict): print(' ',i,'({})'.format(j)) continue modeVals = np.inner(ISO['Var2ModeMatrix'],deltaList) - + pFile.write('\n ISODISTORT Displacive Modes for phase {}\n'.format(data['General'].get('Name',''))) l = str(max([len(i) for i in ISO['IsoModeList']])+3) fmt = ' {:'+l+'}{}' @@ -352,7 +353,7 @@ def PrintISOmodes(pFile,Phases,parmDict,sigDict): except TypeError: value = '?' pFile.write(fmt.format(var,value)+'\n') - + if 'G2OccVarList' in ISO: #untested - probably wrong deltaOccList = [] notfound = [] @@ -380,7 +381,7 @@ def PrintISOmodes(pFile,Phases,parmDict,sigDict): print(' ',i,'({})'.format(j)) continue modeOccVals = np.inner(ISO['Var2OccMatrix'],deltaOccList) - + pFile.write('\n ISODISTORT Occupancy Modes for phase {}\n'.format(data['General'].get('Name',''))) l = str(max([len(i) for i in ISO['OccModeList']])+3) fmt = ' {:'+l+'}{}' @@ -393,7 +394,7 @@ def PrintISOmodes(pFile,Phases,parmDict,sigDict): except TypeError: value = '?' pFile.write(fmt.format(var,value)+'\n') - + def PrintIndependentVars(parmDict,varyList,sigDict,PrintAll=False,pFile=None): '''Print the values and uncertainties on the independent parameters''' printlist = [] @@ -428,7 +429,7 @@ def PrintIndependentVars(parmDict,varyList,sigDict,PrintAll=False,pFile=None): s3 += ('%15s' % ('n/a')).center(wdt) else: s3 += fmtESD(name,sigDict,'f',15,5).center(wdt) - + def GetPhaseNames(GPXfile): ''' Returns a list of phase names found under 'Phases' in GSASII gpx file @@ -441,7 +442,7 @@ def GetPhaseNames(GPXfile): if pos is None: raise Exception("No Phases in GPX file") fl.seek(pos) - data = cPickleLoad(fl) + data = pickleLoad(fl) fl.close() return [datus[0] for datus in data[1:]] @@ -451,20 +452,20 @@ def GetAllPhaseData(GPXfile,PhaseName): :param str GPXfile: full .gpx file name :param str PhaseName: phase name :return: phase dictionary or None if PhaseName is not present - ''' + ''' IndexGPX(GPXfile) fl = open(GPXfile,'rb') pos = gpxIndex.get('Phases') if pos is None: raise Exception("No Phases in GPX file") fl.seek(pos) - data = cPickleLoad(fl) + data = pickleLoad(fl) fl.close() for datus in data[1:]: if datus[0] == PhaseName: return datus[1] - + def GetHistograms(GPXfile,hNames): """ Returns a dictionary of histograms found in GSASII gpx file @@ -481,7 +482,7 @@ def GetHistograms(GPXfile,hNames): if pos is None: raise Exception("Histogram {} not found in GPX file".format(hist)) fl.seek(pos) - data = cPickleLoad(fl) + data = pickleLoad(fl) datum = data[0] if 'PWDR' in hist[:4]: PWDRdata = {} @@ -514,13 +515,13 @@ def GetHistograms(GPXfile,hNames): HKLFdata['Instrument Parameters'] = dict(data)['Instrument Parameters'] HKLFdata['Reflection Lists'] = None HKLFdata['Residuals'] = {} - Histograms[hist] = HKLFdata + Histograms[hist] = HKLFdata fl.close() return Histograms - + def GetHistogramNames(GPXfile,hTypes): """ Returns a list of histogram names found in a GSAS-II .gpx file that - match specifed histogram types. Names are returned in the order they + match specifed histogram types. Names are returned in the order they appear in the file. :param str GPXfile: full .gpx file name @@ -530,12 +531,12 @@ def GetHistogramNames(GPXfile,hTypes): """ IndexGPX(GPXfile) return [n[0] for n in gpxNamelist if n[0][:4] in hTypes] - + def GetUsedHistogramsAndPhases(GPXfile): ''' Returns all histograms that are found in any phase and any phase that uses a histogram. This also assigns numbers to used phases and histograms by the - order they appear in the file. + order they appear in the file. :param str GPXfile: full .gpx file name :returns: (Histograms,Phases) @@ -548,7 +549,7 @@ def GetUsedHistogramsAndPhases(GPXfile): histoList = GetHistogramNames(GPXfile,['PWDR','HKLF']) allHistograms = GetHistograms(GPXfile,histoList) phaseData = {} - for name in phaseNames: + for name in phaseNames: phaseData[name] = GetAllPhaseData(GPXfile,name) Histograms = {} Phases = {} @@ -592,16 +593,16 @@ def GetUsedHistogramsAndPhases(GPXfile): .format(hist,fixedBkg[0])) G2obj.IndexAllIds(Histograms=Histograms,Phases=Phases) return Histograms,Phases - + def getBackupName(GPXfile,makeBack): ''' Get the name for the backup .gpx file name - + :param str GPXfile: full .gpx file name :param bool makeBack: if True the name of a new file is returned, if False the name of the last file that exists is returned :returns: the name of a backup file - + ''' GPXpath,GPXname = ospath.split(GPXfile) if GPXpath == '': GPXpath = '.' @@ -616,32 +617,31 @@ def getBackupName(GPXfile,makeBack): else: last = max(last,int(name[1].strip('bak'))) GPXback = ospath.join(GPXpath,ospath.splitext(GPXname)[0]+'.bak'+str(last)+'.gpx') - return GPXback - + return GPXback + def GPXBackup(GPXfile,makeBack=True): ''' makes a backup of the specified .gpx file - + :param str GPXfile: full .gpx file name :param bool makeBack: if True (default), the backup is written to a new file; if False, the last backup is overwritten :returns: the name of the backup file that was written ''' - import distutils.file_util as dfu GPXback = getBackupName(GPXfile,makeBack) tries = 0 while True: try: - dfu.copy_file(GPXfile,GPXback) + shutil.copy(GPXfile,GPXback) break except: tries += 1 if tries > 10: return GPXfile #failed! - time.sleep(1) #just wait a second! + time.sleep(1) #just wait a second! return GPXback -def SetUsedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData,parmFrozenList,makeBack=True): +def SaveUsedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData,parmFrozenList,makeBack=True): ''' Updates gpxfile from all histograms that are found in any phase and any phase that used a histogram. Also updates rigid body definitions. This is used for non-sequential fits, but not for sequential fitting. @@ -651,21 +651,20 @@ def SetUsedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData,par :param dict Phases: dictionary of phases that use histograms :param dict RigidBodies: dictionary of rigid bodies :param dict CovData: dictionary of refined variables, varyList, & covariance matrix - :param list parmFrozenList: list of parameters (as str) that are frozen + :param list parmFrozenList: list of parameters (as str) that are frozen due to limits; converted to :class:`GSASIIobj.G2VarObj` objects. - :param bool makeBack: True if new backup of .gpx file is to be made; else + :param bool makeBack: True if new backup of .gpx file is to be made; else use the last one made ''' - - import distutils.file_util as dfu + GPXback = GPXBackup(GPXfile,makeBack) - G2fil.G2Print ('Read from file:'+GPXback) - G2fil.G2Print ('Save to file :'+GPXfile) + G2fil.G2Print (f'Read from file: {GPXback}') + G2fil.G2Print (f'Save to file: {GPXfile}') infile = open(GPXback,'rb') outfile = open(GPXfile,'wb') while True: try: - data = cPickleLoad(infile) + data = pickleLoad(infile) except EOFError: break datum = data[0] @@ -681,6 +680,9 @@ def SetUsedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData,par data[0][1] = RigidBodies elif datum[0] == 'Controls': Controls = data[0][1] + # if a LeBail fit has been done, no need to ask again about + # resetting intensities + Controls['newLeBail'] = False if 'parmFrozen' not in Controls: Controls['parmFrozen'] = {} Controls['parmFrozen']['FrozenList'] = [i if type(i) is G2obj.G2VarObj @@ -697,31 +699,31 @@ def SetUsedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData,par if datus[0] == 'Background': # remove fixed background from file d1 = {key:histogram['Background'][1][key] for key in histogram['Background'][1] - if not key.startswith('_fixed')} + if not key.startswith('_fixed')} datus[1] = copy.deepcopy(histogram['Background']) datus[1][1] = d1 except KeyError: pass - try: - cPickle.dump(data,outfile,1) + try: + pickle.dump(data,outfile,1) except AttributeError: G2fil.G2Print ('ERROR - bad data in least squares result') infile.close() outfile.close() - dfu.copy_file(GPXback,GPXfile) + shutil.copy(GPXback,GPXfile) G2fil.G2Print ('GPX file save failed - old version retained',mode='error') return - + infile.close() outfile.close() - + G2fil.G2Print ('GPX file save successful') - + def GetSeqResult(GPXfile): ''' Returns the sequential results table information from a GPX file. Called at the beginning of :meth:`GSASIIstrMain.SeqRefine` - + :param str GPXfile: full .gpx file name :returns: a dict containing the sequential results table ''' @@ -731,12 +733,12 @@ def GetSeqResult(GPXfile): return {} fl = open(GPXfile,'rb') fl.seek(pos) - datum = cPickleLoad(fl)[0] + datum = pickleLoad(fl)[0] fl.close() return datum[1] - + def SetupSeqSavePhases(GPXfile): - '''Initialize the files used to save intermediate results from + '''Initialize the files used to save intermediate results from sequential fits. ''' IndexGPX(GPXfile) @@ -746,12 +748,12 @@ def SetupSeqSavePhases(GPXfile): if pos is None: raise Exception("No Phases in GPX file") fl.seek(pos) - data = cPickleLoad(fl) + data = pickleLoad(fl) fl.close() # create GPX-like file to store latest Phase info; init with start vals GPXphase = os.path.splitext(GPXfile)[0]+'.seqPhase' fp = open(GPXphase,'wb') - cPickle.dump(data,fp,1) + pickle.dump(data,fp,1) fp.close() # create empty file for histogram info GPXhist = os.path.splitext(GPXfile)[0]+'.seqHist' @@ -761,7 +763,7 @@ def SetupSeqSavePhases(GPXfile): def SaveUpdatedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData,parmFrozen): ''' Save phase and histogram information into "pseudo-gpx" files. The phase - information is overwritten each time this is called, but histogram information is + information is overwritten each time this is called, but histogram information is appended after each sequential step. :param str GPXfile: full .gpx file name @@ -772,24 +774,24 @@ def SaveUpdatedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData :param dict parmFrozen: dict with frozen parameters for all phases and histograms (specified as str values) ''' - + GPXphase = os.path.splitext(GPXfile)[0]+'.seqPhase' fp = open(GPXphase,'rb') - data = cPickleLoad(fp) # first block in file should be Phases + data = pickleLoad(fp) # first block in file should be Phases if data[0][0] != 'Phases': raise Exception('Unexpected block in {} file. How did this happen?' .format(GPXphase)) fp.close() # update previous phase info - for datum in data[1:]: + for datum in data[1:]: if datum[0] in Phases: datum[1].update(Phases[datum[0]]) # save latest Phase/refinement info fp = open(GPXphase,'wb') - cPickle.dump(data,fp,1) - cPickle.dump([['Covariance',CovData]],fp,1) - cPickle.dump([['Rigid bodies',RigidBodies]],fp,1) - cPickle.dump([['parmFrozen',parmFrozen]],fp,1) + pickle.dump(data,fp,1) + pickle.dump([['Covariance',CovData]],fp,1) + pickle.dump([['Rigid bodies',RigidBodies]],fp,1) + pickle.dump([['parmFrozen',parmFrozen]],fp,1) fp.close() # create an entry that looks like a PWDR tree item for key in Histograms: @@ -812,11 +814,11 @@ def SaveUpdatedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData if key == 'Background': # remove fixed background from file xfer_dict['Background'][1] = {k:hist['Background'][1][k] for k in hist['Background'][1] - if not k.startswith('_fixed')} + if not k.startswith('_fixed')} del hist[key] # xform into a gpx-type entry data = [] - data.append([histname,[hist,histData,histname]]) + data.append([histname,[hist,histData,histname]]) for key in ['Comments','Limits','Background','Instrument Parameters', 'Sample Parameters','Peak List','Index Peak List', 'Unit Cells List','Reflection Lists']: @@ -824,32 +826,32 @@ def SaveUpdatedHistogramsAndPhases(GPXfile,Histograms,Phases,RigidBodies,CovData # append histogram to histogram info GPXhist = os.path.splitext(GPXfile)[0]+'.seqHist' fp = open(GPXhist,'ab') - cPickle.dump(data,fp,1) + pickle.dump(data,fp,1) fp.close() return - + def SetSeqResult(GPXfile,Histograms,SeqResult): ''' Places the sequential results information into a GPX file - after a refinement has been completed. + after a refinement has been completed. Called at the end of :meth:`GSASIIstrMain.SeqRefine` :param str GPXfile: full .gpx file name ''' GPXback = GPXBackup(GPXfile) - G2fil.G2Print ('Read from file:'+GPXback) - G2fil.G2Print ('Save to file :'+GPXfile) + G2fil.G2Print (f'Read from file: {GPXback}') + G2fil.G2Print (f'Save to file: {GPXfile}') GPXphase = os.path.splitext(GPXfile)[0]+'.seqPhase' fp = open(GPXphase,'rb') - data = cPickleLoad(fp) # first block in file should be Phases + data = pickleLoad(fp) # first block in file should be Phases if data[0][0] != 'Phases': raise Exception('Unexpected block in {} file. How did this happen?'.format(GPXphase)) Phases = {} for name,vals in data[1:]: - Phases[name] = vals - name,CovData = cPickleLoad(fp)[0] # 2nd block in file should be Covariance - name,RigidBodies = cPickleLoad(fp)[0] # 3rd block in file should be Rigid Bodies - name,parmFrozenDict = cPickleLoad(fp)[0] # 4th block in file should be frozen parameters + Phases[name] = vals + name,CovData = pickleLoad(fp)[0] # 2nd block in file should be Covariance + name,RigidBodies = pickleLoad(fp)[0] # 3rd block in file should be Rigid Bodies + name,parmFrozenDict = pickleLoad(fp)[0] # 4th block in file should be frozen parameters fp.close() GPXhist = os.path.splitext(GPXfile)[0]+'.seqHist' hist = open(GPXhist,'rb') @@ -858,7 +860,7 @@ def SetSeqResult(GPXfile,Histograms,SeqResult): while True: loc = hist.tell() try: - datum = cPickleLoad(hist)[0] + datum = pickleLoad(hist)[0] except EOFError: break histIndex[datum[0]] = loc @@ -867,7 +869,7 @@ def SetSeqResult(GPXfile,Histograms,SeqResult): outfile = open(GPXfile,'wb') while True: try: - data = cPickleLoad(infile) + data = pickleLoad(infile) except EOFError: break datum = data[0] @@ -891,7 +893,7 @@ def SetSeqResult(GPXfile,Histograms,SeqResult): for i in parmFrozenDict[key]] elif datum[0] in histIndex: hist.seek(histIndex[datum[0]]) - hdata = cPickleLoad(hist) + hdata = pickleLoad(hist) if data[0][0] != hdata[0][0]: G2fil.G2Print('Error! Updating {} with {}'.format(data[0][0],hdata[0][0])) data[0] = hdata[0] @@ -900,7 +902,7 @@ def SetSeqResult(GPXfile,Histograms,SeqResult): for j,(name,val) in enumerate(data[1:]): if name not in xferItems: continue data[j+1][1] = hdata[hItems[name]][1] - cPickle.dump(data,outfile,1) + pickle.dump(data,outfile,1) hist.close() infile.close() outfile.close() @@ -947,7 +949,7 @@ def ShowControls(Controls,pFile=None,SeqRef=False,preFrozenCount=0): pFile.write(' Process histograms in reverse order: %s\n'%(Controls['Reverse Seq'])) if preFrozenCount: pFile.write('\n Starting refinement with {} Frozen variables\n\n'.format(preFrozenCount)) - + def GetPawleyConstr(SGLaue,PawleyRef,im,pawleyVary): 'needs a doc string' # if SGLaue in ['-1','2/m','mmm']: @@ -966,34 +968,34 @@ def GetPawleyConstr(SGLaue,PawleyRef,im,pawleyVary): isum = ih**2+ik**2 jsum = jh**2+jk**2 if abs(il) == abs(jl) and isum == jsum: - eqvDict[varyI].append(varyJ) + eqvDict[varyI].append(varyJ) elif SGLaue in ['3R','3mR']: isum = ih**2+ik**2+il**2 jsum = jh**2+jk**2+jl**2 isum2 = ih*ik+ih*il+ik*il jsum2 = jh*jk+jh*jl+jk*jl if isum == jsum and isum2 == jsum2: - eqvDict[varyI].append(varyJ) + eqvDict[varyI].append(varyJ) elif SGLaue in ['3','3m1','31m','6/m','6/mmm']: isum = ih**2+ik**2+ih*ik jsum = jh**2+jk**2+jh*jk if abs(il) == abs(jl) and isum == jsum: - eqvDict[varyI].append(varyJ) + eqvDict[varyI].append(varyJ) elif SGLaue in ['m3','m3m']: isum = ih**2+ik**2+il**2 jsum = jh**2+jk**2+jl**2 if isum == jsum: eqvDict[varyI].append(varyJ) elif abs(dspI-dspJ)/dspI < 1.e-4: - eqvDict[varyI].append(varyJ) + eqvDict[varyI].append(varyJ) for item in pawleyVary: if eqvDict[item]: for item2 in pawleyVary: if item2 in eqvDict[item]: eqvDict[item2] = [] G2mv.StoreEquivalence(item,eqvDict[item]) - -def cellVary(pfx,SGData): + +def cellVary(pfx,SGData): '''Creates equivalences for a phase based on the Laue class. Returns a list of A tensor terms that are non-zero. ''' @@ -1017,25 +1019,25 @@ def cellVary(pfx,SGData): elif SGData['SGLaue'] in ['3R', '3mR']: G2mv.StoreEquivalence(pfx+'A0',(pfx+'A1',pfx+'A2',)) G2mv.StoreEquivalence(pfx+'A3',(pfx+'A4',pfx+'A5',)) - return [pfx+'A0',pfx+'A1',pfx+'A2',pfx+'A3',pfx+'A4',pfx+'A5'] + return [pfx+'A0',pfx+'A1',pfx+'A2',pfx+'A3',pfx+'A4',pfx+'A5'] elif SGData['SGLaue'] in ['m3m','m3']: G2mv.StoreEquivalence(pfx+'A0',(pfx+'A1',pfx+'A2',)) return [pfx+'A0',pfx+'A1',pfx+'A2'] - + def modVary(pfx,SSGData): vary = [] for i,item in enumerate(SSGData['modSymb']): if item in ['a','b','g']: vary.append(pfx+'mV%d'%(i)) return vary - + ################################################################################ ##### Rigid Body Models and not General.get('doPawley') ################################################################################ - + def GetRigidBodyModels(rigidbodyDict,Print=True,pFile=None): '''Get Rigid body info from tree entry and print it to .LST file - Adds variables and dict items for vector RBs, but for Residue bodies + Adds variables and dict items for vector RBs, but for Residue bodies this is done in :func:`GetPhaseData`. ''' def PrintSpnRBModel(RBModel): @@ -1047,7 +1049,7 @@ def PrintResRBModel(RBModel): (RBModel['RBname'],len(RBModel['rbTypes']),RBModel['useCount'])) for i in WriteResRBModel(RBModel): pFile.write(i) - + def PrintVecRBModel(RBModel): pFile.write('Vector RB name: %s no.atoms: %d, No. times used: %d\n'% (RBModel['RBname'],len(RBModel['rbTypes']),RBModel['useCount'])) @@ -1055,7 +1057,7 @@ def PrintVecRBModel(RBModel): pFile.write(i) pFile.write('Orientation defined by: atom %s -> atom %s & atom %s -> atom %s\n'% (RBModel['rbRef'][0],RBModel['rbRef'][1],RBModel['rbRef'][0],RBModel['rbRef'][2])) - + if Print and pFile is None: raise Exception("specify pFile or Print=False") rbVary = [] rbDict = {} @@ -1066,7 +1068,7 @@ def PrintVecRBModel(RBModel): if Print: pFile.write('\nSpinning rigid body model:\n') PrintSpnRBModel(rigidbodyDict['Spin'][item]) - + if len(rbIds['Vector']): for irb,item in enumerate(rbIds['Vector']): if rigidbodyDict['Vector'][item]['useCount']: @@ -1087,10 +1089,10 @@ def PrintVecRBModel(RBModel): pFile.write('\nResidue rigid body model:\n') PrintResRBModel(rigidbodyDict['Residue'][item]) return rbVary,rbDict - + def SetRigidBodyModels(parmDict,sigDict,rigidbodyDict,pFile=None): 'needs a doc string' - + def PrintRBVectandSig(VectRB,VectSig): pFile.write('\n Rigid body vector magnitudes for %s:\n'%VectRB['RBname']) namstr = ' names :' @@ -1118,19 +1120,19 @@ def PrintRBVectandSig(VectRB,VectSig): name = '::RBV;'+str(i)+':'+str(irb) if name in sigDict: VectSig.append(sigDict[name]) - PrintRBVectandSig(rigidbodyDict['Vector'][item],VectSig) - + PrintRBVectandSig(rigidbodyDict['Vector'][item],VectSig) + ################################################################################ ##### Phase data -################################################################################ +################################################################################ def GetPhaseData(PhaseData,RestraintDict={},rbIds={},Print=True,pFile=None, seqHistName=None,symHold=None): - '''Setup the phase information for a structural refinement, used for - regular and sequential refinements, optionally printing information - to the .lst file (if Print is True). Used as part of refinements but also - to generate information on refinement settings. Can be used with dicts from - data tree or read from the GPX file. - Note that this routine shares a name with routine G2frame.GetPhaseData() + '''Setup the phase information for a structural refinement, used for + regular and sequential refinements, optionally printing information + to the .lst file (if Print is True). Used as part of refinements but also + to generate information on refinement settings. Can be used with dicts from + data tree or read from the GPX file. + Note that this routine shares a name with routine G2frame.GetPhaseData() (:meth:`GSASIIdata.GSASII.GetPhaseData`) that instead returns the phase dict(s) from the tree. @@ -1138,7 +1140,7 @@ def GetPhaseData(PhaseData,RestraintDict={},rbIds={},Print=True,pFile=None, .gpx file) with information on all phases :param dict RestraintDict: an optional dict with restraint information :param dict rbIds: an optional dict with rigid body information - :param bool Print: a flag that determines if information will be formatted and + :param bool Print: a flag that determines if information will be formatted and printed to the .lst file :param file pFile: a file object (created by open) where print information is sent when Print is True @@ -1147,12 +1149,12 @@ def GetPhaseData(PhaseData,RestraintDict={},rbIds={},Print=True,pFile=None, name is supplied, only the phases used in the current histogram are loaded. If 'All' is specified, all phases are loaded (used for error checking). :param list symHold: if not None (None is the default) the names of parameters - held due to symmetry are placed in this list even if not varied. (Used + held due to symmetry are placed in this list even if not varied. (Used in G2constrGUI and for parameter impact estimates in AllPrmDerivs). :returns: lots of stuff: Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup, - FFtables,EFtables,ORBtables,BLtables,MFtables,maxSSwave (see code for details). + FFtables,EFtables,ORBtables,BLtables,MFtables,maxSSwave (see code for details). ''' - + def PrintFFtable(FFtable): pFile.write('\n X-ray scattering factors:\n') pFile.write(' Symbol fa fb fc\n') @@ -1164,7 +1166,7 @@ def PrintFFtable(FFtable): fb = ffdata['fb'] pFile.write(' %8s %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f\n'% (Ename.ljust(8),fa[0],fa[1],fa[2],fa[3],fb[0],fb[1],fb[2],fb[3],ffdata['fc'])) - + def PrintEFtable(EFtable): pFile.write('\n Electron scattering factors:\n') pFile.write(' Symbol fa fb\n') @@ -1176,7 +1178,7 @@ def PrintEFtable(EFtable): fb = efdata['fb'] pFile.write(' %8s %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f\n'% (Ename.ljust(8),fa[0],fa[1],fa[2],fa[3],fa[4],fb[0],fb[1],fb[2],fb[3],fb[4])) - + def PrintORBtable(ORBtable): if not len(ORBtable): return @@ -1215,7 +1217,7 @@ def PrintMFtable(MFtable): fb = mfdata['nfb'] pFile.write(' %8s %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f\n'% (Ename.ljust(8),fa[0],fa[1],fa[2],fa[3],fb[0],fb[1],fb[2],fb[3],mfdata['nfc'])) - + def PrintBLtable(BLtable): pFile.write('\n Neutron scattering factors:\n') pFile.write(' Symbol isotope mass b resonant terms\n') @@ -1235,9 +1237,9 @@ def PrintBLtable(BLtable): for item in bres: line += '%10.5g'%(item) pFile.write(line+'\n') - + def PrintRBObjects(resRBData,vecRBData,spnRBData): - + def PrintRBThermals(): tlstr = ['11','22','33','12','13','23'] sstr = ['12','13','21','23','31','32','AA','BB'] @@ -1249,7 +1251,7 @@ def PrintRBThermals(): for i in range(6): text += 'T'+tlstr[i]+' %8.4f %s '%(TLS[i],str(TLSvar[i])[0]) pFile.write(text+'\n') - if 'L' in RB['ThermalMotion'][0]: + if 'L' in RB['ThermalMotion'][0]: text = '' for i in range(6,12): text += 'L'+tlstr[i-6]+' %8.2f %s '%(TLS[i],str(TLSvar[i])[0]) @@ -1261,9 +1263,9 @@ def PrintRBThermals(): pFile.write(text+'\n') if 'U' in RB['ThermalMotion'][0]: pFile.write('Uiso data\n') - text = 'Uiso'+' %10.3f %s'%(TLS[0],str(TLSvar[0])[0]) + text = 'Uiso'+' %10.3f %s'%(TLS[0],str(TLSvar[0])[0]) pFile.write(text+'\n') - + if len(resRBData): for RB in resRBData: Oxyz = RB['Orig'][0] @@ -1281,7 +1283,7 @@ def PrintRBThermals(): text += '%10.4f Refine? %s'%(torsion[0],torsion[1]) pFile.write(text+'\n') PrintRBThermals() - + if len(vecRBData): for RB in vecRBData: Oxyz = RB['Orig'][0] @@ -1293,7 +1295,7 @@ def PrintRBThermals(): (Angle,Qrijk[1],Qrijk[2],Qrijk[3],RB['Orient'][1])) pFile.write('Atom site frac: %10.3f Refine? %s\n'%(RB['AtomFrac'][0],RB['AtomFrac'][1])) PrintRBThermals() - + if len(spnRBData): for RB in spnRBData: atId = RB['Ids'][0] @@ -1318,7 +1320,7 @@ def PrintRBThermals(): if not block and 'Q' not in RB['atType']: ptlbls = ' names :%12s'%'Radius' ptstr = ' values:%12.4f'%RB['Radius'][ish][0] - ptref = ' refine:%12s'%RB['Radius'][ish][1] + ptref = ' refine:%12s'%RB['Radius'][ish][1] else: ptlbls = ' names :' ptstr = ' values:' @@ -1332,7 +1334,7 @@ def PrintRBThermals(): pFile.write(ptref+'\n') iBeg += 6 iFin = min(iBeg+6,nCoeff) - + def PrintAtoms(General,Atoms): cx,ct,cs,cia = General['AtomPtrs'] pFile.write('\n Atoms:\n') @@ -1354,7 +1356,7 @@ def PrintAtoms(General,Atoms): line += '%8.5f'%(at[cia+2+j]) pFile.write(line+'\n') elif General['Type'] == 'macromolecular': - pFile.write(135*'-'+'\n') + pFile.write(135*'-'+'\n') for i,at in enumerate(Atoms): line = '%7s'%(at[0])+'%7s'%(at[1])+'%7s'%(at[2])+'%7s'%(at[ct-1])+'%7s'%(at[ct])+'%7s'%(at[ct+1])+'%10.5f'%(at[cx])+'%10.5f'%(at[cx+1])+ \ '%10.5f'%(at[cx+2])+'%8.3f'%(at[cx+3])+'%7s'%(at[cs])+'%5d'%(at[cs+1])+'%5s'%(at[cia]) @@ -1365,7 +1367,7 @@ def PrintAtoms(General,Atoms): for j in range(6): line += '%8.4f'%(at[cia+2+j]) pFile.write(line+'\n') - + def PrintMoments(General,Atoms): cx,ct,cs,cia = General['AtomPtrs'] cmx = cx+4 @@ -1379,7 +1381,7 @@ def PrintMoments(General,Atoms): line = '%7s'%(at[ct-1])+'%7s'%(at[ct])+'%7s'%(at[ct+1])+'%10.5f'%(at[cmx])+'%10.5f'%(at[cmx+1])+ \ '%10.5f'%(at[cmx+2]) pFile.write(line+'\n') - + def PrintDeformations(General,Atoms,Deformations): cx,ct,cs,cia = General['AtomPtrs'] pFile.write('\n Atomic deformation parameters:\n') @@ -1426,11 +1428,11 @@ def PrintWaves(General,Atoms): (at[ct-1],at[cs],Stype,Waves[0])) else: continue - for iw,wave in enumerate(Waves[1:]): + for iw,wave in enumerate(Waves[1:]): line = '' if Waves[0] in ['Block','ZigZag'] and Stype == 'Spos' and not iw: for item in names[Stype][6:]: - line += '%8s '%(item) + line += '%8s '%(item) else: if Stype == 'Spos': for item in names[Stype][:6]: @@ -1444,7 +1446,7 @@ def PrintWaves(General,Atoms): line += '%8.4f '%(item) line += ' Refine? '+str(wave[1]) pFile.write(line+'\n') - + def PrintTexture(textureData): topstr = '\n Spherical harmonics texture: Order:' + \ str(textureData['Order']) @@ -1470,12 +1472,12 @@ def PrintTexture(textureData): ptstr = ' values:' for item in SHkeys[iBeg:iFin]: ptlbls += '%12s'%(item) - ptstr += '%12.4f'%(SHcoeff[item]) + ptstr += '%12.4f'%(SHcoeff[item]) pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') iBeg += 10 iFin = min(iBeg+10,nCoeff) - + def MakeRBParms(rbKey,phaseVary,phaseDict): # patch 2/24/21 BHT: new param, AtomFrac in RB if 'AtomFrac' not in RB and rbKey != 'S': raise Exception('out of date RB: edit in RB Models') @@ -1493,14 +1495,14 @@ def MakeRBParms(rbKey,phaseVary,phaseDict): if rbKey != 'S': XYZ = RB['Orig'][0] Sytsym = G2spc.SytSym(XYZ,SGData)[0] - xId,xCoef = G2spc.GetCSxinel(Sytsym)[:2] # gen origin site sym + xId,xCoef = G2spc.GetCSxinel(Sytsym)[:2] # gen origin site sym equivs = {1:[],2:[],3:[]} if 'S' not in rbKey: for i in range(3): name = pfxRB+pstr[i]+':'+sfx phaseDict[name] = RB['Orig'][0][i] if RB['Orig'][1]: - if xId[i] > 0: + if xId[i] > 0: phaseVary += [name,] equivs[xId[i]].append([name,xCoef[i]]) else: @@ -1518,7 +1520,7 @@ def MakeRBParms(rbKey,phaseVary,phaseDict): atId = RB['Ids'][0] Atom = Atoms[atomIndx[atId][1]] XYZ = Atom[cx:cx+3] - pfxRB = pfx+'RB'+rbKey+'O' + pfxRB = pfx+'RB'+rbKey+'O' A,V = G2mth.Q2AV(RB['Orient'][0]) # fixAxis = [0, np.abs(V).argmax()+1] for i in range(4): @@ -1551,7 +1553,7 @@ def MakeRBParms(rbKey,phaseVary,phaseDict): phaseDict[name] = RB['Natoms'][ish] name = '%sRBSShR;%d:%s'%(pfx,ish,sfx) phaseDict[name] = rbid - + def MakeRBThermals(rbKey,phaseVary,phaseDict): rbid = str(rbids.index(RB['RBId'])) tlstr = ['11','22','33','12','13','23'] @@ -1582,7 +1584,7 @@ def MakeRBThermals(rbKey,phaseVary,phaseDict): phaseDict[name] = RB['ThermalMotion'][1][0] if RB['ThermalMotion'][2][0]: phaseVary += [name,] - + def MakeRBTorsions(rbKey,phaseVary,phaseDict): rbid = str(rbids.index(RB['RBId'])) pfxRB = pfx+'RB'+rbKey+'Tr;' @@ -1591,7 +1593,7 @@ def MakeRBTorsions(rbKey,phaseVary,phaseDict): phaseDict[name] = torsion[0] if torsion[1]: phaseVary += [name,] - + def MakeRBSphHarm(rbKey,phaseVary,phaseDict): iAt = str(atomIndx[RB['Ids'][0]][1]) #for spin RBs for ish,Shcof in enumerate(RB['SHC']): @@ -1610,7 +1612,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): phaseDict[name] = SHcof[0]*SHcof[1] #apply sign p if SHcof[2]: phaseVary += [name,] - + if Print and pFile is None: raise Exception("specify pFile or Print=False") if Print: pFile.write('\n Phases:\n') @@ -1677,7 +1679,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): phaseDict.update({pfx+'A0':A[0],pfx+'A1':A[1],pfx+'A2':A[2], pfx+'A3':A[3],pfx+'A4':A[4],pfx+'A5':A[5],pfx+'Vol':G2lat.calc_V(A)}) if cell[0]: - phaseVary += cellVary(pfx,SGData) #also fills in symmetry required constraints + phaseVary += cellVary(pfx,SGData) #also fills in symmetry required constraints SSGtext = [] #no superstructure im = 0 if General.get('Modulated',False): @@ -1687,7 +1689,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): SSGData = General['SSGData'] SSGtext,SSGtable = G2spc.SSGPrint(SGData,SSGData) if vRef: - phaseVary += modVary(pfx,SSGData) + phaseVary += modVary(pfx,SSGData) if Atoms and not General.get('doPawley'): cia = General['AtomPtrs'][3] for i,at in enumerate(Atoms): @@ -1699,21 +1701,21 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): MakeRBParms('R',phaseVary,phaseDict) MakeRBThermals('R',phaseVary,phaseDict) MakeRBTorsions('R',phaseVary,phaseDict) - + vecRBData = PhaseData[name]['RBModels'].get('Vector',[]) if vecRBData: rbids = rbIds['Vector'] #NB: used in the MakeRB routines for iRB,RB in enumerate(vecRBData): MakeRBParms('V',phaseVary,phaseDict) MakeRBThermals('V',phaseVary,phaseDict) - + spnRBData = PhaseData[name]['RBModels'].get('Spin',[]) if spnRBData: rbids = rbIds['Spin'] #NB: used in the MakeRB routines for iRB,RB in enumerate(spnRBData): MakeRBParms('S',phaseVary,phaseDict) MakeRBSphHarm('S',phaseVary,phaseDict) - + Natoms[pfx] = 0 maxSSwave[pfx] = {'Sfrac':0,'Spos':0,'Sadp':0,'Smag':0} if Atoms and not General.get('doPawley'): @@ -1744,7 +1746,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): names = [pfx+'dAx:'+str(i),pfx+'dAy:'+str(i),pfx+'dAz:'+str(i)] equivs = {1:[],2:[],3:[]} for j in range(3): - if xId[j] > 0: + if xId[j] > 0: phaseVary.append(names[j]) equivs[xId[j]].append([names[j],xCoef[j]]) else: @@ -1772,7 +1774,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): pfx+'AU12:'+str(i),pfx+'AU13:'+str(i),pfx+'AU23:'+str(i)] equivs = {1:[],2:[],3:[],4:[],5:[],6:[]} for j in range(6): - if uId[j] > 0: + if uId[j] > 0: phaseVary.append(names[j]) equivs[uId[j]].append([names[j],uCoef[j]]) for equiv in equivs: @@ -1844,7 +1846,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): phaseDict.update(dict(zip(names,wave[0]))) if wave[1]: #what do we do here for multiple terms in modulation constraints? for j in range(len(equivs)): - if uId[j][0] > 0: + if uId[j][0] > 0: phaseVary.append(names[j]) equivs[uId[j][0]].append([names[j],uCoef[j][0]]) for equiv in equivs: @@ -1855,7 +1857,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): eqv[1] /= coef G2mv.StoreEquivalence(name,(eqv,)) maxSSwave[pfx][Stype] = max(maxSSwave[pfx][Stype],iw+1) - + if len(Deformations) and not General.get('doPawley'): for iAt in Deformations: if iAt < 0: @@ -1887,7 +1889,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): phaseDict[pfx+item] = textureData['SH Coeff'][1][item] if textureData['SH Coeff'][0]: phaseVary.append(pfx+item) - + if Print: pFile.write('\n Phase name: %s\n'%General['Name']) pFile.write(135*'='+'\n') @@ -1904,7 +1906,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): if len(SSGtable): for item in SSGtable: line = ' %s '%(item) - pFile.write(line+'\n') + pFile.write(line+'\n') else: pFile.write(' ( 1) %s\n'%(SSGtable[0])) else: @@ -1912,14 +1914,14 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): if len(SGtable): for item in SGtable: line = ' %s '%(item) - pFile.write(line+'\n') + pFile.write(line+'\n') else: pFile.write(' ( 1) %s\n'%(SGtable[0])) PrintRBObjects(resRBData,vecRBData,spnRBData) PrintAtoms(General,Atoms) if len(Deformations): PrintDeformations(General,Atoms,Deformations) - + if General['Type'] == 'magnetic': PrintMoments(General,Atoms) if General.get('Modulated',False): @@ -1934,7 +1936,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): if name in RestraintDict: PrintRestraints(cell[1:7],SGData,General['AtomPtrs'],Atoms,AtLookup, textureData,RestraintDict[name],pFile) - + elif PawleyRef: if Print: pFile.write('\n Phase name: %s\n'%General['Name']) @@ -1945,7 +1947,7 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): if len(SSGtable): for item in SSGtable: line = ' %s '%(item) - pFile.write(line+'\n') + pFile.write(line+'\n') else: pFile.write(' ( 1) %s\n'%SSGtable[0]) else: @@ -1972,10 +1974,10 @@ def MakeRBSphHarm(rbKey,phaseVary,phaseDict): pawleyVary.append(pfx+'PWLref:'+str(i)) GetPawleyConstr(SGData['SGLaue'],PawleyRef,im,pawleyVary) #does G2mv.StoreEquivalence phaseVary += pawleyVary - + return Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,EFtables,ORBtables,BLtables,MFtables,maxSSwave - -def cellFill(pfx,SGData,parmDict,sigDict): + +def cellFill(pfx,SGData,parmDict,sigDict): '''Returns the filled-out reciprocal cell (A) terms and their uncertainties from the parameter and sig dictionaries. @@ -1984,7 +1986,7 @@ def cellFill(pfx,SGData,parmDict,sigDict): :param dict parmDict: a dictionary of parameters :param dict sigDict: a dictionary of uncertainties on parameters - :returns: A,sigA where each is a list of six terms with the A terms + :returns: A,sigA where each is a list of six terms with the A terms ''' if SGData['SGLaue'] in ['-1',]: A = [parmDict[pfx+'A0'],parmDict[pfx+'A1'],parmDict[pfx+'A2'], @@ -2039,7 +2041,7 @@ def cellFill(pfx,SGData,parmDict,sigDict): except KeyError: sigA = [0,0,0,0,0,0] return A,sigA - + def PrintRestraints(cell,SGData,AtPtrs,Atoms,AtLookup,textureData,phaseRest,pFile): '''Documents Restraint settings in .lst file @@ -2051,7 +2053,7 @@ def PrintRestraints(cell,SGData,AtPtrs,Atoms,AtLookup,textureData,phaseRest,pFil names = G2obj.restraintNames for name,rest in names: if name not in phaseRest: - continue + continue itemRest = phaseRest[name] if rest in itemRest and itemRest[rest] and itemRest['Use']: pFile.write('\n %s restraint weight factor %10.3f Use: %s\n'%(name,itemRest['wtFactor'],str(itemRest['Use']))) @@ -2096,7 +2098,7 @@ def PrintRestraints(cell,SGData,AtPtrs,Atoms,AtLookup,textureData,phaseRest,pFil pFile.write(' %8.3f %8.3f %.3f %8.3f %8.3f %s\n'%(calc,obs,esd,(obs-calc)/esd,tor,AtName[:-1])) else: phi,psi = G2mth.getRestRama(XYZ,Amat) - restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName]) + restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName]) pFile.write(' %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f %s\n'%(calc,obs,esd,(obs-calc)/esd,phi,psi,AtName[:-1])) elif name == 'ChemComp': pFile.write(' atoms mul*frac factor prod\n') @@ -2178,15 +2180,11 @@ def SummRestraints(restraintDict): res += f'Phase {ph} Restraints: {s}' return res - -# getCellEsd has been moved but leave reference here for now -getCellEsd = G2lat.getCellEsd - def SetPhaseData(parmDict,sigDict,Phases,RBIds,covData,RestraintDict=None,pFile=None): '''Called after a refinement to transfer parameters from the parameter dict to the phase(s) information read from a GPX file. Also prints values to the .lst file ''' - + def PrintAtomsAndSig(General,Atoms,sigDict,sigKey): pFile.write('\n Atoms:\n') line = ' name x y z frac Uiso U11 U22 U33 U12 U13 U23' @@ -2212,7 +2210,7 @@ def PrintAtomsAndSig(General,Atoms,sigDict,sigKey): sigstr = ' sig : ' for ind in range(cx,cx+4): sigind = str(i)+':'+str(ind) - valstr += fmt[ind]%(at[ind]) + valstr += fmt[ind]%(at[ind]) # if sigind in atomsSig: # sigstr += fmt[ind]%(atomsSig[sigind]) # else: @@ -2231,7 +2229,7 @@ def PrintAtomsAndSig(General,Atoms,sigDict,sigKey): for ind in range(cia+2,cia+8): sigind = str(i)+':'+str(ind) valstr += fmt[ind]%(at[ind]) - # if sigind in atomsSig: + # if sigind in atomsSig: # sigstr += fmt[ind]%(atomsSig[sigind]) # else: # sigstr += 8*' ' @@ -2239,7 +2237,7 @@ def PrintAtomsAndSig(General,Atoms,sigDict,sigKey): pFile.write(name+'\n') pFile.write(valstr+'\n') pFile.write(sigstr+'\n') - + def PrintMomentsAndSig(General,Atoms,atomsSig): cell = General['Cell'][1:7] G = G2lat.fillgmat(cell) @@ -2261,7 +2259,7 @@ def PrintMomentsAndSig(General,Atoms,atomsSig): sigstr = ' sig :' for ind in range(cmx,cmx+3): sigind = str(i)+':'+str(ind) - valstr += fmt[ind]%(at[ind]) + valstr += fmt[ind]%(at[ind]) if sigind in atomsSig: sigstr += fmt[ind]%(atomsSig[sigind]) else: @@ -2273,7 +2271,7 @@ def PrintMomentsAndSig(General,Atoms,atomsSig): pFile.write(name+'\n') pFile.write(valstr+'\n') pFile.write(sigstr+'\n') - + def PrintDeformationsAndSig(General,Atoms,Deformations,deformSig): pFile.write('\n Atom deformations:\n') cx,ct,cs,cia = General['AtomPtrs'] @@ -2379,30 +2377,30 @@ def PrintWavesAndSig(General,Atoms,wavesSig): sigstr += '%12.4f'%(wavesSig[name+stiw]) else: sigstr += 12*' ' - + pFile.write(namstr+'\n') pFile.write(valstr+'\n') pFile.write(sigstr+'\n') - - + + def PrintRBObjPOAndSig(rbfx,rbsx): for i in WriteRBObjPOAndSig(pfx,rbfx,rbsx,parmDict,sigDict): pFile.write(i+'\n') - + def PrintRBObjTLSAndSig(rbfx,rbsx,TLS): for i in WriteRBObjTLSAndSig(pfx,rbfx,rbsx,TLS,parmDict,sigDict): pFile.write(i) - + def PrintRBObjSHCAndSig(rbfx,SHC,rbsx): for i in WriteRBObjSHCAndSig(pfx,rbfx,rbsx,parmDict,sigDict,SHC): pFile.write(i) - + def PrintRBObjTorAndSig(rbsx): nTors = len(RBObj['Torsions']) if nTors: for i in WriteRBObjTorAndSig(pfx,rbsx,parmDict,sigDict,nTors): pFile.write(i) - + def PrintSHtextureAndSig(textureData,SHtextureSig): Tindx = 1.0 Tvar = 0.0 @@ -2448,7 +2446,7 @@ def PrintSHtextureAndSig(textureData,SHtextureSig): iBeg += 10 iFin = min(iBeg+10,nCoeff) pFile.write(' Texture index J = %.3f(%d)'%(Tindx,int(1000*np.sqrt(Tvar)))) - + ########################################################################## # SetPhaseData starts here if pFile: pFile.write('\n Phases:\n') @@ -2468,7 +2466,7 @@ def PrintSHtextureAndSig(textureData,SHtextureSig): pfx = str(pId)+'::' if cell[0]: A,sigA = cellFill(pfx,SGData,parmDict,sigDict) - cellSig = getCellEsd(pfx,SGData,A,covData,unique=True) #includes sigVol + cellSig = G2lat.getCellEsd(pfx,SGData,A,covData,unique=True) #includes sigVol if pFile: pFile.write(' Reciprocal metric tensor: \n') ptfmt = "%15.9f" names = ['A11','A22','A33','A12','A13','A23'] @@ -2523,7 +2521,7 @@ def PrintSHtextureAndSig(textureData,SHtextureSig): if pFile: pFile.write(namstr+'\n') if pFile: pFile.write(ptstr+'\n') if pFile: pFile.write(sigstr+'\n') - + General['Mass'] = 0. if Phase['General'].get('doPawley'): pawleyRef = Phase['Pawley ref'] @@ -2634,7 +2632,7 @@ def PrintSHtextureAndSig(textureData,SHtextureSig): AtomSS[Stype][iw+1][0][iname] = parmDict[pfx+name] if pfx+name in sigDict: wavesSig[name] = sigDict[pfx+name] - + Deformations = Phase.get('Deformations',{}) for iAt in Deformations: if iAt < 0: @@ -2660,12 +2658,12 @@ def PrintSHtextureAndSig(textureData,SHtextureSig): PrintMomentsAndSig(General,Atoms,atomsSig) if pFile and General.get('Modulated',False): PrintWavesAndSig(General,Atoms,wavesSig) - + density = G2mth.getDensity(General)[0] if pFile: pFile.write('\n Density: {:.4f} g/cm**3\n'.format(density)) - - - textureData = General['SH Texture'] + + + textureData = General['SH Texture'] if textureData['Order']: SHtextureSig = {} for name in ['omega','chi','phi']: @@ -2682,11 +2680,11 @@ def PrintSHtextureAndSig(textureData,SHtextureSig): if phase in RestraintDict and not Phase['General'].get('doPawley'): PrintRestraints(cell[1:7],SGData,General['AtomPtrs'],Atoms,AtLookup, textureData,RestraintDict[phase],pFile) - + def SetISOmodes(parmDict,sigDict,Phases,pFile=None): - '''After a refinement, sets the values for the ISODISTORT modes into - the parameter and s.u. dicts. - Also, in the case of a non-sequential refinement, prints them into + '''After a refinement, sets the values for the ISODISTORT modes into + the parameter and s.u. dicts. + Also, in the case of a non-sequential refinement, prints them into the project's .lst file. :param dict parmDict: parameter dict @@ -2699,7 +2697,7 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): data = Phases[phase] ISO = data['ISODISTORT'] atNames = [atom[0] for atom in data['Atoms']] - + if 'G2VarList' in ISO: deltaList = [] notfound = [] @@ -2715,7 +2713,7 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): notfound.append(var) continue deltaList.append(cval-pval) - + if notfound and pFile: msg = 'SetISOmodes warning: Atom parameters ' for i,v in enumerate(notfound): @@ -2732,11 +2730,11 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): elif notfound: continue modeVals = np.inner(ISO['Var2ModeMatrix'],deltaList) - + if pFile: pFile.write('\n ISODISTORT Displacive Modes for phase {}\n'.format( data['General'].get('Name',''))) - + l = str(max([len(i) for i in ISO['IsoModeList']])+3) fmt = ' {:'+l+'}{}' for varid,[var,val,norm,G2mode] in enumerate(zip( @@ -2753,7 +2751,7 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): value = '?' if pFile: pFile.write(fmt.format(var,value)+'\n') - + if 'G2OccVarList' in ISO: #untested - probably wrong deltaOccList = [] notfound = [] @@ -2767,7 +2765,7 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): notfound.append(var) continue deltaOccList.append(cval-pval) - + if notfound and pFile: msg = 'SetISOmodes warning: Atom parameters ' for i,v in enumerate(notfound): @@ -2784,7 +2782,7 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): elif notfound: continue modeOccVals = np.inner(ISO['Var2OccMatrix'],deltaOccList) - + if pFile: pFile.write('\n ISODISTORT Occupancy Modes for phase {}\n'.format(data['General'].get('Name',''))) l = str(max([len(i) for i in ISO['OccModeList']])+3) @@ -2799,11 +2797,11 @@ def SetISOmodes(parmDict,sigDict,Phases,pFile=None): value = '?' if pFile: pFile.write(fmt.format(var,value)+'\n') - + ################################################################################ ##### Histogram & Phase data -################################################################################ - +################################################################################ + def GetHistogramPhaseData(Phases,Histograms,Controls={},Print=True,pFile=None,resetRefList=True): '''Loads the HAP histogram/phase information into dicts @@ -2819,7 +2817,7 @@ def GetHistogramPhaseData(Phases,Histograms,Controls={},Print=True,pFile=None,re * hapDict: dict with refined variables and their values * controlDict: dict with fixed parameters ''' - + def PrintSize(hapData): if hapData[0] in ['isotropic','uniaxial']: line = '\n Size model : %9s'%(hapData[0]) @@ -2842,7 +2840,7 @@ def PrintSize(hapData): pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(varstr+'\n') - + def PrintMuStrain(hapData,SGData): if hapData[0] in ['isotropic','uniaxial']: line = '\n Mustrain model: %9s'%(hapData[0]) @@ -2886,10 +2884,10 @@ def PrintSHPO(hapData): ptstr = ' values:' for item in hapData[5]: ptlbls += '%12s'%(item) - ptstr += '%12.3f'%(hapData[5][item]) + ptstr += '%12.3f'%(hapData[5][item]) pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') - + def PrintBabinet(hapData): pFile.write('\n Babinet form factor modification:\n') ptlbls = ' names :' @@ -2902,11 +2900,11 @@ def PrintBabinet(hapData): pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(varstr+'\n') - + hapDict = {} hapVary = [] controlDict = {} - + for phase in Phases: HistoPhase = Phases[phase]['Histograms'] SGData = Phases[phase]['General']['SGData'] @@ -2927,7 +2925,7 @@ def PrintBabinet(hapData): for histogram in histoList: try: Histogram = Histograms[histogram] - except KeyError: + except KeyError: #skip if histogram not included e.g. in a sequential refinement continue try: @@ -3042,12 +3040,12 @@ def PrintBabinet(hapData): hapDict[pfx+bab] = hapData['Babinet'][bab][0] if hapData['Babinet'][bab][1]: # and not hapDict[pfx+'LeBail']: hapVary.append(pfx+bab) - - if Print: + + if Print: pFile.write('\n Phase: %s in histogram: %s\n'%(phase,histogram)) pFile.write(135*'='+'\n') if hapDict.get(pfx+'LeBail'): - pFile.write(' Perform LeBail extraction\n') + pFile.write(' Perform LeBail extraction\n') elif 'E' not in inst['Type'][0]: pFile.write(' Phase fraction : %10.4g Refine? %s\n'%(hapData['Scale'][0],hapData['Scale'][1])) pFile.write(' Extinction coeff: %10.4f Refine? %s\n'%(hapData['Extinction'][0],hapData['Extinction'][1])) @@ -3132,7 +3130,7 @@ def PrintBabinet(hapData): refList.append([h,k,l,mul,d, pos,0.0,0.0,0.0,randI*StartI, 0.0,0.0]) # ... sig,gam,fotsq,fctsq, phase,icorr if len(refList) == 0: - raise G2obj.G2Exception(' Ouch #8: no reflections in data range - rethink PWDR limits') + raise G2obj.G2Exception(f'Ouch #8: no reflections in data range.\nRethink PWDR limits for phase {phase!r} and histogram {histogram!r}') Histogram['Reflection Lists'][phase] = {'RefList':np.array(refList),'FF':{},'Type':inst['Type'][0],'Super':ifSuper} elif 'HKLF' in histogram: inst = Histogram['Instrument Parameters'][0] @@ -3145,7 +3143,7 @@ def PrintBabinet(hapData): hapDict[pfx+'Scale'] = hapData['Scale'][0] if hapData['Scale'][1]: hapVary.append(pfx+'Scale') - + extApprox,extType,extParms = hapData['Extinction'] controlDict[pfx+'EType'] = extType controlDict[pfx+'EApprox'] = extApprox @@ -3179,7 +3177,7 @@ def PrintBabinet(hapData): sumTwFr = 0. controlDict[pfx+'TwinLaw'] = [] controlDict[pfx+'TwinInv'] = [] - NTL = 0 + NTL = 0 for it,twin in enumerate(Twins): if 'bool' in str(type(twin[0])): controlDict[pfx+'TwinInv'].append(twin[0]) @@ -3201,7 +3199,7 @@ def PrintBabinet(hapData): controlDict[pfx+'TwinLaw'] = np.array(controlDict[pfx+'TwinLaw']) if len(Twins) > 1: #force sum to unity hapDict[pfx+'TwinFr:0'] = 1.-sumTwFr - if Print: + if Print: pFile.write('\n Phase: %s in histogram: %s\n'%(phase,histogram)) pFile.write(135*'='+'\n') pFile.write(' Scale factor : %10.4g Refine? %s\n'%(hapData['Scale'][0],hapData['Scale'][1])) @@ -3224,16 +3222,16 @@ def PrintBabinet(hapData): else: pFile.write(' Twin law: %s Twin fr.: %5.3f Refine? %s\n'% (str(twin[0]).replace('\n',','),hapDict[pfx+'TwinFr:'+str(it)],str(Twins[0][1][1]))) - - Histogram['Reflection Lists'] = phase - + + Histogram['Reflection Lists'] = phase + return hapVary,hapDict,controlDict - + def SetHistogramPhaseData(parmDict,sigDict,Phases,Histograms,calcControls,Print=True,pFile=None, covMatrix=[],varyList=[]): '''Updates parmDict with HAP results from refinement and prints a summary if Print is True ''' - + def PrintSizeAndSig(hapData,sizeSig): line = '\n Size model: %9s'%(hapData[0]) refine = False @@ -3275,7 +3273,7 @@ def PrintSizeAndSig(hapData,sizeSig): pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(sigstr+'\n') - + def PrintMuStrainAndSig(hapData,mustrainSig,SGData): line = '\n Mustrain model: %9s\n'%(hapData[0]) refine = False @@ -3316,7 +3314,7 @@ def PrintMuStrainAndSig(hapData,mustrainSig,SGData): pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(sigstr+'\n') - + def PrintHStrainAndSig(hapData,strainSig,SGData): Hsnames = G2spc.HStrainNames(SGData) ptlbls = ' name :' @@ -3354,12 +3352,12 @@ def PrintSHPOAndSig(pfx,hapData,POsig): Tvar += (2.*hapData[5][item]*POsig[pfx+item]/l)**2 sigstr += '%12.3f'%(POsig[pfx+item]) else: - sigstr += 12*' ' + sigstr += 12*' ' pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(sigstr+'\n') pFile.write('\n Texture index J = %.3f(%d)\n'%(Tindx,int(1000*np.sqrt(Tvar)))) - + def PrintExtAndSig(pfx,hapData,ScalExtSig): pFile.write('\n Single crystal extinction: Type: %s Approx: %s\n'%(hapData[0],hapData[1])) text = '' @@ -3369,8 +3367,8 @@ def PrintExtAndSig(pfx,hapData,ScalExtSig): text += '%12.2e'%(hapData[2][item][0]) if pfx+item in ScalExtSig: text += ' sig: %12.2e'%(ScalExtSig[pfx+item]) - pFile.write(text+'\n') - + pFile.write(text+'\n') + def PrintBabinetAndSig(pfx,hapData,BabSig): pFile.write('\n Babinet form factor modification:\n') ptlbls = ' names :' @@ -3382,11 +3380,11 @@ def PrintBabinetAndSig(pfx,hapData,BabSig): if pfx+item in BabSig: sigstr += '%12.3f'%(BabSig[pfx+item]) else: - sigstr += 12*' ' + sigstr += 12*' ' pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(sigstr+'\n') - + def PrintTwinsAndSig(pfx,twinData,TwinSig): pFile.write('\n Twin Law fractions :\n') ptlbls = ' names :' @@ -3401,11 +3399,11 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): if pfx+'TwinFr:'+str(it) in TwinSig: sigstr += '%12.3f'%(TwinSig[pfx+'TwinFr:'+str(it)]) else: - sigstr += 12*' ' + sigstr += 12*' ' pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(sigstr+'\n') - + # global PhFrExtPOSig # this is not used externally anymore. Remove? PhFrExtPOSig = {} SizeMuStrSig = {} @@ -3423,7 +3421,7 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): for histogram in histoList: try: Histogram = Histograms[histogram] - except KeyError: + except KeyError: #skip if histogram not included e.g. in a sequential refinement continue if not Phases[phase]['Histograms'][histogram]['Use']: @@ -3452,22 +3450,22 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): if pfx+item in sigDict and not parmDict.get(pfx+'LeBail'): PhFrExtPOSig.update({pfx+item:sigDict[pfx+item],}) SizeMuStrSig.update({pfx+'Mustrain':[[0,0,0],[0 for i in range(len(hapData['Mustrain'][4]))]], - pfx+'Size':[[0,0,0],[0 for i in range(len(hapData['Size'][4]))]],pfx+'HStrain':{}}) + pfx+'Size':[[0,0,0],[0 for i in range(len(hapData['Size'][4]))]],pfx+'HStrain':{}}) for item in ['Mustrain','Size']: hapData[item][1][2] = parmDict[pfx+item+';mx'] # hapData[item][1][2] = min(1.,max(0.,hapData[item][1][2])) if pfx+item+';mx' in sigDict: SizeMuStrSig[pfx+item][0][2] = sigDict[pfx+item+';mx'] - if hapData[item][0] in ['isotropic','uniaxial']: + if hapData[item][0] in ['isotropic','uniaxial']: hapData[item][1][0] = parmDict[pfx+item+';i'] if item == 'Size': hapData[item][1][0] = min(10.,max(0.001,hapData[item][1][0])) - if pfx+item+';i' in sigDict: + if pfx+item+';i' in sigDict: SizeMuStrSig[pfx+item][0][0] = sigDict[pfx+item+';i'] if hapData[item][0] == 'uniaxial': hapData[item][1][1] = parmDict[pfx+item+';a'] if item == 'Size': - hapData[item][1][1] = min(10.,max(0.001,hapData[item][1][1])) + hapData[item][1][1] = min(10.,max(0.001,hapData[item][1][1])) if pfx+item+';a' in sigDict: SizeMuStrSig[pfx+item][0][1] = sigDict[pfx+item+';a'] else: #generalized for mustrain or ellipsoidal for size @@ -3477,7 +3475,7 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): hapData[item][4][i] = parmDict[pfx+item+sfx] if pfx+item+sfx in sigDict: SizeMuStrSig[pfx+item][1][i] = sigDict[pfx+item+sfx] - SizeMuStrSig.update({pfx+'HStrain':{}}) + SizeMuStrSig.update({pfx+'HStrain':{}}) names = G2spc.HStrainNames(SGData) for i,name in enumerate(names): hapData['HStrain'][0][i] = parmDict[pfx+name] @@ -3491,8 +3489,8 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): for name in ['BabA','BabU']: hapData['Babinet'][name][0] = parmDict[pfx+name] if pfx+name in sigDict and not parmDict.get(pfx+'LeBail'): - BabSig[pfx+name] = sigDict[pfx+name] - + BabSig[pfx+name] = sigDict[pfx+name] + elif 'HKLF' in histogram: for item in ['Scale','Flack']: if parmDict.get(pfx+item): @@ -3544,7 +3542,7 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): for histogram in histoList: try: Histogram = Histograms[histogram] - except KeyError: + except KeyError: #skip if histogram not included e.g. in a sequential refinement continue hapData = HistoPhase[histogram] @@ -3562,7 +3560,7 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): (Histogram['Residuals'][pfx+'Rf'],Histogram['Residuals'][pfx+'Rf^2'],Histogram['Residuals'][pfx+'Nref'])) pFile.write(' Durbin-Watson statistic = %.3f\n'%(Histogram['Residuals']['Durbin-Watson'])) pFile.write(' Bragg intensity sum = %.3g\n'%(Histogram['Residuals'][pfx+'sumInt'])) - + if parmDict.get(pfx+'LeBail') or 'E' in Inst['Type'][0]: pFile.write(' Performed LeBail extraction for phase %s in histogram %s\n'%(phase,histogram)) elif 'E' not in Inst['Type'][0]: @@ -3588,11 +3586,11 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): txt = G2lat.showCellSU(cellList,cellSig,SGData) pFile.write(f' resulting cell parameters: {txt}\n') if pfx+'LayerDisp' in SizeMuStrSig: - pFile.write(' Layer displacement : %10.3f, sig %10.3f\n'%(hapData['Layer Disp'][0],SizeMuStrSig[pfx+'LayerDisp'])) + pFile.write(' Layer displacement : %10.3f, sig %10.3f\n'%(hapData['Layer Disp'][0],SizeMuStrSig[pfx+'LayerDisp'])) if Phases[phase]['General']['Type'] != 'magnetic' and not parmDict.get(pfx+'LeBail') and 'E' not in Inst['Type'][0]: if len(BabSig): PrintBabinetAndSig(pfx,hapData['Babinet'],BabSig) - + elif 'HKLF' in histogram: pFile.write(' Final refinement RF, RF^2 = %.2f%%, %.2f%% on %d reflections (%d user rejected, %d sp.gp.extinct)\n'% (Histogram['Residuals'][pfx+'Rf'],Histogram['Residuals'][pfx+'Rf^2'],Histogram['Residuals'][pfx+'Nref'], @@ -3616,11 +3614,11 @@ def PrintTwinsAndSig(pfx,twinData,TwinSig): ################################################################################ ##### Histogram data -################################################################################ - +################################################################################ + def GetHistogramData(Histograms,Print=True,pFile=None): 'needs a doc string' - + def GetBackgroundParms(hId,Background): Back = Background[0] DebyePeaks = Background[1] @@ -3666,8 +3664,8 @@ def GetBackgroundParms(hId,Background): backVary.append(':'+str(hId)+':BF mult') except IndexError: # old version without refine flag pass - return bakType,backDict,backVary - + return bakType,backDict,backVary + def GetInstParms(hId,Inst): #patch dataType = Inst['Type'][0] @@ -3700,10 +3698,10 @@ def GetInstParms(hId,Inst): elif 'E' in dataType: pass return dataType,instDict,insVary - + def GetSampleParms(hId,Sample): sampVary = [] - hfx = ':'+str(hId)+':' + hfx = ':'+str(hId)+':' sampDict = {hfx+'Gonio. radius':Sample['Gonio. radius'],hfx+'Omega':Sample['Omega'], hfx+'Chi':Sample['Chi'],hfx+'Phi':Sample['Phi'],hfx+'Azimuth':Sample['Azimuth']} for key in ('Temperature','Pressure','FreePrm1','FreePrm2','FreePrm3'): @@ -3721,7 +3719,7 @@ def GetSampleParms(hId,Sample): if Sample[item][1]: sampVary.append(hfx+item) return Type,sampDict,sampVary - + def PrintBackground(Background): Back = Background[0] DebyePeaks = Background[1] @@ -3742,7 +3740,7 @@ def PrintBackground(Background): for j,term in enumerate(DebyePeaks['debyeTerms']): line = ' term'+'%2d'%(j)+':' for i in range(3): - line += '%10.3f %5s'%(term[2*i],bool(term[2*i+1])) + line += '%10.3f %5s'%(term[2*i],bool(term[2*i+1])) pFile.write(line+'\n') if DebyePeaks['nPeaks']: pFile.write('\n Single peak coefficients\n') @@ -3754,7 +3752,7 @@ def PrintBackground(Background): for j,term in enumerate(DebyePeaks['peaksList']): line = ' peak'+'%2d'%(j)+':' for i in range(4): - line += '%12.3f %5s'%(term[2*i],bool(term[2*i+1])) + line += '%12.3f %5s'%(term[2*i],bool(term[2*i+1])) pFile.write(line+'\n') if 'background PWDR' in DebyePeaks: try: @@ -3762,7 +3760,7 @@ def PrintBackground(Background): DebyePeaks['background PWDR'][1],DebyePeaks['background PWDR'][2])) except IndexError: #old version without refine flag pass - + def PrintInstParms(Inst): pFile.write('\n Instrument Parameters:\n') insKeys = [item for item in Inst.keys() if item not in ['Type','Source','Bank']] @@ -3789,7 +3787,7 @@ def PrintInstParms(Inst): Ok = False else: pFile.write('\n') - + def PrintSampleParms(Sample): pFile.write('\n Sample Parameters:\n') pFile.write(' Goniometer omega = %.2f, chi = %.2f, phi = %.2f\n'% @@ -3802,7 +3800,7 @@ def PrintSampleParms(Sample): ptlbls += '%14s'%(item) ptstr += '%14.4f'%(Sample[item][0]) varstr += '%14s'%(str(bool(Sample[item][1]))) - + elif 'Debye' in Type: #Debye-Scherrer for item in ['Scale','Absorption','DisplaceX','DisplaceY']: ptlbls += '%14s'%(item) @@ -3812,7 +3810,7 @@ def PrintSampleParms(Sample): pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(varstr+'\n') - + histDict = {} histVary = [] controlDict = {} @@ -3835,7 +3833,7 @@ def PrintSampleParms(Sample): controlDict[pfx+'bakType'] = Type histDict.update(bakDict) histVary += bakVary - + Inst = Histogram['Instrument Parameters'] #TODO ? ignores tabulated alp,bet & delt for TOF if 'T' in Type and len(Inst[1]): #patch - back-to-back exponential contribution to TOF line shape is removed G2fil.G2Print ('Warning: tabulated profile coefficients are ignored') @@ -3845,18 +3843,18 @@ def PrintSampleParms(Sample): if pfx+'Lam1' in instDict: controlDict[pfx+'keV'] = G2mth.wavekE(instDict[pfx+'Lam1']) else: - controlDict[pfx+'keV'] = G2mth.wavekE(instDict[pfx+'Lam']) + controlDict[pfx+'keV'] = G2mth.wavekE(instDict[pfx+'Lam']) histDict.update(instDict) histVary += insVary - + Sample = Histogram['Sample Parameters'] Type,sampDict,sampVary = GetSampleParms(hId,Sample) controlDict[pfx+'instType'] = Type histDict.update(sampDict) histVary += sampVary - - - if Print: + + + if Print: pFile.write('\n Histogram: %s histogram Id: %d\n'%(histogram,hId)) pFile.write(135*'='+'\n') Units = {'C':' deg','T':' msec','B':' deg','E':'keV','A':' deg'} @@ -3867,7 +3865,7 @@ def PrintSampleParms(Sample): if len(controlDict[pfx+'Exclude']): excls = controlDict[pfx+'Exclude'] for excl in excls: - pFile.write(' Excluded region: %8.2f%s to %8.2f%s\n'%(excl[0],units,excl[1],units)) + pFile.write(' Excluded region: %8.2f%s to %8.2f%s\n'%(excl[0],units,excl[1],units)) PrintSampleParms(Sample) PrintInstParms(Inst[0]) PrintBackground(Background) @@ -3883,13 +3881,13 @@ def PrintSampleParms(Sample): controlDict[pfx+'keV'] = G2mth.wavekE(histDict[pfx+'Lam']) elif 'SEC' in Inst['Type'][1]: histDict[pfx+'Lam'] = Inst['Lam'][1] - elif 'NC' in Inst['Type'][1] or 'NB' in Inst['Type'][1]: + elif 'NC' in Inst['Type'][1] or 'NB' in Inst['Type'][1]: histDict[pfx+'Lam'] = Inst['Lam'][1] return histVary,histDict,controlDict - + def SetHistogramData(parmDict,sigDict,Histograms,calcControls,Print=True,pFile=None,seq=False): 'Shows histogram data after a refinement' - + def SetBackgroundParms(pfx,Background,parmDict,sigDict): Back = Background[0] DebyePeaks = Background[1] @@ -3905,7 +3903,7 @@ def SetBackgroundParms(pfx,Background,parmDict,sigDict): for j,name in enumerate(names): DebyePeaks['debyeTerms'][i][2*j] = parmDict[name] if name in sigDict: - backSig[lenBack+3*i+j] = sigDict[name] + backSig[lenBack+3*i+j] = sigDict[name] if DebyePeaks['nPeaks']: for i in range(DebyePeaks['nPeaks']): names = [pfx+'BkPkpos;'+str(i),pfx+'BkPkint;'+str(i), @@ -3917,9 +3915,9 @@ def SetBackgroundParms(pfx,Background,parmDict,sigDict): if pfx+'BF mult' in sigDict: DebyePeaks['background PWDR'][1] = parmDict[pfx+'BF mult'] backSig.append(sigDict[pfx+'BF mult']) - + return backSig - + def SetInstParms(pfx,Inst,parmDict,sigDict): instSig = {} insKeys = list(Inst.keys()) @@ -3932,7 +3930,7 @@ def SetInstParms(pfx,Inst,parmDict,sigDict): else: instSig[item] = 0 return instSig - + def SetSampleParms(pfx,Sample,parmDict,sigDict): if 'Bragg' in Sample['Type']: #Bragg-Brentano sampSig = [0 for i in range(5)] @@ -3947,7 +3945,7 @@ def SetSampleParms(pfx,Sample,parmDict,sigDict): if pfx+item in sigDict: sampSig[i] = sigDict[pfx+item] return sampSig - + def PrintBackgroundSig(Background,backSig): Back = Background[0] DebyePeaks = Background[1] @@ -4011,7 +4009,7 @@ def PrintBackgroundSig(Background,backSig): sumBk = np.array(Histogram['sumBk']) pFile.write(' Background sums: empirical %.3g, Debye %.3g, peaks %.3g, Total %.3g\n'% (sumBk[0],sumBk[1],sumBk[2],np.sum(sumBk))) - + def PrintInstParmsSig(Inst,instSig): refine = False insKeys = [item for item in instSig.keys() if item not in ['Type','Lam1','Lam2','Azimuth','Source','fltPath','Bank']] @@ -4039,7 +4037,7 @@ def PrintInstParmsSig(Inst,instSig): iBeg = iFin if iBeg == len(insKeys): Ok = False - + def PrintSampleParmsSig(Sample,sampleSig): ptlbls = ' names :' ptstr = ' values:' @@ -4054,7 +4052,7 @@ def PrintSampleParmsSig(Sample,sampleSig): sigstr += '%14.4f'%(sampleSig[i]) else: sigstr += 14*' ' - + elif 'Debye' in Sample['Type']: #Debye-Scherrer for i,item in enumerate(['Scale','Absorption','DisplaceX','DisplaceY']): ptlbls += '%14s'%(item) @@ -4070,7 +4068,7 @@ def PrintSampleParmsSig(Sample,sampleSig): pFile.write(ptlbls+'\n') pFile.write(ptstr+'\n') pFile.write(sigstr+'\n') - + histoList = list(Histograms.keys()) histoList.sort() for histogram in histoList: @@ -4080,10 +4078,10 @@ def PrintSampleParmsSig(Sample,sampleSig): pfx = ':'+str(hId)+':' Background = Histogram['Background'] backSig = SetBackgroundParms(pfx,Background,parmDict,sigDict) - + Inst = Histogram['Instrument Parameters'][0] instSig = SetInstParms(pfx,Inst,parmDict,sigDict) - + Sample = Histogram['Sample Parameters'] parmDict[pfx+'Scale'] = max(1.e-12,parmDict[pfx+'Scale']) #put floor on phase fraction scale sampSig = SetSampleParms(pfx,Sample,parmDict,sigDict) @@ -4106,9 +4104,9 @@ def PrintSampleParmsSig(Sample,sampleSig): PrintSampleParmsSig(Sample,sampSig) PrintInstParmsSig(Inst,instSig) PrintBackgroundSig(Background,backSig) - + def WriteRBObjPOAndSig(pfx,rbfx,rbsx,parmDict,sigDict): - '''Cribbed version of PrintRBObjPOAndSig but returns lists of strings. + '''Cribbed version of PrintRBObjPOAndSig but returns lists of strings. Moved so it can be used in ExportCIF ''' namstr = ' names :' @@ -4143,7 +4141,7 @@ def WriteRBObjPOAndSig(pfx,rbfx,rbsx,parmDict,sigDict): return (namstr,valstr,sigstr) def WriteRBObjTLSAndSig(pfx,rbfx,rbsx,TLS,parmDict,sigDict): - '''Cribbed version of PrintRBObjTLSAndSig but returns lists of strings. + '''Cribbed version of PrintRBObjTLSAndSig but returns lists of strings. Moved so it can be used in ExportCIF ''' out = [] @@ -4208,7 +4206,7 @@ def WriteRBObjTLSAndSig(pfx,rbfx,rbsx,TLS,parmDict,sigDict): return out def WriteRBObjTorAndSig(pfx,rbsx,parmDict,sigDict,nTors): - '''Cribbed version of PrintRBObjTorAndSig but returns lists of strings. + '''Cribbed version of PrintRBObjTorAndSig but returns lists of strings. Moved so it can be used in ExportCIF ''' out = [] @@ -4230,7 +4228,7 @@ def WriteRBObjTorAndSig(pfx,rbsx,parmDict,sigDict,nTors): return out def WriteRBObjSHCAndSig(pfx,rbfx,rbsx,parmDict,sigDict,SHC): - '''Cribbed version of PrintRBObjTorAndSig but returns lists of strings. + '''Cribbed version of PrintRBObjTorAndSig but returns lists of strings. Moved so it can be used in ExportCIF ''' out = [] @@ -4262,7 +4260,7 @@ def WriteRBObjSHCAndSig(pfx,rbfx,rbsx,parmDict,sigDict,SHC): return out def WriteResRBModel(RBModel): - '''Write description of a residue rigid body. Code shifted from + '''Write description of a residue rigid body. Code shifted from PrintResRBModel to make usable from G2export_CIF ''' out = [] @@ -4281,7 +4279,7 @@ def WriteResRBModel(RBModel): return out def WriteVecRBModel(RBModel,sigDict={},irb=None): - '''Write description of a vector rigid body. Code shifted from + '''Write description of a vector rigid body. Code shifted from PrintVecRBModel to make usable from G2export_CIF ''' out = [] @@ -4306,8 +4304,8 @@ def WriteVecRBModel(RBModel,sigDict={},irb=None): atmPattrn = re.compile("::A[xyz]:") fmtSplit = re.compile('%([0-9]+)\\.([0-9]+)(.*)') def fmtESD(varname,SigDict,fmtcode,ndig=None,ndec=None): - '''Format an uncertainty value as requested, but surround the - number by () if the parameter is set by an equivalence + '''Format an uncertainty value as requested, but surround the + number by () if the parameter is set by an equivalence or by [] if the parameter is set by an constraint :param str fmtcode: can be a single letter such as 'g' or 'f', diff --git a/GSASII/GSASIIstrMain.py b/GSASII/GSASIIstrMain.py index 0b260388c..97991a140 100644 --- a/GSASII/GSASIIstrMain.py +++ b/GSASII/GSASIIstrMain.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- ''' -:mod:`GSASIIstrMain` routines, used for refinement, or refinement-related -computations (e.g. distance & angle computations), are found below. +:mod:`GSASIIstrMain` routines, used for refinement, or refinement-related +computations (e.g. distance & angle computations), are found below. -These routines are most commonly called when working from a .gpx file, but -may sometimes be called from the GUI. These routines expect that all needed -input will have been read from the file/tree and are passed to thes +These routines are most commonly called when working from a .gpx file, but +may sometimes be called from the GUI. These routines expect that all needed +input will have been read from the file/tree and are passed to thes routines here as arguments. The data tree is never accessed directly in this module and no GUI modules should be imported here. ''' @@ -15,23 +15,30 @@ import time import math import copy -import pickle as cPickle +import pickle import numpy as np import numpy.linalg as nl import numpy.ma as ma import scipy.optimize as so -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIImapvars as G2mv -import GSASIImath as G2mth -import GSASIIstrIO as G2stIO -import GSASIIstrMath as G2stMth -import GSASIIobj as G2obj -import GSASIIfiles as G2fil -import GSASIIElem as G2elem -import atmdata +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIImapvars as G2mv +from . import GSASIImath as G2mth +from . import GSASIIstrIO as G2stIO +from . import GSASIIstrMath as G2stMth +from . import GSASIIobj as G2obj +from . import GSASIIfiles as G2fil +from . import GSASIIElem as G2elem +from . import atmdata +try: + if GSASIIpath.binaryPath: + import pytexture as ptx + else: + from . import pytexture as ptx +except ImportError: # ignore; will report this as an error in GSASIIplot import + pass sind = lambda x: np.sin(x*np.pi/180.) cosd = lambda x: np.cos(x*np.pi/180.) @@ -77,9 +84,9 @@ def ReportProblems(result,Rvals,varyList): msg += ', {}'.format(varyList[val]) if m: G2fil.G2Print(m, mode='warn') SVD0 = result[2].get('SVD0',0) - if SVD0 == 1: + if SVD0 == 1: msg += 'Warning: Soft (SVD) singularity in the Hessian' - elif SVD0 > 0: + elif SVD0 > 0: msg += 'Warning: {} soft (SVD) Hessian singularities'.format(SVD0) SVDsing = result[2].get('SVDsing',[]) if len(SVDsing): @@ -108,7 +115,7 @@ def ReportProblems(result,Rvals,varyList): if m: G2fil.G2Print(m, mode='warn') #report on highly correlated variables Hcorr = result[2].get('Hcorr',[]) - if len(Hcorr) > 0: + if len(Hcorr) > 0: if msg: msg += '\n' m = 'Note highly correlated parameters:' G2fil.G2Print(m, mode='warn') @@ -170,11 +177,11 @@ def IgnoredLatticePrms(Phases): def AllPrmDerivs(Controls,Histograms,Phases,restraintDict,rigidbodyDict, parmDict,varyList,calcControls,pawleyLookup,symHold,dlg=None): - '''Computes the derivative of the fitting function (total Chi**2) with + '''Computes the derivative of the fitting function (total Chi**2) with respect to every parameter in the parameter dictionary (parmDict) - by applying shift below the parameter value as well as above. - - :returns: a dict with the derivatives keyed by variable number. + by applying shift below the parameter value as well as above. + + :returns: a dict with the derivatives keyed by variable number. Derivatives are a list with three values: evaluated over v-d to v; v-d to v+d; v to v+d where v is the current value for the variable and d is a small delta value chosen for that variable type. @@ -209,7 +216,7 @@ def AllPrmDerivs(Controls,Histograms,Phases,restraintDict,rigidbodyDict, if hId != '*' and h != '' and h != hId: continue if (type(parmDict[prm]) is bool or type(parmDict[prm]) is str or type(parmDict[prm]) is int): continue - if type(parmDict[prm]) is not float and type(parmDict[prm]) is not np.float64: + if type(parmDict[prm]) is not float and type(parmDict[prm]) is not np.float64: print('*** unexpected type for ',prm,parmDict[prm],type(parmDict[prm])) continue if prm in latIgnoreLst: continue # remove unvaried lattice params @@ -221,7 +228,7 @@ def AllPrmDerivs(Controls,Histograms,Phases,restraintDict,rigidbodyDict, delta = 0.1 elif nam.startswith('AUiso'): delta = 1e-5 - if nam[0] == 'A' and nam[1] in ['x','y','z']: + if nam[0] == 'A' and nam[1] in ['x','y','z']: dprm = prm.replace('::A','::dA') if dprm in symHold: continue # held by symmetry delta = 1e-6 @@ -233,7 +240,7 @@ def AllPrmDerivs(Controls,Histograms,Phases,restraintDict,rigidbodyDict, #origVal = parmDict[dprm] parmDict[dprm] -= delta G2mv.Dict2Map(parmDict) - if dprm in latCopyDict: # apply contraints on lattice parameters + if dprm in latCopyDict: # apply contraints on lattice parameters for i in latCopyDict: parmDict[i] = parmDict[dprm] #for i in parmDict: @@ -241,7 +248,7 @@ def AllPrmDerivs(Controls,Histograms,Phases,restraintDict,rigidbodyDict, chiLow = rms(G2stMth.errRefine([],HistoPhases,parmDict,[],calcControls,pawleyLookup,None)) parmDict[dprm] += 2*delta G2mv.Dict2Map(parmDict) - if dprm in latCopyDict: # apply contraints on lattice parameters + if dprm in latCopyDict: # apply contraints on lattice parameters for i in latCopyDict: parmDict[i] = parmDict[dprm] #for i in parmDict: @@ -282,8 +289,8 @@ def RefineCore(Controls,Histograms,Phases,restraintDict,rigidbodyDict,parmDict,v #args = ([Histograms,Phases,restraintDict,rigidbodyDict],parmDict,varyList,calcControls,pawleyLookup,dlg) #print '*** before fit chi**2',np.sum(G2stMth.errRefine(values,*args)**2) #fl = open('beforeFit.cpickle','wb') - #cPickle.dump(values,fl,1) - #cPickle.dump(args[:-1],fl,1) + #pickle.dump(values,fl,1) + #pickle.dump(args[:-1],fl,1) #fl.close() Ftol = Controls['min dM/M'] Xtol = Controls['SVDtol'] @@ -436,15 +443,15 @@ def RefineCore(Controls,Histograms,Phases,restraintDict,rigidbodyDict,parmDict,v Rvals['GOF0'] = np.sqrt(chisq0/(Histograms['Nobs']-len(varyList))) return IfOK,Rvals,result,covMatrix,sig,Lastshft -def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): - '''Global refinement -- refines to minimize against all histograms. - This can be called in one of three ways, from :meth:`GSASIIdataGUI.GSASII.OnRefine` in an - interactive refinement, where dlg will be a wx.ProgressDialog, or non-interactively from +def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,newLeBail=False,allDerivs=False): + '''Global refinement -- refines to minimize against all histograms. + This can be called in one of three ways, from :meth:`GSASIIdataGUI.GSASII.OnRefine` in an + interactive refinement, where dlg will be a wx.ProgressDialog, or non-interactively from :meth:`GSASIIscriptable.G2Project.refine` or from :func:`do_refine`, where dlg will be None. ''' - import GSASIImpsubs as G2mp + from . import GSASIImpsubs as G2mp G2mp.InitMP() - import pytexture as ptx +# from . import pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics if allDerivs: @@ -456,7 +463,7 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): parmDict = {} G2mv.InitVars() Controls = G2stIO.GetControls(GPXfile) - Controls['newLeBail'] = Controls.get('newLeBail',False) + Controls['newLeBail'] = newLeBail # override value from file G2stIO.ShowControls(Controls,printFile) calcControls = {} calcControls.update(Controls) @@ -519,7 +526,7 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): # remove frozen vars from refinement if 'parmFrozen' not in Controls: Controls['parmFrozen'] = {} - if 'FrozenList' not in Controls['parmFrozen']: + if 'FrozenList' not in Controls['parmFrozen']: Controls['parmFrozen']['FrozenList'] = [] if varyList is not None: parmFrozenList = Controls['parmFrozen']['FrozenList'] @@ -529,8 +536,8 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): G2fil.G2Print( 'Frozen refined variables (due to exceeding limits)\n\t:{}' .format(frozenList)) - - ifSeq = False + + ifSeq = False printFile.write('\n Refinement results:\n') printFile.write(135*'-'+'\n') Rvals = {} @@ -588,7 +595,8 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): ) G2fil.G2Print('Note: ',msg) Rvals['msg'] += msg - G2stIO.SetUsedHistogramsAndPhases(GPXfile,Histograms,Phases,rigidbodyDict,covData,parmFrozenList,makeBack) + # save refinement results into .gpx file + G2stIO.SaveUsedHistogramsAndPhases(GPXfile,Histograms,Phases,rigidbodyDict,covData,parmFrozenList,makeBack) printFile.close() G2fil.G2Print (' Refinement results are in file: '+ospath.splitext(GPXfile)[0]+'.lst') G2fil.G2Print (' ***** Refinement successful *****') @@ -615,7 +623,7 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): except Exception as Msg: # cell metric error, others? if GSASIIpath.GetConfigValue('debug'): import traceback - print(traceback.format_exc()) + print(traceback.format_exc()) if not hasattr(Msg,'msg'): Msg.msg = str(Msg) printFile.close() G2fil.G2Print (' ***** Refinement error *****') @@ -632,13 +640,13 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): # document the refinement further: RB, constraints, restraints, what's varied Rvals['varyList'] = 'Varied: ' + ', '.join(varyList) s = G2mv.VarRemapSumm() - if s: Rvals['contrSumm'] = f'Constraints: {s}' + if s: Rvals['contrSumm'] = f'Constraints: {s}' Rvals['restrSumm'] = G2stIO.SummRestraints(restraintDict) Rvals['RBsumm'] = '' for ph in Phases: s = '' for i in 'Vector','Residue': - try: + try: l = len(Phases[ph]['RBModels'][i]) if s: s += '; ' s += f'{l} {i} bodies' @@ -647,18 +655,18 @@ def Refine(GPXfile,dlg=None,makeBack=True,refPlotUpdate=None,allDerivs=False): if s: if not Rvals['RBsumm']: Rvals['RBsumm'] += 'Rigid Bodies: ' Rvals['RBsumm'] += f'{ph}: {s}' - + #for testing purposes, create a file for testderiv if GSASIIpath.GetConfigValue('debug'): # and IfOK: #needs: values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup fl = open(ospath.splitext(GPXfile)[0]+'.testDeriv','wb') - cPickle.dump(result[0],fl,1) - cPickle.dump([Histograms,Phases,restraintDict,rigidbodyDict],fl,1) - cPickle.dump([constrDict,fixedList,G2mv.GetDependentVars()],fl,1) - cPickle.dump(parmDict,fl,1) - cPickle.dump(varyList,fl,1) - cPickle.dump(calcControls,fl,1) - cPickle.dump(pawleyLookup,fl,1) + pickle.dump(result[0],fl,1) + pickle.dump([Histograms,Phases,restraintDict,rigidbodyDict],fl,1) + pickle.dump([constrDict,fixedList,G2mv.GetDependentVars()],fl,1) + pickle.dump(parmDict,fl,1) + pickle.dump(varyList,fl,1) + pickle.dump(calcControls,fl,1) + pickle.dump(pawleyLookup,fl,1) fl.close() if dlg: return True,Rvals @@ -694,9 +702,9 @@ def DoNoFit(GPXfile,key): :param str key: name of histogram to be computed :returns: the computed diffraction pattern for the selected histogram ''' - import GSASIImpsubs as G2mp + from . import GSASIImpsubs as G2mp G2mp.InitMP() - import pytexture as ptx +# from . import pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics parmDict = {} @@ -738,27 +746,27 @@ def DoNoFit(GPXfile,key): parmDict.update(hapDict) parmDict.update(histDict) G2stIO.GetFprime(calcControls,Histograms) - + G2stMth.errRefine([],[Histograms,Phases,restraintDict,rigidbodyDict],parmDict,[],calcControls,pawleyLookup,None) return Histograms[key]['Data'][3] def DoLeBail(GPXfile,dlg=None,cycles=10,refPlotUpdate=None,seqList=None): '''Fit LeBail intensities without changes to any other refined parameters. - This is a stripped-down version of :func:`Refine` that does not perform + This is a stripped-down version of :func:`Refine` that does not perform any refinement cycles :param str GPXfile: G2 .gpx file name - :param wx.ProgressDialog dlg: optional progress window to update. - Default is None, which means no calls are made to this. + :param wx.ProgressDialog dlg: optional progress window to update. + Default is None, which means no calls are made to this. :param int cycles: Number of LeBail cycles to perform - :param function refPlotUpdate: Optional routine used to plot results. - Default is None, which means no calls are made to this. - :param list seqList: List of histograms to be processed. Default + :param function refPlotUpdate: Optional routine used to plot results. + Default is None, which means no calls are made to this. + :param list seqList: List of histograms to be processed. Default is None which means that all used histograms in .gpx file are processed. ''' - import GSASIImpsubs as G2mp + from . import GSASIImpsubs as G2mp G2mp.InitMP() - import pytexture as ptx +# from . import pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics #varyList = [] @@ -819,8 +827,8 @@ def DoLeBail(GPXfile,dlg=None,cycles=10,refPlotUpdate=None,seqList=None): covData = {'variables':0,'varyList':[],'sig':[],'Rvals':Rvals,'varyListStart':[], 'covMatrix':None,'title':GPXfile,'freshCOV':True} #np.zeros([0,0])? # ?? 'newAtomDict':newAtomDict,'newCellDict':newCellDict, - - G2stIO.SetUsedHistogramsAndPhases(GPXfile,Histograms,Phases,rigidbodyDict,covData,[],True) + + G2stIO.SaveUsedHistogramsAndPhases(GPXfile,Histograms,Phases,rigidbodyDict,covData,[],True) G2fil.G2Print (' ***** LeBail fit completed *****') return True,Rvals except Exception as Msg: @@ -828,7 +836,7 @@ def DoLeBail(GPXfile,dlg=None,cycles=10,refPlotUpdate=None,seqList=None): if not hasattr(Msg,'msg'): Msg.msg = str(Msg) if GSASIIpath.GetConfigValue('debug'): import traceback - print(traceback.format_exc()) + print(traceback.format_exc()) return False,{'msg':Msg.msg} def phaseCheck(phaseVary,Phases,histogram): @@ -860,9 +868,9 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): '''Perform a sequential refinement -- cycles through all selected histgrams, one at a time ''' - import GSASIImpsubs as G2mp + from . import GSASIImpsubs as G2mp G2mp.InitMP() - import pytexture as ptx +# from . import pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics msgs = {} printFile = open(ospath.splitext(GPXfile)[0]+'.lst','w') @@ -873,7 +881,7 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): for h in Controls['parmFrozen']: if h == 'FrozenList': continue - preFrozenCount += len(Controls['parmFrozen'][h]) + preFrozenCount += len(Controls['parmFrozen'][h]) G2stIO.ShowControls(Controls,printFile,SeqRef=True,preFrozenCount=preFrozenCount) restraintDict = G2stIO.GetRestraints(GPXfile) Histograms,Phases = G2stIO.GetUsedHistogramsAndPhases(GPXfile) @@ -902,7 +910,7 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): return False,'Phase texture refinement error - see console message' if 'Seq Data' in Controls: histNames = Controls['Seq Data'] - else: # patch from before Controls['Seq Data'] was implemented? + else: # patch from before Controls['Seq Data'] was implemented? histNames = G2stIO.GetHistogramNames(GPXfile,['PWDR',]) if Controls.get('Reverse Seq'): histNames.reverse() @@ -982,7 +990,7 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): for parm in NewparmDict: if '::' in parm and parm in parmDict: parmDict[parm] = NewparmDict[parm] - + G2stIO.GetFprime(calcControls,Histo) # do constraint processing (again, if called from GSASIIdataGUI.GSASII.OnSeqRefine) constrDict,fixedList = G2stIO.ReadConstraints(GPXfile,seqHist=hId) @@ -991,7 +999,7 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): msg = G2mv.EvaluateMultipliers(constrDict,phaseDict,hapDict,histDict) if msg: return False,'Unable to interpret multiplier(s): '+msg - + try: errmsg,warnmsg,groups,parmlist = G2mv.GenerateConstraints(varyList,constrDict,fixedList,parmDict, seqHistNum=hId,raiseException=True) @@ -1049,11 +1057,11 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): # remove frozen vars if 'parmFrozen' not in Controls: Controls['parmFrozen'] = {} - if histogram not in Controls['parmFrozen']: + if histogram not in Controls['parmFrozen']: Controls['parmFrozen'][histogram] = [] parmFrozenList = Controls['parmFrozen'][histogram] frozenList = [i for i in varyList if i in parmFrozenList] - if len(frozenList) != 0: + if len(frozenList) != 0: varyList = [i for i in varyList if i not in parmFrozenList] s = '' for a in frozenList: @@ -1085,7 +1093,7 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): sigDict = dict(zip(varyList,sig)) # add indirectly computed uncertainties into the esd dict sigDict.update(G2mv.ComputeDepESD(covMatrix,varyList)) - + newCellDict = copy.deepcopy(G2stMth.GetNewCellParms(parmDict,varyList)) newAtomDict = copy.deepcopy(G2stMth.ApplyXYZshifts(parmDict,varyList)) SeqResult[histogram] = { @@ -1121,11 +1129,11 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): sig[i] = -0.1 # a dict with values & esds for dependent (constrained) parameters - avoid extraneous holds SeqResult[histogram]['depParmDict'] = {i:(parmDict[i],sigDict[i]) for i in sigDict if i not in varyList} - - + + G2stIO.SaveUpdatedHistogramsAndPhases(GPXfile,Histo,Phases, rigidbodyDict,SeqResult[histogram],Controls['parmFrozen']) - if msg: + if msg: printFile.write(msg+'\n') NewparmDict = {} # make dict of varied parameters in current histogram, renamed to @@ -1135,16 +1143,16 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): nexthId = Histograms[histNames[ihst+1]]['hId'] for parm in set(list(varyList)+list(varyListStart)): items = parm.split(':') - if len(items) < 3: + if len(items) < 3: continue if str(hId) in items[1]: items[1] = str(nexthId) newparm = ':'.join(items) NewparmDict[newparm] = parmDict[parm] else: - if items[2].startswith('dA'): parm = parm.replace(':dA',':A') + if items[2].startswith('dA'): parm = parm.replace(':dA',':A') NewparmDict[parm] = parmDict[parm] - + except G2obj.G2RefineCancel as Msg: if not hasattr(Msg,'msg'): Msg.msg = str(Msg) printFile.close() @@ -1166,7 +1174,7 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): print('Error reading Sequential results\n',str(msg)) if GSASIIpath.GetConfigValue('debug'): import traceback - print(traceback.format_exc()) + print(traceback.format_exc()) postFrozenCount = 0 for h in Controls['parmFrozen']: if h == 'FrozenList': continue @@ -1180,9 +1188,9 @@ def SeqRefine(GPXfile,dlg,refPlotUpdate=None): return True,msgs def dropOOBvars(varyList,parmDict,sigDict,Controls,parmFrozenList): - '''Find variables in the parameters dict that are outside the ranges - (in parmMinDict and parmMaxDict) and set them to the limits values. - Add any such variables into the list of frozen variable + '''Find variables in the parameters dict that are outside the ranges + (in parmMinDict and parmMaxDict) and set them to the limits values. + Add any such variables into the list of frozen variable (parmFrozenList). Returns a list of newly frozen variables, if any. ''' parmMinDict = Controls.get('parmMinDict',{}) @@ -1225,9 +1233,9 @@ def RetDistAngle(DisAglCtls,DisAglData,dlg=None): :param dict DisAglData: contains phase & refinement data: * 'OrigAtoms' and 'TargAtoms' contain the atoms to be used - for distance/angle origins and atoms to be used as targets. + for distance/angle origins and atoms to be used as targets. * 'OrigIndx' contains the index numbers for the Origin atoms. - * 'SGData' has the space group information (see + * 'SGData' has the space group information (see :ref:`Space Group object`) * 'pId' has the phase id * 'Cell' has the unit cell parameters and cell volume @@ -1237,7 +1245,7 @@ def RetDistAngle(DisAglCtls,DisAglData,dlg=None): * 'RBlist' has the index numbers for atoms in a rigid body * 'rigidbodyDict' the contents of the main Rigid Body data tree item - * 'Phases' has the phase information for all used phases in the + * 'Phases' has the phase information for all used phases in the data tree. Only the current phase is needed, but this is easy. * 'parmDict' is the GSAS-II parameter dict @@ -1250,7 +1258,7 @@ def RetDistAngle(DisAglCtls,DisAglData,dlg=None): 0) the target atom number (int); 1) the unit cell offsets added to x,y & z (tuple of int values); - 2) the symmetry transformation, which includes the symmetry operator + 2) the symmetry transformation, which includes the symmetry operator number, any centering, if a center of symmetry was applied; 3) an interatomic distance in A (float); 4) an uncertainty on the distance in A or 0.0 (float). @@ -1261,7 +1269,7 @@ def RetDistAngle(DisAglCtls,DisAglData,dlg=None): 0) a distance item reference for one neighbor atom (int); 1) a distance item reference for the second neighbor atom (int); - 2) a angle, uncertainty pair; the s.u. may be zero (degrees, tuple of + 2) a angle, uncertainty pair; the s.u. may be zero (degrees, tuple of two floats). The AngArray distance reference items refer directly to the index of the items in the @@ -1411,7 +1419,7 @@ def ShowBanner(name): covData = DisAglData['covData'] pfx = str(DisAglData['pId'])+'::' A = G2lat.cell2A(Cell[:6]) - cellSig = G2stIO.getCellEsd(pfx,SGData,A,covData) + cellSig = G2lat.getCellEsd(pfx,SGData,A,covData) names = [' a = ',' b = ',' c = ',' alpha = ',' beta = ',' gamma = ',' Volume = '] valEsd = [G2mth.ValEsd(Cell[i],cellSig[i],True) for i in range(7)] line = '\n Unit cell:' @@ -1470,7 +1478,7 @@ def ShowBanner(name): Tatm = G2elem.FixValence(DisAglData['TargAtoms'][dist[0]][2]).split('-')[0] if Tatm in ['O','F','Cl']: for BV in BVox: - BVS[BV] += np.exp((BVdat[BV][Tatm]-dist[3])/0.37) + BVS[BV] += np.exp((BVdat[BV][Tatm]-dist[3])/0.37) tunit = '[%2d%2d%2d]'% dist[1] MyPrint((' %8s%10s+(%4d) %12s'%(AtomLabels[dist[0]].ljust(8),tunit.ljust(10),dist[2],val.center(12)))+line.rstrip()) if len(BVox): @@ -1578,7 +1586,7 @@ def do_refine(*args): # TODO: test below # figure out if this is a sequential refinement and call SeqRefine(GPXfile,None) #Controls = G2stIO.GetControls(GPXfile) - #if Controls.get('Seq Data',[]): + #if Controls.get('Seq Data',[]): Refine(GPXfile,None) #else: # SeqRefine(GPXfile,None) diff --git a/GSASII/GSASIIstrMath.py b/GSASII/GSASIIstrMath.py index 93d7ef83e..9b63d8f57 100644 --- a/GSASII/GSASIIstrMath.py +++ b/GSASII/GSASIIstrMath.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- ''' -:mod:`GSASIIstrMath` routines, found below, used to support -refinement-related computations. These routines are used primarily in -:mod:`GSASIIstrMain` and :mod:`GSASIIstrIO`, but also in a few other routines +:mod:`GSASIIstrMath` routines, found below, used to support +refinement-related computations. These routines are used primarily in +:mod:`GSASIIstrMain` and :mod:`GSASIIstrIO`, but also in a few other routines in other locations: -The :meth:`GSASIIfiles.ExportBaseclass.loadParmDict` routine accesses routine -:func:`computeRBsu`, :meth:`GSASIIdataGUI.GSASII.OnExpressionCalc` -accesses :func:`ApplyRBModels` and in module :mod:`testDeriv` routines -:func:`errRefine` and :func:`dervRefine` are accessed a several places. - -The routines here are most commonly called when working from a .gpx file, but -may sometimes be called from the GUI. These routines expect that all needed -input will have been read from the file/tree and are passed to the +The :meth:`GSASIIfiles.ExportBaseclass.loadParmDict` routine accesses routine +:func:`computeRBsu`, :meth:`GSASIIdataGUI.GSASII.OnExpressionCalc` +accesses :func:`ApplyRBModels` and in module :mod:`testDeriv` routines +:func:`errRefine` and :func:`dervRefine` are accessed a several places. + +The routines here are most commonly called when working from a .gpx file, but +may sometimes be called from the GUI. These routines expect that all needed +input will have been read from the file/tree and are passed to the routines as arguments. The data tree is never accessed directly here. ''' @@ -25,16 +25,23 @@ import scipy.special as sp import multiprocessing as mp import pickle -import GSASIIpath -import GSASIIElem as G2el -import GSASIIlattice as G2lat -import GSASIIspc as G2spc -import GSASIIpwd as G2pwd -import GSASIImapvars as G2mv -import GSASIImath as G2mth -import GSASIIobj as G2obj -import GSASIImpsubs as G2mp -#G2mp.InitMP(False) # This disables multiprocessing +from . import GSASIIpath +from . import GSASIIElem as G2el +from . import GSASIIlattice as G2lat +from . import GSASIIspc as G2spc +from . import GSASIIpwd as G2pwd +from . import GSASIImapvars as G2mv +from . import GSASIImath as G2mth +from . import GSASIIobj as G2obj +from . import GSASIImpsubs as G2mp +#G2mp.InitMP(False) # This disables multiprocessing +try: + if GSASIIpath.binaryPath: + import pytexture as ptx + else: + from . import pytexture as ptx +except ImportError: # ignore; will report this as an error in GSASIIplot import + pass sind = lambda x: np.sin(x*np.pi/180.) cosd = lambda x: np.cos(x*np.pi/180.) @@ -42,7 +49,7 @@ asind = lambda x: 180.*np.arcsin(x)/np.pi acosd = lambda x: 180.*np.arccos(x)/np.pi atan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi - + try: # fails on doc build ateln2 = 8.0*np.log(2.0) twopi = 2.0*np.pi @@ -55,7 +62,7 @@ ################################################################################ ##### Rigid Body Models ################################################################################ - + def ApplyRBModels(parmDict,Phases,rigidbodyDict,Update=False): '''Takes RB info from RBModels in Phase and RB data in rigidbodyDict along with current RB values in parmDict & modifies atom contents (fxyz & Uij) of parmDict @@ -91,7 +98,7 @@ def ApplyRBModels(parmDict,Phases,rigidbodyDict,Update=False): for j in range(len(VRBData[rbId]['VectMag'])): name = '::RBV;'+str(j)+':'+str(i) VRBData[rbId]['VectMag'][j] = parmDict[name] - + for phase in Phases: Phase = Phases[phase] General = Phase['General'] @@ -143,14 +150,14 @@ def ApplyRBModels(parmDict,Phases,rigidbodyDict,Update=False): elif UIJ[i][0] == 'I': parmDict[pfx+'AUiso:'+str(AtLookup[atId])] = UIJ[i][1] changedPrms.append(pfx+'AUiso:'+str(AtLookup[atId])) - + for irb,RBObj in enumerate(RBModels.get('Residue',[])): jrb = RRBIds.index(RBObj['RBId']) rbsx = str(irb)+':'+str(jrb) for i,px in enumerate(['RBRPx:','RBRPy:','RBRPz:']): RBObj['Orig'][0][i] = parmDict[pfx+px+rbsx] for i,po in enumerate(['RBROa:','RBROi:','RBROj:','RBROk:']): - RBObj['Orient'][0][i] = parmDict[pfx+po+rbsx] + RBObj['Orient'][0][i] = parmDict[pfx+po+rbsx] RBObj['Orient'][0] = G2mth.normQ(RBObj['Orient'][0]) RBObj['AtomFrac'][0] = parmDict[pfx+'RBRf:'+rbsx] TLS = RBObj['ThermalMotion'] @@ -184,14 +191,14 @@ def ApplyRBModels(parmDict,Phases,rigidbodyDict,Update=False): elif UIJ[i][0] == 'I': parmDict[pfx+'AUiso:'+str(AtLookup[atId])] = UIJ[i][1] changedPrms.append(pfx+'AUiso:'+str(AtLookup[atId])) - + for irb,RBObj in enumerate(RBModels.get('Spin',[])): iAt = AtLookup[RBObj['Ids'][0]] jrb = SRBIds.index(RBObj['RBId'][0]) name = pfx+'RBSOa:%d:%d'%(iAt,jrb) for i,po in enumerate(['RBSOa:','RBSOi:','RBSOj:','RBSOk:']): name = pfx+'%s%d:%d'%(po,iAt,jrb) - RBObj['Orient'][0][i] = parmDict[name] + RBObj['Orient'][0][i] = parmDict[name] for ish in range(len(RBObj['RBId'])): jrb = SRBIds.index(RBObj['RBId'][ish]) if 'Q' not in RBObj['atType']: @@ -201,7 +208,7 @@ def ApplyRBModels(parmDict,Phases,rigidbodyDict,Update=False): name = pfx+'RBSSh;%d;%s:%d:%d'%(ish,item,iAt,jrb) RBObj['SHC'][ish][item][0] = parmDict[name] return changedPrms - + def ApplyRBModelDervs(dFdvDict,parmDict,rigidbodyDict,Phase): 'Computes rigid body derivatives w/r to RB params; N.B.: there are none for Spin RBs' atxIds = ['dAx:','dAy:','dAz:'] @@ -228,7 +235,7 @@ def ApplyRBModelDervs(dFdvDict,parmDict,rigidbodyDict,Phase): AtLookup = G2mth.FillAtomLookUp(Phase['Atoms'],cia+8) pfx = str(Phase['pId'])+'::' RBModels = Phase['RBModels'] - + for irb,RBObj in enumerate(RBModels.get('Vector',[])): symAxis = RBObj.get('symAxis') VModel = RBData['Vector'][RBObj['RBId']] @@ -264,7 +271,7 @@ def ApplyRBModelDervs(dFdvDict,parmDict,rigidbodyDict,Phase): X = G2mth.prodQVQ(Q,Cart[ia]) dFdu = np.array([dFdvDict[pfx+Uid+str(AtLookup[atId])] for Uid in atuIds]).T/gvec dFdu = G2lat.U6toUij(dFdu.T) - dFdu = np.tensordot(Amat,np.tensordot(Amat,dFdu,([1,0])),([0,1])) + dFdu = np.tensordot(Amat,np.tensordot(Amat,dFdu,([1,0])),([0,1])) dFdu = G2lat.UijtoU6(dFdu) atNum = AtLookup[atId] if 'T' in RBObj['ThermalMotion'][0]: @@ -300,7 +307,7 @@ def ApplyRBModelDervs(dFdvDict,parmDict,rigidbodyDict,Phase): rbsx = str(irb)+':'+str(jrb) XYZ,Cart = G2mth.UpdateRBXYZ(Bmat,RBObj,RBData,'Residue') for itors,tors in enumerate(RBObj['Torsions']): #derivative error? - tname = pfx+'RBRTr;'+str(itors)+':'+rbsx + tname = pfx+'RBRTr;'+str(itors)+':'+rbsx orId,pvId = torData[itors][:2] pivotVec = Cart[orId]-Cart[pvId] QA = G2mth.AVdeg2Q(-0.001,pivotVec) @@ -358,7 +365,7 @@ def ApplyRBModelDervs(dFdvDict,parmDict,rigidbodyDict,Phase): dFdvDict[pfx+'RBRSBB:'+rbsx] += rpd*(dFdu[5]*X[0]-dFdu[3]*X[2]) if 'U' in RBObj['ThermalMotion'][0]: dFdvDict[pfx+'RBRU:'+rbsx] += dFdvDict[pfx+'AUiso:'+str(AtLookup[atId])] - + def computeRBsu(parmDict,Phases,rigidbodyDict,covMatrix,CvaryList,Csig): '''Computes s.u. values for atoms in rigid bodies @@ -369,11 +376,11 @@ def computeRBsu(parmDict,Phases,rigidbodyDict,covMatrix,CvaryList,Csig): :param np.array CvaryList: list of refined parameters (length N) :param np.array Csig: s.u. values for items in CvaryList (length N) - :returns: a dict with s.u. values for parameters that are generated + :returns: a dict with s.u. values for parameters that are generated by Rigid Bodies. Will be an empty dict if there are no RBs in use. ''' def extendChanges(prms): - '''Propagate changes due to constraint and rigid bodies + '''Propagate changes due to constraint and rigid bodies from varied parameters to dependent parameters ''' # apply constraints @@ -391,7 +398,7 @@ def extendChanges(prms): prms = copy.deepcopy(parmDict) if len(covMatrix) == 0: return {} - + changedPrms = ApplyRBModels(parmDict,Phases,RBData) if changedPrms is None: return {} # evaluate the derivatives w/each atom parm with respect to the varied parms @@ -421,7 +428,7 @@ def extendChanges(prms): RBsu[k] = np.sqrt(np.inner(Avec.T,np.inner(covMatrix,Avec))) return RBsu -def MakeSpHarmFF(HKL,Bmat,SHCdict,Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ,ifDeriv=False): +def MakeSpHarmFF(HKL,Amat,Bmat,SHCdict,Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ,ifDeriv=False): ''' Computes hkl dependent form factors & derivatives from spinning rigid bodies :param array HKL: reflection hkl set to be considered :param array Bmat: inv crystal to Cartesian transfomation matrix @@ -434,22 +441,21 @@ def MakeSpHarmFF(HKL,Bmat,SHCdict,Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ, :param array FF: form factors - will be modified by adding the spin/deformation RB spherical harmonics terms :param array SQ: 1/4d^2 for the HKL set :param bool ifDeriv: True if dFF/dcoff to be returned - + :returns: dict dFFdS of derivatives if ifDeriv = True ''' - + def MakePolar(Orient,QB): QA = G2mth.invQ(Orient) #rotates about chosen axis - Q = G2mth.prodQQ(QB,QA) #might be switched? QB,QA is order for plotting - return G2lat.H2ThPh(np.reshape(HKL,(-1,3)),Bmat,Q) - + Q = G2mth.prodQQ(QA,QB) #might be switched? QB,QA is order for plotting + M = np.inner(G2mth.Q2Mat(Q),Bmat) + return G2lat.H2ThPh2(np.reshape(HKL,(-1,3)),M)[1:] + dFFdS = {} atFlg = [] - Th,Ph = G2lat.H2ThPh(np.reshape(HKL,(-1,3)),Bmat,[1.,0.,0.,1.]) SQR = np.repeat(SQ,HKL.shape[1]) for iAt,Atype in enumerate(Tdata): - if 'Q' in Atype: - Th,Ph = G2lat.H2ThPh(np.reshape(HKL,(-1,3)),Bmat,[1.,0.,0.,1.]) + if 'Q' in Atype: #spinning RB atFlg.append(1.0) SHdat = SHCdict[iAt] symAxis = np.array(SHdat['symAxis']) @@ -519,7 +525,7 @@ def MakePolar(Orient,QB): SHMk = G2lat.KslCalc(item,ThMk,PhMk) BS = 1.0 if 'Q' in Atm: - BS = sp.spherical_jn(l,1.0) #Slater term here? + BS = sp.spherical_jn(l,1.0)/(4.*np.pi) #Slater term here? else: BS = sp.spherical_jn(l,QR*R)/(4.*np.pi) #Bessel function BSP = sp.spherical_jn(l,QR*(R+0.01))/(4.*np.pi) @@ -539,7 +545,7 @@ def MakePolar(Orient,QB): dFFdS[Oiname] = dSHdOi dFFdS[Ojname] = dSHdOj dFFdS[Okname] = dSHdOk - elif iAt in SHCdict and 'X' in hType: + elif iAt in SHCdict and 'X' in hType: #X-ray deformation radial = SHCdict[-iAt]['Radial'] orKeys = [item for item in ORBtables[Atype] if item not in ['Slater','ZSlater','NSlater','SZE','popCore','popVal']] if 'B' in radial: @@ -548,7 +554,8 @@ def MakePolar(Orient,QB): orKeys = [item for item in orKeys if 'Sl' in item] orbs = SHCdict[iAt] UVmat = np.inner(nl.inv(SHCdict[-iAt]['UVmat']),Bmat) - Th,Ph = G2lat.H2ThPh(np.reshape(HKL,(-1,3)),UVmat,[1.,0.,0.,1.]) + R,Th,Ph = G2lat.H2ThPh2(np.reshape(HKL,(-1,3)),UVmat) + R = 1/R # correct dspacings atFlg.append(1.0) orbTable = ORBtables[Atype][orKeys[0]] # should point at either Sl core or a Bessel core ffOrb = {item:orbTable[item] for item in orbTable if item not in ['Slater','ZSlater','NSlater','SZE','popCore','popVal']} @@ -604,8 +611,8 @@ def MakePolar(Orient,QB): else: atFlg.append(0.) if ifDeriv: - return dFFdS,atFlg - + return dFFdS,atFlg + def GetSHC(pfx,parmDict): SHCdict = {} for parm in parmDict: @@ -647,9 +654,9 @@ def GetSHC(pfx,parmDict): if len(SHCdict): return {pfx:SHCdict,} else: return {} - + ################################################################################ -##### Penalty & restraint functions +##### Penalty & restraint functions ################################################################################ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): @@ -713,7 +720,7 @@ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): restr,calc = G2mth.calcTorsionEnergy(tor,coeffDict[cofName]) else: phi,psi = G2mth.getRestRama(XYZ,Amat) - restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName]) + restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName]) pVals.append(restr) pWt.append(wt/esd**2) pWsum[name] += wt*(restr/esd)**2 @@ -725,7 +732,7 @@ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): frac = np.array(G2mth.GetAtomFracByID(pId,parmDict,AtLookup,indx)) calc = np.sum(mul*frac*factors) pVals.append(obs-calc) - pWt.append(wt/esd**2) + pWt.append(wt/esd**2) pWsum[name] += wt*((obs-calc)/esd)**2 pWnum[name] += 1 elif name == 'Moments': @@ -740,7 +747,7 @@ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): obs /= len(indx) for calc in calcs: pVals.append(obs-calc) - pWt.append(wt/esd**2) + pWt.append(wt/esd**2) pWsum[name] += wt*((obs-calc)/esd)**2 pWnum[name] += 1 @@ -777,7 +784,7 @@ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): calc = calcobj.EvalExpression() try: pVals.append(obs-calc) - pWt.append(wt/esd**2) + pWt.append(wt/esd**2) pWsum[name] += wt*((obs-calc)/esd)**2 pWnum[name] += 1 pNames.append(str(pId)+':'+name+':'+str(i)) @@ -810,7 +817,7 @@ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): SH3Coef = {} for item in SHcof: L,N = eval(item.strip('C')) - SH3Coef['C%d,0,%d'%(L,N)] = SHcof[item] + SH3Coef['C%d,0,%d'%(L,N)] = SHcof[item] ODFln = G2lat.Flnh(SH3Coef,phi,beta,SGData) X = np.linspace(0,90.0,26) Y = ma.masked_greater(G2lat.polfcal(ODFln,'0',X,0.0),0.0) #+ or -? @@ -835,14 +842,14 @@ def penaltyFxn(HistoPhases,calcControls,parmDict,varyList): pVals = np.array(pVals) pWt = np.array(pWt) #should this be np.sqrt? return pNames,pVals,pWt,pWsum,pWnum - + def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): - '''Compute derivatives on user-supplied and built-in restraint + '''Compute derivatives on user-supplied and built-in restraint (penalty) functions where pNames is list of restraint labels - :returns: array pDerv: partial derivatives by variable# in varList and + :returns: array pDerv: partial derivatives by variable# in varList and restraint# in pNames (pDerv[variable#][restraint#]) ''' Histograms,Phases,restraintDict,rigidbodyDict = HistoPhases @@ -887,7 +894,7 @@ def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): continue elif 'SH-' in pName: continue - Id = int(pnames[2]) + Id = int(pnames[2]) itemRest = phaseRest[name] if name in ['Bond','Angle','Plane','Chiral']: indx,ops,obs,esd = itemRest[names[name]][Id] @@ -929,7 +936,7 @@ def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): for i,ind in enumerate(indx): calc = G2mth.GetMag(moms[i],cell) dNames += [str(pId)+'::'+Xname+':'+str(AtLookup[ind]) for Xname in ['AMx','AMy','AMz']] - deriv += list(G2mth.GetMagDerv(moms[i],cell)*np.sign((obs-calc))) + deriv += list(G2mth.GetMagDerv(moms[i],cell)*np.sign((obs-calc))) elif 'Texture' in name: deriv = [] dNames = [] @@ -938,7 +945,7 @@ def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): if np.any(lasthkl-hkl): phi,beta = G2lat.CrsAng(np.array(hkl),cell,SGData) ODFln = G2lat.Flnh(SHCoef,phi,beta,SGData) - lasthkl = copy.copy(hkl) + lasthkl = copy.copy(hkl) if 'unit' in name: pass else: @@ -999,7 +1006,7 @@ def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): pass except: pass - + lasthkl = np.array([0,0,0]) for ip,pName in enumerate(pNames): deriv = [] @@ -1012,15 +1019,15 @@ def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): HKLs = calcControls[phfx+'SHhkl'] SHnames = calcControls[phfx+'SHnames'] SHcof = dict(zip(SHnames,[parmDict[phfx+cof] for cof in SHnames])) - hkl = np.array(HKLs[int(pnames[3])]) + hkl = np.array(HKLs[int(pnames[3])]) if np.any(lasthkl-hkl): phi,beta = G2lat.CrsAng(np.array(hkl),cell,SGData) SH3Coef = {} for item in SHcof: L,N = eval(item.strip('C')) - SH3Coef['C%d,0,%d'%(L,N)] = SHcof[item] + SH3Coef['C%d,0,%d'%(L,N)] = SHcof[item] ODFln = G2lat.Flnh(SH3Coef,phi,beta,SGData) - lasthkl = copy.copy(hkl) + lasthkl = copy.copy(hkl) for SHname in SHnames: l,n = eval(SHname[1:]) SH3name = 'C%d,0,%d'%(l,n) @@ -1037,8 +1044,8 @@ def penaltyDeriv(pNames,pVal,HistoPhases,calcControls,parmDict,varyList): ################################################################################ ##### Function & derivative calculations -################################################################################ - +################################################################################ + def GetAtomFXU(pfx,calcControls,parmDict): 'Needs a doc string' Natoms = calcControls['Natoms'][pfx] @@ -1064,9 +1071,9 @@ def GetAtomFXU(pfx,calcControls,parmDict): keys[key][iatm] = parmDict[parm] Fdata = np.where(Fdata,Fdata,1.e-8) #avoid divide by zero in derivative calc. Gdata = np.where(Gdata,Gdata,1.e-8) #avoid divide by zero in derivative calc. - + return Tdata,Mdata,Fdata,Xdata,dXdata,IAdata,Uisodata,Uijdata,Gdata - + def GetAtomSSFXU(pfx,calcControls,parmDict): 'Needs a doc string' Natoms = calcControls['Natoms'][pfx] @@ -1093,13 +1100,13 @@ def GetAtomSSFXU(pfx,calcControls,parmDict): if parm in parmDict: keys[key][m][iatm] = parmDict[parm] return waveTypes,FSSdata,XSSdata,USSdata,MSSdata - + def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): ''' Compute structure factors for all h,k,l for phase puts the result, F^2, in each ref[8] in refList operates on blocks of 100 reflections for speed input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,it,d,... 'FF' dict of form factors - filed in below @@ -1109,7 +1116,7 @@ def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): :param dict calcControls: :param dict ParmDict: - ''' + ''' phfx = pfx.split(':')[0]+hfx ast = np.sqrt(np.diag(G)) Mast = twopisq*np.multiply.outer(ast,ast) @@ -1125,7 +1132,7 @@ def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): Flack = 1.-2.*parmDict[phfx+'Flack'] TwinLaw = np.array([[[1,0,0],[0,1,0],[0,0,1]],]) TwDict = refDict.get('TwDict',{}) - hType = calcControls[hfx+'histType'] + hType = calcControls[hfx+'histType'] if 'S' in hType: NTL = calcControls[phfx+'NTL'] NM = calcControls[phfx+'TwinNMN']+1 @@ -1158,7 +1165,7 @@ def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): refDict['FF']['El'] = list(dat.keys()) refDict['FF']['FF'] = np.zeros((nRef,len(dat))) for iel,El in enumerate(refDict['FF']['El']): - refDict['FF']['FF'].T[iel] = G2el.ScatFac(EFtables[El],SQ) + refDict['FF']['FF'].T[iel] = G2el.ScatFac(EFtables[El],SQ) else: #'X' dat = G2el.getFFvalues(FFtables,0.) refDict['FF']['El'] = list(dat.keys()) @@ -1207,7 +1214,7 @@ def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): #FF has to have the Bessel*Sph.Har.*atm form factor for each refletion in Uniq for Q atoms; otherwise just normal FF #this must be done here. NB: same place for non-spherical atoms; same math except no Bessel part. if pfx in SHCdict: - MakeSpHarmFF(Uniq,Bmat,SHCdict[pfx],Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ) #Not Amat! + MakeSpHarmFF(Uniq,Amat,Bmat,SHCdict[pfx],Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ) #Not Amat! Bab = np.repeat(parmDict[phfx+'BabA']*np.exp(-parmDict[phfx+'BabU']*SQfactor),len(SGT)*len(TwinLaw)) if 'T' in calcControls[hfx+'histType']: #fa,fb are 2 X blkSize X nTwin X nOps x nAtoms fa = np.array([np.reshape(((FF+FP).T-Bab).T,cosp.shape)*cosp*Tcorr,-np.reshape(Flack*FPP,sinp.shape)*sinp*Tcorr]) @@ -1216,12 +1223,12 @@ def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): fa = np.array([np.reshape(((FF+FP).T-Bab).T,cosp.shape)*cosp*Tcorr,-Flack*FPP*sinp*Tcorr]) fb = np.array([np.reshape(((FF+FP).T-Bab).T,sinp.shape)*sinp*Tcorr,Flack*FPP*cosp*Tcorr]) fas = np.sum(np.sum(fa,axis=-1),axis=-1) #real 2 x blkSize x nTwin; sum over atoms & uniq hkl - fbs = np.sum(np.sum(fb,axis=-1),axis=-1) #imag + fbs = np.sum(np.sum(fb,axis=-1),axis=-1) #imag if SGData['SGInv']: #centrosymmetric; B=0 fbs[0] *= 0. fas[1] *= 0. if 'P' in hType: #PXC, PNC & PNT: F^2 = A[0]^2 + A[1]^2 + B[0]^2 + B[1]^2 - refl.T[9] = np.sum(fas**2,axis=0)+np.sum(fbs**2,axis=0) + refl.T[9] = np.sum(fas**2,axis=0)+np.sum(fbs**2,axis=0) refl.T[10] = atan2d(fbs[0],fas[0]) #ignore f' & f" else: #HKLF: F^2 = (A[0]+A[1])^2 + (B[0]+B[1])^2 if len(TwinLaw) > 1: @@ -1231,17 +1238,17 @@ def StructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): refl.T[10] = atan2d(fbs[0].T[0],fas[0].T[0]) #ignore f' & f" & use primary twin else: # checked correct!! refl.T[9] = np.sum(fas,axis=0)**2+np.sum(fbs,axis=0)**2 - refl.T[7] = np.copy(refl.T[9]) + refl.T[7] = np.copy(refl.T[9]) refl.T[10] = atan2d(fbs[0],fas[0]) #ignore f' & f" # refl.T[10] = atan2d(np.sum(fbs,axis=0),np.sum(fas,axis=0)) #include f' & f" iBeg += blkSize # print 'sf time %.4f, nref %d, blkSize %d'%(time.time()-time0,nRef,blkSize) - + def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): '''Compute structure factor derivatives on blocks of reflections - for powders/nontwins only faster than StructureFactorDerv - correct for powders/nontwins!! input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,it,d,... 'FF' dict of form factors - filled in below @@ -1251,7 +1258,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): :param dict SGData: space group info. dictionary output from SpcGroup :param dict calcControls: :param dict parmDict: - + :returns: dict dFdvDict: dictionary of derivatives ''' phfx = pfx.split(':')[0]+hfx @@ -1262,7 +1269,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): FFtables = calcControls['FFtables'] ORBtables = calcControls['ORBtables'] BLtables = calcControls['BLtables'] - hType = calcControls[hfx+'histType'] + hType = calcControls[hfx+'histType'] Amat,Bmat = G2lat.Gmat2AB(G) nRef = len(refDict['RefList']) Tdata,Mdata,Fdata,Xdata,dXdata,IAdata,Uisodata,Uijdata,Gdata = \ @@ -1330,7 +1337,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): Hij = np.array([Mast*np.multiply.outer(U,U) for U in np.reshape(Uniq,(-1,3))]) #Nref*Nops,3,3 Hij = np.reshape(np.array([G2lat.UijtoU6(uij) for uij in Hij]),(-1,len(SGT),6)) #Nref,Nops,6 if pfx in SHCdict: - dffdsh,atFlg = MakeSpHarmFF(Uniq,Bmat,SHCdict[pfx],Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ,True) + dffdsh,atFlg = MakeSpHarmFF(Uniq,Amat,Bmat,SHCdict[pfx],Tdata,hType,FFtables,ORBtables,BLtables,FF,SQ,True) if len(dffdSH): for item in dffdSH: dffdSH[item] = np.concatenate((dffdSH[item],dffdsh[item])) @@ -1340,7 +1347,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): if len(FPP.shape) > 1: fotp = np.reshape(FPP,cosp.shape)*Tcorr else: - fotp = FPP*Tcorr + fotp = FPP*Tcorr if 'T' in calcControls[hfx+'histType']: fa = np.array([fot*cosp,-np.reshape(Flack*FPP,sinp.shape)*sinp*Tcorr]) fb = np.array([fot*sinp,np.reshape(Flack*FPP,cosp.shape)*cosp*Tcorr]) @@ -1364,7 +1371,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dfbdba = np.sum(-sinp*Tcorr,axis=-2) dfadfl = np.sum(np.sum(-fotp*sinp,axis=-1),axis=-1) dfbdfl = np.sum(np.sum(fotp*cosp,axis=-1),axis=-1) - dfbdx = np.sum(twopi*Uniq[nxs,:,nxs,:,:]*np.swapaxes(fbx,-2,-1)[:,:,:,:,nxs],axis=-2) + dfbdx = np.sum(twopi*Uniq[nxs,:,nxs,:,:]*np.swapaxes(fbx,-2,-1)[:,:,:,:,nxs],axis=-2) dfbdui = np.sum(-SQfactor[nxs,:,nxs,nxs]*fb,axis=-2) dfbdua = np.sum(-Hij[nxs,:,nxs,:,:]*np.swapaxes(fb,-2,-1)[:,:,:,:,nxs],axis=-2) else: @@ -1376,7 +1383,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dfbdba = np.zeros_like(dfadba) dfadfl = 0.0 dfbdfl = 0.0 - #NB: the above have been checked against PA(1:10,1:2) in strfctr.for for Al2O3! + #NB: the above have been checked against PA(1:10,1:2) in strfctr.for for Al2O3! SA = fas[0]+fas[1] SB = fbs[0]+fbs[1] if 'P' in calcControls[hfx+'histType']: #checked perfect for centro & noncentro @@ -1410,7 +1417,7 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dFdvDict[pfx+'AU13:'+str(i)] = dFdua.T[4][i] dFdvDict[pfx+'AU23:'+str(i)] = dFdua.T[5][i] for item in dffdSH: - if 'SH' in item or 'O' in item: + if 'Sh' in item or 'O' in item: if i == int(item.split(':')[1]): dFdvDict[pfx+'RBS'+item] = np.sum(dFdff[:,:,i]*np.reshape(dffdSH[item],(nRef,-1)),axis=1) else: @@ -1420,13 +1427,13 @@ def StructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dFdvDict[phfx+'BabA'] = dFdbab.T[0] dFdvDict[phfx+'BabU'] = dFdbab.T[1] return dFdvDict - + def MagStructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): ''' Compute neutron magnetic structure factors for all h,k,l for phase puts the result, F^2, in each ref[8] in refList operates on blocks of 100 reflections for speed input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,it,d,... 'FF' dict of form factors - filed in below @@ -1435,10 +1442,10 @@ def MagStructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): :param dict SGData: space group info. dictionary output from SpcGroup :param dict calcControls: :param dict ParmDict: - + :returns: copy of new refList - used in calculating numerical derivatives - ''' + ''' g = nl.inv(G) ast = np.sqrt(np.diag(G)) ainv = np.sqrt(np.diag(g)) @@ -1456,7 +1463,7 @@ def MagStructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): MFtables = calcControls['MFtables'] TwinLaw = np.ones(1) # TwinLaw = np.array([[[1,0,0],[0,1,0],[0,0,1]],]) -# TwDict = refDict.get('TwDict',{}) +# TwDict = refDict.get('TwDict',{}) # if 'S' in calcControls[hfx+'histType']: # NTL = calcControls[phfx+'NTL'] # NM = calcControls[phfx+'TwinNMN']+1 @@ -1534,10 +1541,10 @@ def MagStructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): fams = np.sum(np.sum(fam,axis=-1),axis=-1) #Mxyz,Nref Sum(sum(fam,atoms),ops) fbms = np.sum(np.sum(fbm,axis=-1),axis=-1) #ditto refl.T[9] = np.sum(fams**2,axis=0)+np.sum(fbms**2,axis=0) #Sum(fams**2,Mxyz) Re + Im - refl.T[7] = np.copy(refl.T[9]) + refl.T[7] = np.copy(refl.T[9]) refl.T[10] = atan2d(fbms[0],fams[0]) #- what is phase for mag refl? # if 'P' in calcControls[hfx+'histType']: #PXC, PNC & PNT: F^2 = A[0]^2 + A[1]^2 + B[0]^2 + B[1]^2 -# refl.T[9] = np.sum(fas**2,axis=0)+np.sum(fbs**2,axis=0) #add fam**2 & fbm**2 here +# refl.T[9] = np.sum(fas**2,axis=0)+np.sum(fbs**2,axis=0) #add fam**2 & fbm**2 here # refl.T[10] = atan2d(fbs[0],fas[0]) #ignore f' & f" # else: #HKLF: F^2 = (A[0]+A[1])^2 + (B[0]+B[1])^2 # if len(TwinLaw) > 1: @@ -1547,7 +1554,7 @@ def MagStructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): # refl.T[10] = atan2d(fbs[0].T[0],fas[0].T[0]) #ignore f' & f" & use primary twin # else: # checked correct!! # refl.T[9] = np.sum(fas,axis=0)**2+np.sum(fbs,axis=0)**2 -# refl.T[7] = np.copy(refl.T[9]) +# refl.T[7] = np.copy(refl.T[9]) # refl.T[10] = atan2d(fbs[0],fas[0]) #ignore f' & f" ## refl.T[10] = atan2d(np.sum(fbs,axis=0),np.sum(fas,axis=0)) #include f' & f" iBeg += blkSize @@ -1557,7 +1564,7 @@ def MagStructureFactor2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): def MagStructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): '''Compute magnetic structure factor derivatives numerically - for powders/nontwins only input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,it,d,... 'FF' dict of form factors - filled in below @@ -1567,10 +1574,10 @@ def MagStructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): :param dict SGData: space group info. dictionary output from SpcGroup :param dict calcControls: :param dict parmDict: - + :returns: dict dFdvDict: dictionary of magnetic derivatives ''' - + trefDict = copy.deepcopy(refDict) dM = 1.e-6 dFdvDict = {} @@ -1583,11 +1590,11 @@ def MagStructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): parmDict[parm] += dM dFdvDict[parm] = (prefList[:,9]-mrefList[:,9])/(2.*dM) return dFdvDict - + def MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict): '''Compute nonmagnetic structure factor derivatives on blocks of reflections in magnetic structures - for powders/nontwins only input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,it,d,... 'FF' dict of form factors - filled in below @@ -1597,10 +1604,10 @@ def MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict): :param dict SGData: space group info. dictionary output from SpcGroup :param dict calcControls: :param dict parmDict: - + :returns: dict dFdvDict: dictionary of derivatives ''' - + g = nl.inv(G) ast = np.sqrt(np.diag(G)) ainv = np.sqrt(np.diag(g)) @@ -1685,7 +1692,7 @@ def MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict): HM = HM/np.sqrt(np.sum(HM**2,axis=0)) #unit cartesian vector for H eDotK = np.sum(HM[:,:,nxs,nxs]*Kdata[:,nxs,:,:],axis=0) Q = HM[:,:,nxs,nxs]*eDotK[nxs,:,:,:]-Kdata[:,nxs,:,:] #Mxyz,Nref,Nop,Natm = BPM in magstrfc.for OK - + fam = Q*TMcorr[nxs,:,nxs,:]*cosm[nxs,:,:,:]*Mag[nxs,nxs,:,:] #Mxyz,Nref,Nop,Natm fbm = Q*TMcorr[nxs,:,nxs,:]*sinm[nxs,:,:,:]*Mag[nxs,nxs,:,:] fams = np.sum(np.sum(fam,axis=-1),axis=-1) #Mxyz,Nref @@ -1698,11 +1705,11 @@ def MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dfadui = np.sum(-SQfactor[:,nxs,nxs]*fam,axis=2) #array(Ops,refBlk,nAtoms) deriv OK dfadua = np.sum(-Hij[nxs,:,:,nxs,:]*fam[:,:,:,:,nxs],axis=2) #deriv OK # imaginary part; array(3,refBlk,nAtom,3) & array(3,refBlk,nAtom,6) - dfbdfr = np.sum(fbm/occ,axis=2) #array(mxyz,refBlk,nAtom) Fdata != 0 avoids /0. problem + dfbdfr = np.sum(fbm/occ,axis=2) #array(mxyz,refBlk,nAtom) Fdata != 0 avoids /0. problem dfbdx = np.sum(twopi*Uniq[nxs,:,:,nxs,:]*fbmx[:,:,:,:,nxs],axis=2) dfbdui = np.sum(-SQfactor[:,nxs,nxs]*fbm,axis=2) #array(Ops,refBlk,nAtoms) dfbdua = np.sum(-Hij[nxs,:,:,nxs,:]*fbm[:,:,:,:,nxs],axis=2) - #accumulate derivatives + #accumulate derivatives dFdfr[iBeg:iFin] = 2.*np.sum((fams[:,:,nxs]*dfadfr+fbms[:,:,nxs]*dfbdfr)*Mdata/Nops,axis=0) #ok dFdx[iBeg:iFin] = 2.*np.sum(fams[:,:,nxs,nxs]*dfadx+fbms[:,:,nxs,nxs]*dfbdx,axis=0) #ok dFdui[iBeg:iFin] = 2.*np.sum(fams[:,:,nxs]*dfadui+fbms[:,:,nxs]*dfbdui,axis=0) #ok @@ -1723,12 +1730,12 @@ def MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dFdvDict[pfx+'AU13:'+str(i)] = dFdua.T[4][i] dFdvDict[pfx+'AU23:'+str(i)] = dFdua.T[5][i] return dFdvDict - + def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): '''Compute structure factor derivatives on blocks of reflections - for twins only faster than StructureFactorDervTw input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,it,d,... 'FF' dict of form factors - filled in below @@ -1738,7 +1745,7 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): :param dict SGData: space group info. dictionary output from SpcGroup :param dict calcControls: :param dict parmDict: - + :returns: dict dFdvDict: dictionary of derivatives ''' phfx = pfx.split(':')[0]+hfx @@ -1748,13 +1755,13 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): SGT = np.array([ops[1] for ops in SGData['SGOps']]) FFtables = calcControls['FFtables'] BLtables = calcControls['BLtables'] - TwDict = refDict.get('TwDict',{}) + TwDict = refDict.get('TwDict',{}) NTL = calcControls[phfx+'NTL'] NM = calcControls[phfx+'TwinNMN']+1 TwinLaw = calcControls[phfx+'TwinLaw'] TwinFr = np.array([parmDict[phfx+'TwinFr:'+str(i)] for i in range(len(TwinLaw))]) TwinInv = list(np.where(calcControls[phfx+'TwinInv'],-1,1)) - nTwin = len(TwinLaw) + nTwin = len(TwinLaw) nRef = len(refDict['RefList']) Tdata,Mdata,Fdata,Xdata,dXdata,IAdata,Uisodata,Uijdata,Gdata = \ GetAtomFXU(pfx,calcControls,parmDict) @@ -1770,7 +1777,7 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): elif 'SEC' in calcControls[hfx+'histType']: FP = np.zeros(len(Tdata)) FPP = np.zeros(len(Tdata)) - + Uij = np.array(G2lat.U6toUij(Uijdata)) bij = Mast*Uij.T dFdvDict = {} @@ -1824,7 +1831,7 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): Tuij = np.where(HbH<1.,np.exp(HbH),1.0) Tcorr = (np.reshape(Tiso,Tuij.shape)*Tuij).T*Mdata*Fdata/len(SGMT) fot = np.reshape(((FF+FP).T-Bab).T,cosp.shape)*Tcorr - fotp = FPP*Tcorr + fotp = FPP*Tcorr if 'T' in calcControls[hfx+'histType']: #fa,fb are 2 X blkSize X nTwin X nOps x nAtoms fa = np.array([np.reshape(((FF+FP).T-Bab).T,cosp.shape)*cosp*Tcorr,-np.reshape(FPP,sinp.shape)*sinp*Tcorr]) fb = np.array([np.reshape(((FF+FP).T-Bab).T,sinp.shape)*sinp*Tcorr,np.reshape(FPP,cosp.shape)*cosp*Tcorr]) @@ -1838,12 +1845,12 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): fas[1] *= 0. fax = np.array([-fot*sinp,-fotp*cosp]) #positions array(2,nRef,ntwi,nEqv,nAtoms) fbx = np.array([fot*cosp,-fotp*sinp]) - #sum below is over Uniq - dfadfr = np.sum(np.sum(fa/occ,axis=-2),axis=0) #array(2,nRef,ntwin,nAtom) Fdata != 0 avoids /0. problem + #sum below is over Uniq + dfadfr = np.sum(np.sum(fa/occ,axis=-2),axis=0) #array(2,nRef,ntwin,nAtom) Fdata != 0 avoids /0. problem dfadba = np.sum(-cosp*Tcorr[:,nxs],axis=1) - dfadui = np.sum(np.sum(-SQfactor[nxs,:,nxs,nxs,nxs]*fa,axis=-2),axis=0) + dfadui = np.sum(np.sum(-SQfactor[nxs,:,nxs,nxs,nxs]*fa,axis=-2),axis=0) dfadx = np.sum(np.sum(twopi*Uniq[nxs,:,:,:,nxs,:]*fax[:,:,:,:,:,nxs],axis=-3),axis=0) # nRef x nTwin x nAtoms x xyz; sum on ops & A,A' - dfadua = np.sum(np.sum(-Hij[nxs,:,:,:,nxs,:]*fa[:,:,:,:,:,nxs],axis=-3),axis=0) + dfadua = np.sum(np.sum(-Hij[nxs,:,:,:,nxs,:]*fa[:,:,:,:,:,nxs],axis=-3),axis=0) if not SGData['SGInv']: dfbdfr = np.sum(np.sum(fb/occ,axis=-2),axis=0) #Fdata != 0 avoids /0. problem dfadba /= 2. @@ -1865,7 +1872,7 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): dFdua[iBeg:iFin] = (2.*TwMask*SA)[:,:,nxs,nxs]*dfadua+(2.*TwMask*SB)[:,:,nxs,nxs]*dfbdua if SGData['SGInv']: #centrosymmetric; B=0 dFdtw[iBeg:iFin] = np.sum(TwMask[nxs,:]*fas,axis=0)**2 - else: + else: dFdtw[iBeg:iFin] = np.sum(TwMask[nxs,:]*fas,axis=0)**2+np.sum(TwMask[nxs,:]*fbs,axis=0)**2 # dFdbab[iBeg:iFin] = fas[0,:,nxs]*np.array([np.sum(dfadba*dBabdA),np.sum(-dfadba*parmDict[phfx+'BabA']*SQfactor*dBabdA)]).T+ \ # fbs[0,:,nxs]*np.array([np.sum(dfbdba*dBabdA),np.sum(-dfbdba*parmDict[phfx+'BabA']*SQfactor*dBabdA)]).T @@ -1889,14 +1896,14 @@ def StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict): for i in range(nTwin): dFdvDict[phfx+'TwinFr:'+str(i)] = dFdtw.T[i] return dFdvDict - + def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): - ''' + ''' Compute super structure factors for all h,k,l,m for phase - no twins puts the result, F^2, in each ref[9] in refList works on blocks of 32 reflections for speed input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,m,it,d,... 'FF' dict of form factors - filed in below @@ -1912,7 +1919,7 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): GS = G/np.outer(ast,ast) uAmat,uBmat = G2lat.Gmat2AB(GS) Amat,Bmat = G2lat.Gmat2AB(G) - Mast = twopisq*np.multiply.outer(ast,ast) + Mast = twopisq*np.multiply.outer(ast,ast) SGInv = SGData['SGInv'] SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) Nops = len(SGMT)*(1+SGData['SGInv']) @@ -1943,17 +1950,17 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): modQ = np.array([parmDict[pfx+'mV0'],parmDict[pfx+'mV1'],parmDict[pfx+'mV2']]) if parmDict[pfx+'isMag']: #This part correct for making modulated mag moments on equiv atoms - Mmod matched drawing & Bilbao drawings - + mXYZ,MmodAR,MmodBR,MmodAI,MmodBI = G2mth.MagMod(glTau,Xdata+dXdata,modQ,MSSdata,SGData,SSGData) #Ntau,Nops,Natm,Mxyz cos,sin parts sum matches drawing #expand Mmod over mag symm ops. --> GSSdata if not SGData['SGGray']: #for fixed Mx,My,Mz GSdata = np.inner(Gdata.T,np.swapaxes(SGMT,1,2)) #apply sym. ops.--> Natm,Nops,Nxyz if SGData['SGInv'] and not SGData['SGFixed']: #inversion if any - GSdata = np.hstack((GSdata,-GSdata)) + GSdata = np.hstack((GSdata,-GSdata)) GSdata = np.hstack([GSdata for cen in SSCen]) #dup over cell centering - Natm,Nops,Mxyz GSdata = SGData['MagMom'][nxs,:,nxs]*GSdata #flip vectors according to spin flip * det(opM) GSdata = np.swapaxes(GSdata,0,1) #Nop,Natm,Mxyz - + FF = np.zeros(len(Tdata)) if 'NC' in calcControls[hfx+'histType'] or 'NB' in calcControls[hfx+'histType']: FP,FPP = G2el.BlenResCW(Tdata,BLtables,parmDict[hfx+'Lam']) @@ -1981,7 +1988,7 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): refDict['FF']['El'] = list(dat.keys()) refDict['FF']['FF'] = np.zeros((nRef,len(dat))) for iel,El in enumerate(refDict['FF']['El']): - refDict['FF']['FF'].T[iel] = G2el.ScatFac(EFtables[El],SQ) + refDict['FF']['FF'].T[iel] = G2el.ScatFac(EFtables[El],SQ) else: dat = G2el.getFFvalues(FFtables,0.) refDict['FF']['El'] = list(dat.keys()) @@ -2026,8 +2033,8 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): Tuij = np.where(HbH<1.,np.exp(HbH),1.0) Tcorr = np.reshape(Tiso,Tuij.shape)*Tuij*Mdata*Fdata/Uniq.shape[1] #refBlk x ops x atoms - if 'N' in calcControls[hfx+'histType'] and parmDict[pfx+'isMag']: - + if 'N' in calcControls[hfx+'histType'] and parmDict[pfx+'isMag']: + phasem = twopi*np.inner(mXYZ,HP.T).T #2pi(Q.r) cosm = np.cos(phasem) #Nref,nops,natm sinm = np.sin(phasem) @@ -2036,11 +2043,11 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): HM = np.inner(Bmat,HP.T) #put into cartesian space X||H,Z||H*L; Bmat.T correct Cart coordinates eM = (HM*refl.T[5]).T # normalize HP by d* Nref,hkl=Unit vectors || Q - if not SGData['SGGray']: #correct -fixed Mx,My,Mz contribution + if not SGData['SGGray']: #correct -fixed Mx,My,Mz contribution fam0 = TMcorr[:,nxs,:,nxs]*GSdata[nxs,:,:,:]*cosm[:,:,:,nxs] #Nref,Nops,Natm,Mxyz fbm0 = TMcorr[:,nxs,:,nxs]*GSdata[nxs,:,:,:]*sinm[:,:,:,nxs] # calc mag. structure factors; Nref,Ntau,Nops,Natm,Mxyz - Rs = [-1.,-1.,-1.,-1.] + Rs = [-1.,-1.,-1.,-1.] fams = TMcorr[:,nxs,nxs,:,nxs]*SGData['MagMom'][nxs,nxs,:,nxs,nxs]*np.array([np.where(H[3,i]!=0,( (Rs[0]*MmodAR+Rs[1]*H[3,i]*MmodBR)*cosm[i,nxs,:,:,nxs]+GamI[nxs,:,nxs,nxs]*(Rs[2]*MmodAI+Rs[3]*H[3,i]*MmodBI)*sinm[i,nxs,:,:,nxs]), 0.) for i in range(mRef)])/2. #Nref,Ntau,Nops,Natm,Mxyz @@ -2048,19 +2055,19 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): fbms = TMcorr[:,nxs,nxs,:,nxs]*SGData['MagMom'][nxs,nxs,:,nxs,nxs]*np.array([np.where(H[3,i]!=0,( (Is[0]*MmodAR+Is[1]*H[3,i]*MmodBR)*sinm[i,nxs,:,:,nxs]+GamI[nxs,:,nxs,nxs]*(Is[2]*MmodAI+Is[3]*H[3,i]*MmodBI)*cosm[i,nxs,:,:,nxs]), 0.) for i in range(mRef)])/2. #Nref,Ntau,Nops,Natm,Mxyz - + if not SGData['SGGray']: fams += fam0[:,nxs,:,:,:] fbms += fbm0[:,nxs,:,:,:] - -#sum ops & atms + +#sum ops & atms fasm = np.sum(np.sum(fams,axis=-2),axis=-2) #Nref,Ntau,Mxyz; sum ops & atoms fbsm = np.sum(np.sum(fbms,axis=-2),axis=-2) # #put into cartesian space facm = np.inner(fasm,uBmat) #uBmat best fit for DyMnGe; +,- & -,+ fams, fbms; Nref, Ntau, Mxyz fbcm = np.inner(fbsm,uBmat) #Nref,Ntau,Mxyz #form e.F dot product - eDotFa = np.sum(eM[:,nxs,:]*facm,axis=-1) #Nref,Ntau + eDotFa = np.sum(eM[:,nxs,:]*facm,axis=-1) #Nref,Ntau eDotFb = np.sum(eM[:,nxs,:]*fbcm,axis=-1) #intensity Halpern & Johnson fass = np.sum((facm-eM[:,nxs,:]*eDotFa[:,:,nxs])**2,axis=-1) #Nref,Ntau @@ -2069,10 +2076,10 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): if SGData['SGGray']: fass *= 2. fbss *= 2. -## #do integration +## #do integration fas = np.sum(fass*glWt[nxs,:],axis=1) fbs = np.sum(fbss*glWt[nxs,:],axis=1) - + refl.T[10] = fas+fbs #Sum(fams**2,Mxyz) Re + Im refl.T[11] = atan2d(fbs,fas) @@ -2088,21 +2095,21 @@ def SStructureFactor(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): fbg = fb*GfpuA[0]+fa*GfpuA[1] fas = np.sum(np.sum(fag,axis=-1),axis=-1) #2 x refBlk; sum sym & atoms fbs = np.sum(np.sum(fbg,axis=-1),axis=-1) - + refl.T[10] = np.sum(fas,axis=0)**2+np.sum(fbs,axis=0)**2 #square of sums refl.T[11] = atan2d(fbs[0],fas[0]) #use only tau=0; ignore f' & f" if 'P' not in calcControls[hfx+'histType']: - refl.T[8] = np.copy(refl.T[10]) + refl.T[8] = np.copy(refl.T[10]) iBeg += blkSize return copy.deepcopy(refDict['RefList']) def SStructureFactorTw(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): - ''' + ''' Compute super structure factors for all h,k,l,m for phase - twins only puts the result, F^2, in each ref[8+im] in refList works on blocks of 32 reflections for speed input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,m,it,d,... 'FF' dict of form factors - filed in below @@ -2115,7 +2122,7 @@ def SStructureFactorTw(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): ''' phfx = pfx.split(':')[0]+hfx ast = np.sqrt(np.diag(G)) - Mast = twopisq*np.multiply.outer(ast,ast) + Mast = twopisq*np.multiply.outer(ast,ast) SGInv = SGData['SGInv'] SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']]) @@ -2128,7 +2135,7 @@ def SStructureFactorTw(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): if not SGData['SGInv'] and 'S' in calcControls[hfx+'histType'] and phfx+'Flack' in parmDict: Flack = 1.-2.*parmDict[phfx+'Flack'] TwinLaw = np.array([[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],]) #4D? - TwDict = refDict.get('TwDict',{}) + TwDict = refDict.get('TwDict',{}) if 'S' in calcControls[hfx+'histType']: NTL = calcControls[phfx+'NTL'] NM = calcControls[phfx+'TwinNMN']+1 @@ -2170,7 +2177,7 @@ def SStructureFactorTw(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): refDict['FF']['El'] = list(dat.keys()) refDict['FF']['FF'] = np.zeros((nRef,len(dat))) for iel,El in enumerate(refDict['FF']['El']): - refDict['FF']['FF'].T[iel] = G2el.ScatFac(EFtables[El],SQ) + refDict['FF']['FF'].T[iel] = G2el.ScatFac(EFtables[El],SQ) else: dat = G2el.getFFvalues(FFtables,0.) refDict['FF']['El'] = list(dat.keys()) @@ -2187,7 +2194,7 @@ def SStructureFactorTw(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): H3 = refl[:,:3] HP = H[:,:3]+modQ[nxs,:]*H[:,3:] #projected hklm to hkl HP = np.inner(HP,TwinLaw) #array(blkSize,nTwins,4) - H3 = np.inner(H3,TwinLaw) + H3 = np.inner(H3,TwinLaw) TwMask = np.any(HP,axis=-1) if TwinLaw.shape[0] > 1 and TwDict: #need np.inner(TwinLaw[?],TwDict[iref][i])*TwinInv[i] for ir in range(blkSize): @@ -2246,11 +2253,11 @@ def SStructureFactorTw(refDict,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): # print ('nRef %d time %.4f\r'%(nRef,time.time()-time0)) def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): - ''' + ''' Compute super structure factor derivatives for all h,k,l,m for phase - no twins Only Fourier component are done analytically here input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,m,it,d,... 'FF' dict of form factors - filled in below @@ -2262,7 +2269,7 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi :param dict SSGData: super space group info. :param dict calcControls: :param dict ParmDict: - + :returns: dict dFdvDict: dictionary of derivatives ''' phfx = pfx.split(':')[0]+hfx @@ -2302,7 +2309,7 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi elif 'SEC' in calcControls[hfx+'histType']: dat = G2el.getFFvalues(EFtables,0.) else: - dat = G2el.getFFvalues(FFtables,0.) + dat = G2el.getFFvalues(FFtables,0.) refDict['FF']['El'] = list(dat.keys()) refDict['FF']['FF'] = np.zeros((len(refDict['RefList']),len(dat))) dFdvDict = {} @@ -2361,7 +2368,7 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi fb = np.array([((FF+FP).T-Bab).T*sinp*Tcorr,Flack*FPP*cosp*Tcorr]) #or array(2,nEqv,nAtoms) fag = fa*GfpuA[0]-fb*GfpuA[1] fbg = fb*GfpuA[0]+fa*GfpuA[1] - + fas = np.sum(np.sum(fag,axis=1),axis=1) # 2 x twin fbs = np.sum(np.sum(fbg,axis=1),axis=1) fax = np.array([-fot*sinp,-fotp*cosp]) #positions; 2 x ops x atoms @@ -2376,7 +2383,7 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi dfadui = np.sum(-SQfactor*fag,axis=1) dfbdui = np.sum(-SQfactor*fbg,axis=1) dfadx = np.sum(twopi*Uniq[:,:3]*np.swapaxes(fax,-2,-1)[:,:,:,nxs],axis=-2) #2 x nAtom x 3xyz; sum nOps - dfbdx = np.sum(twopi*Uniq[:,:3]*np.swapaxes(fbx,-2,-1)[:,:,:,nxs],axis=-2) + dfbdx = np.sum(twopi*Uniq[:,:3]*np.swapaxes(fbx,-2,-1)[:,:,:,nxs],axis=-2) dfadua = np.sum(-Hij*np.swapaxes(fag,-2,-1)[:,:,:,nxs],axis=-2) #2 x nAtom x 6Uij; sum nOps dfbdua = np.sum(-Hij*np.swapaxes(fbg,-2,-1)[:,:,:,nxs],axis=-2) #these are correct also for twins above # array(2,nAtom,nWave,2) & array(2,nAtom,nWave,6) & array(2,nAtom,nWave,12); sum on nOps @@ -2385,17 +2392,17 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi dfadGx = np.sum(fa[:,:,:,nxs,nxs]*dGdx[0][nxs,:,:,:,:]-fb[:,:,:,nxs,nxs]*dGdx[1][nxs,:,:,:,:],axis=1) dfbdGx = np.sum(fb[:,:,:,nxs,nxs]*dGdx[0][nxs,:,:,:,:]+fa[:,:,:,nxs,nxs]*dGdx[1][nxs,:,:,:,:],axis=1) dfadGu = np.sum(fa[:,:,:,nxs,nxs]*dGdu[0][nxs,:,:,:,:]-fb[:,:,:,nxs,nxs]*dGdu[1][nxs,:,:,:,:],axis=1) - dfbdGu = np.sum(fb[:,:,:,nxs,nxs]*dGdu[0][nxs,:,:,:,:]+fa[:,:,:,nxs,nxs]*dGdu[1][nxs,:,:,:,:],axis=1) + dfbdGu = np.sum(fb[:,:,:,nxs,nxs]*dGdu[0][nxs,:,:,:,:]+fa[:,:,:,nxs,nxs]*dGdu[1][nxs,:,:,:,:],axis=1) if not SGData['SGInv']: #Flack derivative dfadfl = np.sum(-FPP*Tcorr*sinp) dfbdfl = np.sum(FPP*Tcorr*cosp) else: dfadfl = 1.0 dfbdfl = 1.0 - SA = fas[0]+fas[1] #float = A+A' - SB = fbs[0]+fbs[1] #float = B+B' + SA = fas[0]+fas[1] #float = A+A' + SB = fbs[0]+fbs[1] #float = B+B' if 'P' in calcControls[hfx+'histType']: #checked perfect for centro & noncentro? - dFdfl[iref] = -SA*dfadfl-SB*dfbdfl #array(nRef,) + dFdfl[iref] = -SA*dfadfl-SB*dfbdfl #array(nRef,) dFdfr[iref] = 2.*(fas[0]*dfadfr[0]+fas[1]*dfadfr[1])*Mdata/len(Uniq)+ \ 2.*(fbs[0]*dfbdfr[0]-fbs[1]*dfbdfr[1])*Mdata/len(Uniq) dFdx[iref] = 2.*(fas[0]*dfadx[0]+fas[1]*dfadx[1])+ \ @@ -2415,8 +2422,8 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi dFdx[iref] = 2.*(SA*dfadx[0]+SA*dfadx[1]+SB*dfbdx[0]+SB*dfbdx[1]) #array(nRef,nAtom,3) dFdui[iref] = 2.*(SA*dfadui[0]+SA*dfadui[1]+SB*dfbdui[0]+SB*dfbdui[1]) #array(nRef,nAtom) dFdua[iref] = 2.*(SA*dfadua[0]+SA*dfadua[1]+SB*dfbdua[0]+SB*dfbdua[1]) #array(nRef,nAtom,6) - dFdfl[iref] = -SA*dfadfl-SB*dfbdfl #array(nRef,) - + dFdfl[iref] = -SA*dfadfl-SB*dfbdfl #array(nRef,) + dFdGf[iref] = 2.*(SA*dfadGf[0]+SB*dfbdGf[1]) #array(nRef,natom,nwave,2) dFdGx[iref] = 2.*(SA*dfadGx[0]+SB*dfbdGx[1]) #array(nRef,natom,nwave,6) dFdGu[iref] = 2.*(SA*dfadGu[0]+SB*dfbdGu[1]) #array(nRef,natom,nwave,12) @@ -2443,8 +2450,8 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi dFdvDict[pfx+'Fcos:'+str(i)+':'+str(j)] = dFdGf.T[1][j][i] nx = 0 if waveTypes[i] in ['Block','ZigZag']: - nx = 1 - for j in range(XSSdata.shape[1]-nx): #loop over waves + nx = 1 + for j in range(XSSdata.shape[1]-nx): #loop over waves dFdvDict[pfx+'Xsin:'+str(i)+':'+str(j+nx)] = dFdGx.T[0][j][i] dFdvDict[pfx+'Ysin:'+str(i)+':'+str(j+nx)] = dFdGx.T[1][j][i] dFdvDict[pfx+'Zsin:'+str(i)+':'+str(j+nx)] = dFdGx.T[2][j][i] @@ -2464,7 +2471,7 @@ def SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDi dFdvDict[pfx+'U12cos:'+str(i)+':'+str(j)] = dFdGu.T[9][j][i] dFdvDict[pfx+'U13cos:'+str(i)+':'+str(j)] = dFdGu.T[10][j][i] dFdvDict[pfx+'U23cos:'+str(i)+':'+str(j)] = dFdGu.T[11][j][i] - + dFdvDict[phfx+'Flack'] = 4.*dFdfl.T dFdvDict[phfx+'BabA'] = dFdbab.T[0] dFdvDict[phfx+'BabU'] = dFdbab.T[1] @@ -2474,7 +2481,7 @@ def SStructureFactorDerv2(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmD ''' Compute super structure factor derivatives for all h,k,l,m for phase - no twins input: - + :param dict refDict: where 'RefList' list where each ref = h,k,l,m,it,d,... 'FF' dict of form factors - filled in below @@ -2486,7 +2493,7 @@ def SStructureFactorDerv2(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmD :param dict SSGData: super space group info. :param dict calcControls: :param dict ParmDict: - + :returns: dict dFdvDict: dictionary of derivatives ''' @@ -2505,7 +2512,7 @@ def SStructureFactorDerv2(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmD parmDict[parm] += dM dFdvDict[parm] = (prefList[:,9+im]-mrefList[:,9+im])/(2.*dM) return dFdvDict - + def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDict): 'Needs a doc string' phfx = pfx.split(':')[0]+hfx @@ -2519,13 +2526,13 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm EFtables = calcControls['EFtables'] BLtables = calcControls['BLtables'] TwinLaw = np.array([[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],]) - TwDict = refDict.get('TwDict',{}) + TwDict = refDict.get('TwDict',{}) if 'S' in calcControls[hfx+'histType']: NTL = calcControls[phfx+'NTL'] NM = calcControls[phfx+'TwinNMN']+1 TwinLaw = calcControls[phfx+'TwinLaw'] TwinInv = list(np.where(calcControls[phfx+'TwinInv'],-1,1)) - nTwin = len(TwinLaw) + nTwin = len(TwinLaw) nRef = len(refDict['RefList']) Tdata,Mdata,Fdata,Xdata,dXdata,IAdata,Uisodata,Uijdata,Gdata = \ GetAtomFXU(pfx,calcControls,parmDict) @@ -2551,9 +2558,9 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm if 'N' in calcControls[hfx+'histType']: dat = G2el.getBLvalues(BLtables) #will need wave here for anom. neutron b's elif 'SEC' in calcControls[hfx+'histType']: - dat = G2el.getFFvalues(EFtables,0.) + dat = G2el.getFFvalues(EFtables,0.) else: - dat = G2el.getFFvalues(FFtables,0.) + dat = G2el.getFFvalues(FFtables,0.) refDict['FF']['El'] = list(dat.keys()) refDict['FF']['FF'] = np.zeros((len(refDict['RefList']),len(dat))) dFdvDict = {} @@ -2619,7 +2626,7 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm fb = np.array([((FF+FP).T-Bab).T*sinp*Tcorr,Flack*FPP*cosp*Tcorr]) #or array(2,nEqv,nAtoms) fag = fa*GfpuA[0]-fb*GfpuA[1] fbg = fb*GfpuA[0]+fa*GfpuA[1] - + fas = np.sum(np.sum(fag,axis=1),axis=1) # 2 x twin fbs = np.sum(np.sum(fbg,axis=1),axis=1) fax = np.array([-fot*sinp,-fotp*cosp]) #positions; 2 x twin x ops x atoms @@ -2634,7 +2641,7 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm dfadui = np.sum(-SQfactor*fag,axis=1) dfbdui = np.sum(-SQfactor*fbg,axis=1) dfadx = np.array([np.sum(twopi*Uniq[it,:,:3]*np.swapaxes(fax,-2,-1)[:,it,:,:,nxs],axis=-2) for it in range(nTwin)]) - dfbdx = np.array([np.sum(twopi*Uniq[it,:,:3]*np.swapaxes(fbx,-2,-1)[:,it,:,:,nxs],axis=-2) for it in range(nTwin)]) + dfbdx = np.array([np.sum(twopi*Uniq[it,:,:3]*np.swapaxes(fbx,-2,-1)[:,it,:,:,nxs],axis=-2) for it in range(nTwin)]) dfadua = np.array([np.sum(-Hij[it]*np.swapaxes(fag,-2,-1)[:,it,:,:,nxs],axis=-2) for it in range(nTwin)]) dfbdua = np.array([np.sum(-Hij[it]*np.swapaxes(fbg,-2,-1)[:,it,:,:,nxs],axis=-2) for it in range(nTwin)]) # array(2,nTwin,nAtom,3) & array(2,nTwin,nAtom,6) & array(2,nTwin,nAtom,12) @@ -2647,7 +2654,7 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm dfadGu = np.sum(fa[:,it,:,:,nxs,nxs]*dGdu[0][nxs,nxs,:,:,:,:]-fb[:,it,:,:,nxs,nxs]*dGdu[1][nxs,nxs,:,:,:,:],axis=1) dfbdGu = np.sum(fb[:,it,:,:,nxs,nxs]*dGdu[0][nxs,nxs,:,:,:,:]+fa[:,it,:,:,nxs,nxs]*dGdu[1][nxs,nxs,:,:,:,:],axis=1) # GSASIIpath.IPyBreak() - #NB: the above have been checked against PA(1:10,1:2) in strfctr.for for Al2O3! + #NB: the above have been checked against PA(1:10,1:2) in strfctr.for for Al2O3! SA = fas[0]+fas[1] #float = A+A' (might be array[nTwin]) SB = fbs[0]+fbs[1] #float = B+B' (might be array[nTwin]) dFdfr[iref] = [2.*TwMask[it]*(SA[it]*dfadfr[0][it]+SA[it]*dfadfr[1][it]+SB[it]*dfbdfr[0][it]+SB[it]*dfbdfr[1][it])*Mdata/len(Uniq[it]) for it in range(nTwin)] @@ -2659,7 +2666,7 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm dFdGf[iref] = [2.*TwMask[it]*(SA[it]*dfadGf[1]+SB[it]*dfbdGf[1]) for it in range(nTwin)] dFdGx[iref] = [2.*TwMask[it]*(SA[it]*dfadGx[1]+SB[it]*dfbdGx[1]) for it in range(nTwin)] dFdGz[iref] = [2.*TwMask[it]*(SA[it]*dfadGz[1]+SB[it]*dfbdGz[1]) for it in range(nTwin)] - dFdGu[iref] = [2.*TwMask[it]*(SA[it]*dfadGu[1]+SB[it]*dfbdGu[1]) for it in range(nTwin)] + dFdGu[iref] = [2.*TwMask[it]*(SA[it]*dfadGu[1]+SB[it]*dfbdGu[1]) for it in range(nTwin)] # GSASIIpath.IPyBreak() dFdbab[iref] = 2.*fas[0]*np.array([np.sum(dfadba*dBabdA),np.sum(-dfadba*parmDict[phfx+'BabA']*SQfactor*dBabdA)]).T+ \ 2.*fbs[0]*np.array([np.sum(dfbdba*dBabdA),np.sum(-dfbdba*parmDict[phfx+'BabA']*SQfactor*dBabdA)]).T @@ -2683,13 +2690,13 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm dFdvDict[pfx+'Fcos:'+str(i)+':'+str(j)] = dFdGf.T[1][j][i] nx = 0 if waveTypes[i] in ['Block','ZigZag']: - nx = 1 + nx = 1 dFdvDict[pfx+'Tmin:'+str(i)+':0'] = dFdGz.T[0][i] #ZigZag/Block waves (if any) dFdvDict[pfx+'Tmax:'+str(i)+':0'] = dFdGz.T[1][i] dFdvDict[pfx+'Xmax:'+str(i)+':0'] = dFdGz.T[2][i] dFdvDict[pfx+'Ymax:'+str(i)+':0'] = dFdGz.T[3][i] dFdvDict[pfx+'Zmax:'+str(i)+':0'] = dFdGz.T[4][i] - for j in range(XSSdata.shape[1]-nx): #loop over waves + for j in range(XSSdata.shape[1]-nx): #loop over waves dFdvDict[pfx+'Xsin:'+str(i)+':'+str(j+nx)] = dFdGx.T[0][j][i] dFdvDict[pfx+'Ysin:'+str(i)+':'+str(j+nx)] = dFdGx.T[1][j][i] dFdvDict[pfx+'Zsin:'+str(i)+':'+str(j+nx)] = dFdGx.T[2][j][i] @@ -2709,12 +2716,12 @@ def SStructureFactorDervTw(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parm dFdvDict[pfx+'U12cos:'+str(i)+':'+str(j)] = dFdGu.T[9][j][i] dFdvDict[pfx+'U13cos:'+str(i)+':'+str(j)] = dFdGu.T[10][j][i] dFdvDict[pfx+'U23cos:'+str(i)+':'+str(j)] = dFdGu.T[11][j][i] - + # GSASIIpath.IPyBreak() dFdvDict[phfx+'BabA'] = dFdbab.T[0] dFdvDict[phfx+'BabU'] = dFdbab.T[1] return dFdvDict - + def SCExtinction(ref,im,phfx,hfx,pfx,calcControls,parmDict,varyList): ''' Single crystal extinction function; returns extinction & derivative ''' @@ -2723,10 +2730,10 @@ def SCExtinction(ref,im,phfx,hfx,pfx,calcControls,parmDict,varyList): dervCor = 1.0 if calcControls[phfx+'EType'] != 'None': SQ = 1/(4.*ref[4+im]**2) - if 'C' in parmDict[hfx+'Type']: + if 'C' in parmDict[hfx+'Type']: cos2T = 1.0-2.*SQ*parmDict[hfx+'Lam']**2 #cos(2theta) else: #'T' - cos2T = 1.0-2.*SQ*ref[12+im]**2 #cos(2theta) + cos2T = 1.0-2.*SQ*ref[12+im]**2 #cos(2theta) if 'SXC' in parmDict[hfx+'Type'] or 'SEC' in parmDict[hfx+'Type']: AV = 7.9406e5/parmDict[pfx+'Vol']**2 #is 7.9406e5 constant right for electroms? PL = np.sqrt(1.0-cos2T**2)/parmDict[hfx+'Lam'] @@ -2740,7 +2747,7 @@ def SCExtinction(ref,im,phfx,hfx,pfx,calcControls,parmDict,varyList): AV = 1.e7/parmDict[pfx+'Vol']**2 PL = np.sqrt(1.0-cos2T**2)/parmDict[hfx+'Lam'] PLZ = AV*ref[9+im]*parmDict[hfx+'Lam']**2 - + if 'Primary' in calcControls[phfx+'EType']: PLZ *= 1.5 else: @@ -2757,7 +2764,7 @@ def SCExtinction(ref,im,phfx,hfx,pfx,calcControls,parmDict,varyList): PSIG = parmDict[phfx+'Es'] else: # 'Secondary Type I' PSIG = parmDict[phfx+'Eg']/PL - + AG = 0.58+0.48*cos2T+0.24*cos2T**2 AL = 0.025+0.285*cos2T BG = 0.02-0.025*cos2T @@ -2767,7 +2774,7 @@ def SCExtinction(ref,im,phfx,hfx,pfx,calcControls,parmDict,varyList): CG = 2. CL = 2. PF = PLZ*PSIG - + if 'Gaussian' in calcControls[phfx+'EApprox']: PF4 = 1.+CG*PF+AG*PF**2/(1.+BG*PF) extCor = np.sqrt(PF4) @@ -2784,19 +2791,19 @@ def SCExtinction(ref,im,phfx,hfx,pfx,calcControls,parmDict,varyList): dervDict[phfx+'Es'] = -ref[7+im]*PLZ*PF3*(PSIG/parmDict[phfx+'Es'])**3 if 'I' in calcControls[phfx+'EType'] and phfx+'Eg' in varyList: dervDict[phfx+'Eg'] = -ref[7+im]*PLZ*PF3*(PSIG/parmDict[phfx+'Eg'])**3*PL**2 - + return 1./extCor,dervDict,dervCor - + def Dict2Values(parmdict, varylist): - '''Use before call to leastsq to setup list of values for the parameters + '''Use before call to leastsq to setup list of values for the parameters in parmdict, as selected by key in varylist''' - return [parmdict[key] for key in varylist] - + return [parmdict[key] for key in varylist] + def Values2Dict(parmdict, varylist, values): - ''' Use after call to leastsq to update the parameter dictionary with + ''' Use after call to leastsq to update the parameter dictionary with values corresponding to keys in varylist''' parmdict.update(zip(varylist,values)) - + def GetNewCellParms(parmDict,varyList): '''Compute unit cell tensor terms from varied Aij and Dij values. Terms are included in the dict only if Aij or Dij is varied. @@ -2811,11 +2818,11 @@ def GetNewCellParms(parmDict,varyList): parm = keys[0]+'::'+keys[2] #parm is e.g. '0::D11' newCellDict[parm] = [key,parmDict[key]+parmDict[item]] return newCellDict # is e.g. {'0::D11':A0-D11} - + def ApplyXYZshifts(parmDict,varyList): ''' takes atom x,y,z shift and applies it to corresponding atom x,y,z value - + :param dict parmDict: parameter dictionary :param list varyList: list of variables (not used!) :returns: newAtomDict - dictionary of new atomic coordinate names & values; key is parameter shift name @@ -2828,7 +2835,7 @@ def ApplyXYZshifts(parmDict,varyList): parmDict[parm] += parmDict[item] newAtomDict[item] = [parm,parmDict[parm]] return newAtomDict - + def SHTXcal(refl,im,g,pfx,hfx,SGData,calcControls,parmDict): 'Spherical harmonics texture' IFCoup = 'Bragg' in calcControls[hfx+'instType'] @@ -2851,7 +2858,7 @@ def SHTXcal(refl,im,g,pfx,hfx,SGData,calcControls,parmDict): Lnorm = G2lat.Lnorm(L) odfCor += parmDict[pfx+item]*Lnorm*Kcl[0]*Ksl[0] return odfCor - + def SHTXcalDerv(refl,im,g,pfx,hfx,SGData,calcControls,parmDict): 'Spherical harmonics texture derivatives' if 'T' in calcControls[hfx+'histType']: @@ -2879,7 +2886,7 @@ def SHTXcalDerv(refl,im,g,pfx,hfx,SGData,calcControls,parmDict): for i in range(3): dFdSA[i] += parmDict[pfx+item]*Lnorm*Kcl*(dKsdp*dPSdA[i]+dKsdg*dGMdA[i]) return odfCor,dFdODF,dFdSA - + def SHPOcal(refl,im,g,phfx,hfx,SGData,calcControls,parmDict): 'spherical harmonics preferred orientation (cylindrical symmetry only)' if 'T' in calcControls[hfx+'histType']: @@ -2906,7 +2913,7 @@ def SHPOcal(refl,im,g,phfx,hfx,SGData,calcControls,parmDict): Lnorm = G2lat.Lnorm(L) odfCor += parmDict[phfx+item]*Lnorm*Kcl[0]*Ksl[0] return np.squeeze(odfCor) - + def SHPOcalDerv(refl,im,g,phfx,hfx,SGData,calcControls,parmDict): 'spherical harmonics preferred orientation derivatives (cylindrical symmetry only)' if 'T' in calcControls[hfx+'histType']: @@ -2935,7 +2942,7 @@ def SHPOcalDerv(refl,im,g,phfx,hfx,SGData,calcControls,parmDict): odfCor += parmDict[phfx+item]*Lnorm*Kcl[0]*Ksl[0] dFdODF[phfx+item] = Kcl*Ksl*Lnorm return odfCor,dFdODF - + def GetPrefOri(uniq,G,g,phfx,hfx,SGData,calcControls,parmDict): 'March-Dollase preferred orientation correction' POcorr = 1.0 @@ -2943,13 +2950,13 @@ def GetPrefOri(uniq,G,g,phfx,hfx,SGData,calcControls,parmDict): if MD != 1.0: MDAxis = calcControls[phfx+'MDAxis'] sumMD = 0 - for H in uniq: + for H in uniq: cosP,sinP = G2lat.CosSinAngle(H,MDAxis,G) A = 1.0/np.sqrt((MD*cosP)**2+sinP**2/MD) sumMD += A**3 POcorr = sumMD/len(uniq) return POcorr - + def GetPrefOriDerv(refl,im,uniq,G,g,phfx,hfx,SGData,calcControls,parmDict): 'Needs a doc string' POcorr = 1.0 @@ -2959,7 +2966,7 @@ def GetPrefOriDerv(refl,im,uniq,G,g,phfx,hfx,SGData,calcControls,parmDict): MDAxis = calcControls[phfx+'MDAxis'] sumMD = 0 sumdMD = 0 - for H in uniq: + for H in uniq: cosP,sinP = G2lat.CosSinAngle(H,MDAxis,G) A = 1.0/np.sqrt((MD*cosP)**2+sinP**2/MD) sumMD += A**3 @@ -2970,7 +2977,7 @@ def GetPrefOriDerv(refl,im,uniq,G,g,phfx,hfx,SGData,calcControls,parmDict): if calcControls[phfx+'SHord']: POcorr,POderv = SHPOcalDerv(refl,im,g,phfx,hfx,SGData,calcControls,parmDict) return POcorr,POderv - + def GetAbsorb(refl,im,hfx,calcControls,parmDict): 'Needs a doc string' if 'Debye' in calcControls[hfx+'instType']: @@ -2980,7 +2987,7 @@ def GetAbsorb(refl,im,hfx,calcControls,parmDict): return G2pwd.Absorb('Cylinder',parmDict[hfx+'Absorption'],refl[5+im],0,0) else: return G2pwd.SurfaceRough(parmDict[hfx+'SurfRoughA'],parmDict[hfx+'SurfRoughB'],refl[5+im]) - + def GetAbsorbDerv(refl,im,hfx,calcControls,parmDict): 'Needs a doc string' if 'Debye' in calcControls[hfx+'instType']: @@ -2990,7 +2997,7 @@ def GetAbsorbDerv(refl,im,hfx,calcControls,parmDict): return G2pwd.AbsorbDerv('Cylinder',parmDict[hfx+'Absorption'],refl[5+im],0,0) else: return np.array(G2pwd.SurfaceRoughDerv(parmDict[hfx+'SurfRoughA'],parmDict[hfx+'SurfRoughB'],refl[5+im])) - + def GetPwdrExt(refl,im,pfx,phfx,hfx,calcControls,parmDict): 'Needs a doc string' coef = np.array([-0.5,0.25,-0.10416667,0.036458333,-0.0109375,2.8497409E-3]) @@ -3017,7 +3024,7 @@ def GetPwdrExt(refl,im,pfx,phfx,hfx,calcControls,parmDict): xfac2 = 1./np.sqrt(xfac) exl = pi2*(1.-0.125/xfac)*xfac2 return exb*sth2+exl*(1.-sth2) - + def GetPwdrExtDerv(refl,im,pfx,phfx,hfx,calcControls,parmDict): 'Needs a doc string' coef = np.array([-0.5,0.25,-0.10416667,0.036458333,-0.0109375,2.8497409E-3]) @@ -3043,9 +3050,9 @@ def GetPwdrExtDerv(refl,im,pfx,phfx,hfx,calcControls,parmDict): elif xfac > 1.: xfac2 = 1./np.sqrt(xfac) dlde = 0.5*flv2*pi2*xfac2*(-1./xfac+0.375/xfac**2) - + return dbde*sth2+dlde*(1.-sth2) - + def GetIntensityCorr(refl,im,uniq,G,g,pfx,phfx,hfx,SGData,calcControls,parmDict): 'Needs a doc string' parmDict[phfx+'Scale'] = max(1.e-12,parmDict[phfx+'Scale']) #put floor on phase fraction scale @@ -3067,7 +3074,7 @@ def GetIntensityCorr(refl,im,uniq,G,g,pfx,phfx,hfx,SGData,calcControls,parmDict) ExtCorr = GetPwdrExt(refl,im,pfx,phfx,hfx,calcControls,parmDict) Icorr *= ExtCorr return Icorr,POcorr,AbsCorr,ExtCorr - + def GetIntensityDerv(refl,im,wave,uniq,G,g,pfx,phfx,hfx,SGData,calcControls,parmDict): 'Needs a doc string' #need powder extinction derivs! dIdsh = 1./parmDict[hfx+'Scale'] @@ -3087,7 +3094,7 @@ def GetIntensityDerv(refl,im,wave,uniq,G,g,pfx,phfx,hfx,SGData,calcControls,parm for i in range(3): dFdSA[i] /= odfCor elif calcControls[phfx+'poType'] == 'MD' or calcControls[phfx+'SHord']: - POcorr,dIdPO = GetPrefOriDerv(refl,im,uniq,G,g,phfx,hfx,SGData,calcControls,parmDict) + POcorr,dIdPO = GetPrefOriDerv(refl,im,uniq,G,g,phfx,hfx,SGData,calcControls,parmDict) for iPO in dIdPO: dIdPO[iPO] /= POcorr if 'T' in parmDict[hfx+'Type']: @@ -3095,11 +3102,11 @@ def GetIntensityDerv(refl,im,wave,uniq,G,g,pfx,phfx,hfx,SGData,calcControls,parm dFdEx = GetPwdrExtDerv(refl,im,pfx,phfx,hfx,calcControls,parmDict)/refl[17+im] #/ext corr else: dFdAb = GetAbsorbDerv(refl,im,hfx,calcControls,parmDict)*wave/refl[13+im] #wave/abs corr - dFdEx = GetPwdrExtDerv(refl,im,pfx,phfx,hfx,calcControls,parmDict)/refl[14+im] #/ext corr + dFdEx = GetPwdrExtDerv(refl,im,pfx,phfx,hfx,calcControls,parmDict)/refl[14+im] #/ext corr return dIdsh,dIdsp,dIdPola,dIdPO,dFdODF,dFdSA,dFdAb,dFdEx - + def GetSampleSigGam(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict): - '''Computes the sample-dependent Lorentzian & Gaussian peak width contributions from + '''Computes the sample-dependent Lorentzian & Gaussian peak width contributions from size & microstrain parameters :param float wave: wavelength for CW data, 2-theta for EDX data ''' @@ -3119,7 +3126,7 @@ def GetSampleSigGam(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict): H = np.array(refl[:3]) lenR = G2pwd.ellipseSize(H,Sij,GB) Sgam = 1.8*wave/(np.pi*costh*lenR) - #microstrain + #microstrain if calcControls[phfx+'MustrainType'] == 'isotropic': Mgam = 0.018*parmDict[phfx+'Mustrain;i']*tand(refl[5+im]/2.)/np.pi elif calcControls[phfx+'MustrainType'] == 'uniaxial': @@ -3151,7 +3158,7 @@ def GetSampleSigGam(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict): H = np.array(refl[:3]) lenR = G2pwd.ellipseSize(H,Sij,GB) Sgam = 1.e-4*keV/(2.*sinth*lenR) - #microstrain + #microstrain if calcControls[phfx+'MustrainType'] == 'isotropic': Mgam = 1.e-4*parmDict[phfx+'Mustrain;i'] elif calcControls[phfx+'MustrainType'] == 'uniaxial': @@ -3182,7 +3189,7 @@ def GetSampleSigGam(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict): H = np.array(refl[:3]) lenR = G2pwd.ellipseSize(H,Sij,GB) Sgam = 1.e-4*parmDict[hfx+'difC']*refl[4+im]**2/lenR - #microstrain + #microstrain if calcControls[phfx+'MustrainType'] == 'isotropic': #OK Mgam = 1.e-6*parmDict[hfx+'difC']*refl[4+im]*parmDict[phfx+'Mustrain;i'] elif calcControls[phfx+'MustrainType'] == 'uniaxial': #OK @@ -3198,12 +3205,12 @@ def GetSampleSigGam(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict): for i,strm in enumerate(Strms): Sum += parmDict[phfx+'Mustrain;'+str(i)]*strm Mgam = 1.e-6*parmDict[hfx+'difC']*np.sqrt(Sum)*refl[4+im]**3 - + gam = Sgam*parmDict[phfx+'Size;mx']+Mgam*parmDict[phfx+'Mustrain;mx'] sig = (Sgam*(1.-parmDict[phfx+'Size;mx']))**2+(Mgam*(1.-parmDict[phfx+'Mustrain;mx']))**2 sig /= ateln2 return sig,gam - + def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict): '''Computes the derivatives on sample-dependent Lorentzian & Gaussian peak widths contributions from size & microstrain parameters @@ -3245,12 +3252,12 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) sigDict[item] = -2.*Sgam*(const/lenR**2)*dRdS[i]*(1.-parmDict[phfx+'Size;mx'])**2/ateln2 gamDict[phfx+'Size;mx'] = Sgam sigDict[phfx+'Size;mx'] = -2.*Sgam**2*(1.-parmDict[phfx+'Size;mx'])/ateln2 - - #microstrain derivatives + + #microstrain derivatives if calcControls[phfx+'MustrainType'] == 'isotropic': Mgam = 0.018*parmDict[phfx+'Mustrain;i']*tand(refl[5+im]/2.)/np.pi gamDict[phfx+'Mustrain;i'] = 0.018*tanth*parmDict[phfx+'Mustrain;mx']/np.pi - sigDict[phfx+'Mustrain;i'] = 0.036*Mgam*tanth*(1.-parmDict[phfx+'Mustrain;mx'])**2/(np.pi*ateln2) + sigDict[phfx+'Mustrain;i'] = 0.036*Mgam*tanth*(1.-parmDict[phfx+'Mustrain;mx'])**2/(np.pi*ateln2) elif calcControls[phfx+'MustrainType'] == 'uniaxial': H = np.array(refl[:3]) P = np.array(calcControls[phfx+'MustrainAxis']) @@ -3265,7 +3272,7 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) gamDict[phfx+'Mustrain;i'] = (Mgam/Si+dsi)*parmDict[phfx+'Mustrain;mx'] gamDict[phfx+'Mustrain;a'] = (Mgam/Sa+dsa)*parmDict[phfx+'Mustrain;mx'] sigDict[phfx+'Mustrain;i'] = 2*(Mgam/Si+dsi)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 - sigDict[phfx+'Mustrain;a'] = 2*(Mgam/Sa+dsa)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 + sigDict[phfx+'Mustrain;a'] = 2*(Mgam/Sa+dsa)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 else: #generalized - P.W. Stephens model const = 0.018*refl[4+im]**2*tanth/np.pi Strms = G2spc.MustrainCoeff(refl[:3],SGData) @@ -3313,12 +3320,12 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) sigDict[item] = -2.*Sgam*(const/lenR**2)*dRdS[i]*(1.-parmDict[phfx+'Size;mx'])**2/ateln2 gamDict[phfx+'Size;mx'] = Sgam sigDict[phfx+'Size;mx'] = -2.*Sgam**2*(1.-parmDict[phfx+'Size;mx'])/ateln2 - - #microstrain derivatives + + #microstrain derivatives if calcControls[phfx+'MustrainType'] == 'isotropic': Mgam = 1.e-4*parmDict[phfx+'Mustrain;i'] gamDict[phfx+'Mustrain;i'] = 1.e-4*parmDict[phfx+'Mustrain;mx'] - sigDict[phfx+'Mustrain;i'] = 2.e-4*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/(ateln2) + sigDict[phfx+'Mustrain;i'] = 2.e-4*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/(ateln2) elif calcControls[phfx+'MustrainType'] == 'uniaxial': H = np.array(refl[:3]) P = np.array(calcControls[phfx+'MustrainAxis']) @@ -3333,7 +3340,7 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) gamDict[phfx+'Mustrain;i'] = (Mgam/Si+dsi)*parmDict[phfx+'Mustrain;mx'] gamDict[phfx+'Mustrain;a'] = (Mgam/Sa+dsa)*parmDict[phfx+'Mustrain;mx'] sigDict[phfx+'Mustrain;i'] = 2*(Mgam/Si+dsi)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 - sigDict[phfx+'Mustrain;a'] = 2*(Mgam/Sa+dsa)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 + sigDict[phfx+'Mustrain;a'] = 2*(Mgam/Sa+dsa)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 else: #generalized - P.W. Stephens model const = 1.e-4*refl[4+im]**2 Strms = G2spc.MustrainCoeff(refl[:3],SGData) @@ -3369,7 +3376,7 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) gamDict[phfx+'Size;a'] = dsa*parmDict[phfx+'Size;mx'] sigDict[phfx+'Size;i'] = 2.*dsi*Sgam*(1.-parmDict[phfx+'Size;mx'])**2/ateln2 sigDict[phfx+'Size;a'] = 2.*dsa*Sgam*(1.-parmDict[phfx+'Size;mx'])**2/ateln2 - else: #OK ellipsoidal crystallites + else: #OK ellipsoidal crystallites const = 1.e-4*parmDict[hfx+'difC']*refl[4+im]**2 Sij =[parmDict[phfx+'Size;%d'%(i)] for i in range(6)] H = np.array(refl[:3]) @@ -3380,12 +3387,12 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) sigDict[item] = -2.*Sgam*(const/lenR**2)*dRdS[i]*(1.-parmDict[phfx+'Size;mx'])**2/ateln2 gamDict[phfx+'Size;mx'] = Sgam #OK sigDict[phfx+'Size;mx'] = -2.*Sgam**2*(1.-parmDict[phfx+'Size;mx'])/ateln2 #OK - - #microstrain derivatives + + #microstrain derivatives if calcControls[phfx+'MustrainType'] == 'isotropic': Mgam = 1.e-6*parmDict[hfx+'difC']*refl[4+im]*parmDict[phfx+'Mustrain;i'] gamDict[phfx+'Mustrain;i'] = 1.e-6*refl[4+im]*parmDict[hfx+'difC']*parmDict[phfx+'Mustrain;mx'] #OK - sigDict[phfx+'Mustrain;i'] = 2.*Mgam**2*(1.-parmDict[phfx+'Mustrain;mx'])**2/(ateln2*parmDict[phfx+'Mustrain;i']) + sigDict[phfx+'Mustrain;i'] = 2.*Mgam**2*(1.-parmDict[phfx+'Mustrain;mx'])**2/(ateln2*parmDict[phfx+'Mustrain;i']) elif calcControls[phfx+'MustrainType'] == 'uniaxial': H = np.array(refl[:3]) P = np.array(calcControls[phfx+'MustrainAxis']) @@ -3400,7 +3407,7 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) gamDict[phfx+'Mustrain;i'] = (Mgam/Si+dsi)*parmDict[phfx+'Mustrain;mx'] gamDict[phfx+'Mustrain;a'] = (Mgam/Sa+dsa)*parmDict[phfx+'Mustrain;mx'] sigDict[phfx+'Mustrain;i'] = 2*(Mgam/Si+dsi)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 - sigDict[phfx+'Mustrain;a'] = 2*(Mgam/Sa+dsa)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 + sigDict[phfx+'Mustrain;a'] = 2*(Mgam/Sa+dsa)*Mgam*(1.-parmDict[phfx+'Mustrain;mx'])**2/ateln2 else: #generalized - P.W. Stephens model OK Strms = G2spc.MustrainCoeff(refl[:3],SGData) const = 1.e-6*parmDict[hfx+'difC']*refl[4+im]**3 @@ -3412,12 +3419,12 @@ def GetSampleSigGamDerv(refl,im,wave,G,GB,SGData,hfx,phfx,calcControls,parmDict) Mgam = const*np.sqrt(Sum) for i in range(len(Strms)): gamDict[phfx+'Mustrain;'+str(i)] *= Mgam/Sum - sigDict[phfx+'Mustrain;'+str(i)] *= const**2/ateln2 + sigDict[phfx+'Mustrain;'+str(i)] *= const**2/ateln2 gamDict[phfx+'Mustrain;mx'] = Mgam sigDict[phfx+'Mustrain;mx'] = -2.*Mgam**2*(1.-parmDict[phfx+'Mustrain;mx'])/ateln2 - + return sigDict,gamDict - + def GetReflPos(refl,im,wave,A,pfx,hfx,phfx,calcControls,parmDict): 'Needs a doc string' if im: @@ -3432,7 +3439,7 @@ def GetReflPos(refl,im,wave,A,pfx,hfx,phfx,calcControls,parmDict): pos = 2.0*asind(wave/(2.0*d)) const = 9.e-2/(np.pi*parmDict[hfx+'Gonio. radius']) #shifts in microns if 'Bragg' in calcControls[hfx+'instType']: #trans(=1/mueff) in cm - pos -= const*(4.*parmDict[hfx+'Shift']*cosd(pos/2.0)+parmDict[hfx+'Transparency']*sind(pos)*100.0) + pos -= const*(4.*parmDict[hfx+'Shift']*cosd(pos/2.0)+parmDict[hfx+'Transparency']*sind(pos)*100.0) else: #Debye-Scherrer - simple but maybe not right pos -= 2.0*const*(parmDict[hfx+'DisplaceX']*cosd(pos)+(parmDict[hfx+'DisplaceY']+parmDict[phfx+'LayerDisp'])*sind(pos)) pos += parmDict[hfx+'Zero'] @@ -3453,7 +3460,7 @@ def GetReflPosDerv(refl,im,wave,A,pfx,hfx,phfx,calcControls,parmDict): h,k,l = [h+m*vec[0],k+m*vec[1],l+m*vec[2]] #do proj of hklm to hkl so dPdA & dPdV come out right else: m = 0 - h,k,l = refl[:3] + h,k,l = refl[:3] dstsq = G2lat.calc_rDsq(np.array([h,k,l]),A) dst = np.sqrt(dstsq) dsp = 1./dst @@ -3491,9 +3498,9 @@ def GetReflPosDerv(refl,im,wave,A,pfx,hfx,phfx,calcControls,parmDict): dpdV = np.array([2.*h*A[0]+k*A[3]+l*A[4],2*k*A[1]+h*A[3]+l*A[5], 2*l*A[2]+h*A[4]+k*A[5]])*m*parmDict[hfx+'difC']*dsp**3/2. return dpdA,dpdZ,dpdDC,dpdDA,dpdDB,dpdV - + def GetHStrainShift(refl,im,SGData,phfx,hfx,calcControls,parmDict): - '''Computes the shifts in peak position due to the Hydrostatic strain + '''Computes the shifts in peak position due to the Hydrostatic strain (HStrain, Dij terms). This routine is not used anywhere ''' @@ -3529,9 +3536,9 @@ def GetHStrainShift(refl,im,SGData,phfx,hfx,calcControls,parmDict): return Dij*refl[5+im]/refl[4+im]**2 else: return -Dij*parmDict[hfx+'difC']*0.5*refl[4+im]**2 - + def GetHStrainShiftDerv(refl,im,SGData,phfx,hfx,calcControls,parmDict): - '''Computes the derivatives due to the shifts in peak position from Hydrostatic strain + '''Computes the derivatives due to the shifts in peak position from Hydrostatic strain (HStrain, Dij terms). ''' laue = SGData['SGLaue'] @@ -3569,11 +3576,11 @@ def GetHStrainShiftDerv(refl,im,SGData,phfx,hfx,calcControls,parmDict): for item in dDijDict: dDijDict[item] *= -parmDict[hfx+'difC']*refl[4+im]**3/2. return dDijDict - + def GetDij(phfx,SGData,parmDict): HSvals = [parmDict[phfx+name] for name in G2spc.HStrainNames(SGData)] return G2spc.HStrainVals(HSvals,SGData) - + def GetFobsSq(Histograms,Phases,parmDict,calcControls): '''Compute the observed structure factors for Powder histograms and store in reflection array Multiprocessing support added @@ -3606,7 +3613,7 @@ def GetFobsSq(Histograms,Phases,parmDict,calcControls): ymb = np.array(y-yb) ymb = np.where(ymb,ymb,1.0) ycmb = np.array(yc-yb) - ratio = 1./np.where(ycmb,ycmb/ymb,1.e10) + ratio = 1./np.where(ycmb,ycmb/ymb,1.e10) refLists = Histogram['Reflection Lists'] for phase in refLists: if phase not in Phases: #skips deleted or renamed phases silently! @@ -3628,18 +3635,18 @@ def GetFobsSq(Histograms,Phases,parmDict,calcControls): nExcl = 0 # test to see if we are using multiprocessing below useMP,ncores = G2mp.InitMP() - if len(refDict['RefList']) < 100: useMP = False + if len(refDict['RefList']) < 100: useMP = False if useMP: # multiprocessing: create a set of initialized Python processes MPpool = mp.Pool(G2mp.ncores,G2mp.InitFobsSqGlobals, [x,ratio,shl,xB,xF,im,lamRatio,kRatio,xMask,Ka2]) profArgs = [[] for i in range(G2mp.ncores)] else: G2mp.InitFobsSqGlobals(x,ratio,shl,xB,xF,im,lamRatio,kRatio,xMask,Ka2) - + if histType[2] in ['A','B','C']: # are we multiprocessing? for iref,refl in enumerate(refDict['RefList']): - if useMP: + if useMP: profArgs[iref%G2mp.ncores].append((refl,iref)) else: if 'C' in histType: @@ -3651,7 +3658,7 @@ def GetFobsSq(Histograms,Phases,parmDict,calcControls): if type(icod) is tuple: refl[8+im] = icod[0] sumInt += icod[1] - if parmDict.get(phfx+'LeBail'): + if parmDict.get(phfx+'LeBail'): refl[9+im] = refl[8+im] elif icod == -1: refl[3+im] *= -1 @@ -3677,14 +3684,14 @@ def GetFobsSq(Histograms,Phases,parmDict,calcControls): refDict['RefList'][irefl][9+im] = refDict['RefList'][irefl][8+im] elif 'T' in histType: for iref,refl in enumerate(refDict['RefList']): - if useMP: + if useMP: profArgs[iref%G2mp.ncores].append((refl,iref)) else: icod = G2mp.ComputeFobsSqTOF(refl,iref) if type(icod) is tuple: refl[8+im] = icod[0] sumInt += icod[1] - if parmDict.get(phfx+'LeBail'): + if parmDict.get(phfx+'LeBail'): refl[9+im] = refl[8+im] elif icod == -1: refl[3+im] *= -1 @@ -3704,14 +3711,14 @@ def GetFobsSq(Histograms,Phases,parmDict,calcControls): refDict['RefList'][irefl][9+im] = refDict['RefList'][irefl][8+im] elif 'E' in histType: for iref,refl in enumerate(refDict['RefList']): - if useMP: + if useMP: profArgs[iref%G2mp.ncores].append((refl,iref)) else: icod = G2mp.ComputeFobsSqED(refl,iref) if type(icod) is tuple: refl[8+im] = icod[0] sumInt += icod[1] - if parmDict.get(phfx+'LeBail'): + if parmDict.get(phfx+'LeBail'): refl[9+im] = refl[8+im] elif icod == -1: refl[3+im] *= -1 @@ -3755,11 +3762,11 @@ def GetFobsSq(Histograms,Phases,parmDict,calcControls): Histogram['Residuals']['hId'] = Histograms[histogram]['hId'] if GSASIIpath.GetConfigValue('Show_timing',False): print ('GetFobsSq t=',time.time()-starttime) - + def getPowderProfile(parmDict,x,varylist,Histogram,Phases,calcControls,pawleyLookup,histogram=None): 'Computes the powder pattern for a histogram based on contributions from all used phases' if GSASIIpath.GetConfigValue('Show_timing',False): starttime = time.time() - + def GetReflSigGamCW(refl,im,wave,G,GB,phfx,calcControls,parmDict): U = parmDict[hfx+'U'] V = parmDict[hfx+'V'] @@ -3774,7 +3781,7 @@ def GetReflSigGamCW(refl,im,wave,G,GB,phfx,calcControls,parmDict): gam = X/cosd(refl[5+im]/2.0)+Y*tanPos+Sgam+Z #save peak gamma gam = max(0.001,gam) return sig,gam - + def GetReflSigGamTOF(refl,im,G,GB,phfx,calcControls,parmDict): sig = parmDict[hfx+'sig-0']+parmDict[hfx+'sig-1']*refl[4+im]**2+ \ parmDict[hfx+'sig-2']*refl[4+im]**4+parmDict[hfx+'sig-q']*refl[4+im] @@ -3783,7 +3790,7 @@ def GetReflSigGamTOF(refl,im,G,GB,phfx,calcControls,parmDict): sig += Ssig gam += Sgam return sig,gam - + def GetReflSigGamED(refl,im,G,GB,phfx,calcControls,parmDict): sig = parmDict[hfx+'A']*refl[5+im]**2+parmDict[hfx+'B']*refl[5+im]+parmDict[hfx+'C'] gam = parmDict[hfx+'X']*refl[5+im]**2+parmDict[hfx+'Y']*refl[5+im]+parmDict[hfx+'Z'] @@ -3791,12 +3798,12 @@ def GetReflSigGamED(refl,im,G,GB,phfx,calcControls,parmDict): sig += Ssig gam += Sgam return sig,gam - + def GetReflAlpBet(refl,im,hfx,parmDict): alp = parmDict[hfx+'alpha']/refl[4+im] bet = parmDict[hfx+'beta-0']+parmDict[hfx+'beta-1']/refl[4+im]**4+parmDict[hfx+'beta-q']/refl[4+im]**2 return alp,bet - + def GetPinkReflAlpBet(refl,im,hfx,parmDict): sinPos = sind(refl[5+im]/2.0) alp = max(0.1,parmDict[hfx+'alpha-0']+parmDict[hfx+'alpha-1']*sinPos) @@ -3808,7 +3815,7 @@ def SavePartial(phase,y): pickle.dump(phase,phPartialFP) pickle.dump(y,phPartialFP) phPartialFP.close() - + hId = Histogram['hId'] hfx = ':%d:'%(hId) bakType = calcControls[hfx+'bakType'] @@ -3822,7 +3829,7 @@ def SavePartial(phase,y): Nphase = len(Histogram['Reflection Lists']) #partials made ony if Nphase > 1 histType = calcControls[hfx+'histType'] if phasePartials: - + phPartialFP = open(phasePartials,'ab') # create histogram header pickle.dump(None,phPartialFP) pickle.dump(hId,phPartialFP) @@ -3833,7 +3840,7 @@ def SavePartial(phase,y): pickle.dump(None,phPartialFP) pickle.dump(None,phPartialFP) phPartialFP.close() - + if histType[2] in ['A','B','C']: if 'B' not in histType: shl = max(parmDict[hfx+'SH/L'],0.002) @@ -3863,7 +3870,7 @@ def SavePartial(phase,y): SSGData = Phase['General']['SSGData'] im = 1 #offset in SS reflection list Dij = GetDij(phfx,SGData,parmDict) - A = [parmDict[pfx+'A%d'%(i)]+Dij[i] for i in range(6)] #TODO: need to do something if Dij << 0. + A = [parmDict[pfx+'A%d'%(i)]+Dij[i] for i in range(6)] #TODO: need to do something if Dij << 0. G,g = G2lat.A2Gmat(A) #recip & real metric tensors if np.any(np.diag(G)<0.): msg = 'Invalid metric tensor for phase #{}\n ({})'.format( @@ -3892,7 +3899,7 @@ def SavePartial(phase,y): badPeak = False # test to see if we are using multiprocessing here useMP,ncores = G2mp.InitMP() - if len(refDict['RefList']) < 100: useMP = False + if len(refDict['RefList']) < 100: useMP = False if phasePartials: useMP = False ypartial = np.zeros_like(yb) @@ -3915,7 +3922,7 @@ def SavePartial(phase,y): if histType[2] in ['B','A']: refl[12+im:14+im] = GetPinkReflAlpBet(refl,im,hfx,parmDict) refl[11+im],refl[14+im],refl[15+im],refl[16+im] = GetIntensityCorr(refl,im,Uniq,G,g,pfx,phfx,hfx,SGData,calcControls,parmDict) - refl[11+im] *= Vst*Lorenz + refl[11+im] *= Vst*Lorenz if Phase['General'].get('doPawley'): try: if im: @@ -3930,7 +3937,7 @@ def SavePartial(phase,y): Wd,fmin,fmax = G2pwd.getWidthsCW(refl[5+im],refl[6+im],refl[7+im],shl) elif 'B' in histType: Wd,fmin,fmax = G2pwd.getWidthsCWB(refl[5+im],refl[12+im],refl[13+im],refl[6+im],refl[7+im]) - else: #'A' + else: #'A' Wd,fmin,fmax = G2pwd.getWidthsCWA(refl[5+im],refl[12+im],refl[13+im],refl[6+im],refl[7+im],shl) iBeg = np.searchsorted(x,refl[5+im]-fmin) iFin = np.searchsorted(x,refl[5+im]+fmax) @@ -3952,14 +3959,14 @@ def SavePartial(phase,y): elif 'B' in histType: fp = G2pwd.getEpsVoigt(refl[5+im],refl[12+im],refl[13+im],refl[6+im]/1.e4,refl[7+im]/100.,ma.getdata(x[iBeg:iFin]))[0]/100. else: #'A' - fp = G2pwd.getExpFCJVoigt3(refl[5+im],refl[12+im],refl[13+im],refl[6+im],refl[7+im],shl,ma.getdata(x[iBeg:iFin]))[0] + fp = G2pwd.getExpFCJVoigt3(refl[5+im],refl[12+im],refl[13+im],refl[6+im],refl[7+im],shl,ma.getdata(x[iBeg:iFin]))[0] yc[iBeg:iFin] += refl[11+im]*refl[9+im]*fp #>90% of time spent here if phasePartials: ypartial[iBeg:iFin] += refl[11+im]*refl[9+im]*fp if Ka2 and 'B' not in histType: pos2 = refl[5+im]+lamRatio*tand(refl[5+im]/2.0) # + 360/pi * Dlam/lam * tan(th) if 'C' in histType: Wd,fmin,fmax = G2pwd.getWidthsCW(pos2,refl[6+im],refl[7+im],shl) - else: #'A' + else: #'A' Wd,fmin,fmax = G2pwd.getWidthsCWA(pos2,refl[12+im],refl[13+im],refl[6+im],refl[7+im],shl) iBeg = np.searchsorted(x,pos2-fmin) iFin = np.searchsorted(x,pos2+fmax) @@ -3975,12 +3982,12 @@ def SavePartial(phase,y): if 'C' in histType: fp2 = G2pwd.getFCJVoigt3(pos2,refl[6+im],refl[7+im],shl,ma.getdata(x[iBeg:iFin]))[0] else: #'A' - fp2 = G2pwd.getExpFCJVoigt3(pos2,refl[12+im],refl[13+im],refl[6+im],refl[7+im],shl,ma.getdata(x[iBeg:iFin]))[0] + fp2 = G2pwd.getExpFCJVoigt3(pos2,refl[12+im],refl[13+im],refl[6+im],refl[7+im],shl,ma.getdata(x[iBeg:iFin]))[0] yc[iBeg:iFin] += refl[11+im]*refl[9+im]*kRatio*fp2 #and here if phasePartials: ypartial[iBeg:iFin] += refl[11+im]*refl[9+im]*kRatio*fp2 - + elif 'E' in histType: # Energy-dispersive X-ray - + for iref,refl in enumerate(refDict['RefList']): if im: h,k,l,m = refl[:4] @@ -3991,7 +3998,7 @@ def SavePartial(phase,y): # refl[5+im] += GetHStrainShift(refl,im,SGData,phfx,hfx,calcControls,parmDict) #apply hydrostatic strain shift refl[6+im:8+im] = GetReflSigGamED(refl,im,G,GB,phfx,calcControls,parmDict) #peak sig & gam refl[11+im] = 1.0 #no intensity corrections; default = 1.0 - + if Phase['General'].get('doPawley'): try: if im: @@ -4017,8 +4024,8 @@ def SavePartial(phase,y): fp = G2pwd.getPsVoigt(refl[5+im],refl[6+im]*1.e4,refl[7+im]*100.,ma.getdata(x[iBeg:iFin]))[0] yc[iBeg:iFin] += refl[9+im]*fp if phasePartials: ypartial[iBeg:iFin] += refl[11+im]*refl[9+im]*fp - - elif 'T' in histType: # TOF + + elif 'T' in histType: # TOF for iref,refl in enumerate(refDict['RefList']): if im: h,k,l,m = refl[:4] @@ -4057,13 +4064,13 @@ def SavePartial(phase,y): fp = G2pwd.getEpsVoigt(refl[5+im],refl[12+im],refl[13+im],refl[6+im],refl[7+im],ma.getdata(x[iBeg:iFin]))[0] yc[iBeg:iFin] += refl[11+im]*refl[9+im]*fp if phasePartials: ypartial[iBeg:iFin] += refl[11+im]*refl[9+im]*fp - + if phasePartials: #for all flavors of PWDR if Nphase > 1: SavePartial(phase,ypartial) else: SavePartial(phase,[]) - + # print 'profile calc time: %.3fs'%(time.time()-time0) if useMP and 'C' in histType: for y in MPpool.imap_unordered(G2mp.ComputePwdrProfCW,profArgs): @@ -4090,21 +4097,21 @@ def SavePartial(phase,y): if GSASIIpath.GetConfigValue('Show_timing',False): print ('getPowderProfile t=%.3f'%(time.time()-starttime)) return yc,yb - + def getPowderProfileDerv(args): '''Computes the derivatives of the computed powder pattern with respect to all refined parameters. Used for single processor & Multiprocessor versions ''' - import pytexture as ptx +# from . import pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics for each processor parmDict,x,varylist,Histogram,Phases,rigidbodyDict,calcControls,pawleyLookup,dependentVars = args[:9] prc,tprc,histogram = 0,1,None if len(args) >= 10: prc=args[9] if len(args) >= 11: tprc=args[10] if len(args) >= 12: histogram=args[11] - - def cellVaryDerv(pfx,SGData,dpdA): + + def cellVaryDerv(pfx,SGData,dpdA): if SGData['SGLaue'] in ['-1',]: return [[pfx+'A0',dpdA[0]],[pfx+'A1',dpdA[1]],[pfx+'A2',dpdA[2]], [pfx+'A3',dpdA[3]],[pfx+'A4',dpdA[4]],[pfx+'A5',dpdA[5]]] @@ -4122,10 +4129,10 @@ def cellVaryDerv(pfx,SGData,dpdA): elif SGData['SGLaue'] in ['6/m','6/mmm','3m1', '31m', '3']: return [[pfx+'A0',dpdA[0]],[pfx+'A2',dpdA[2]]] elif SGData['SGLaue'] in ['3R', '3mR']: - return [[pfx+'A0',dpdA[0]+dpdA[1]+dpdA[2]],[pfx+'A3',dpdA[3]+dpdA[4]+dpdA[5]]] + return [[pfx+'A0',dpdA[0]+dpdA[1]+dpdA[2]],[pfx+'A3',dpdA[3]+dpdA[4]+dpdA[5]]] elif SGData['SGLaue'] in ['m3m','m3']: return [[pfx+'A0',dpdA[0]]] - + # create a list of dependent variables and set up a dictionary to hold their derivatives # dependentVars = G2mv.GetDependentVars() depDerivDict = {} @@ -4200,7 +4207,7 @@ def cellVaryDerv(pfx,SGData,dpdA): dFdvDict = SStructureFactorDerv(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDict) dFdvDict.update(SStructureFactorDerv2(refDict,im,G,hfx,pfx,SGData,SSGData,calcControls,parmDict)) else: - if Phase['General']['Type'] == 'magnetic': + if Phase['General']['Type'] == 'magnetic': dFdvDict = MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict) dFdvDict.update(MagStructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict)) else: @@ -4283,7 +4290,7 @@ def cellVaryDerv(pfx,SGData,dpdA): for i in range(7): dMdpk[i] = refl[11+im]*refl[9+im]*dMdipk[i] dervDict = {'int':dMdpk[0],'pos':dMdpk[1],'alp':dMdpk[2],'bet':dMdpk[3],'sig':dMdpk[4],'gam':dMdpk[5], - 'shl':dMdpk[6],'L1/L2':np.zeros_like(dMdpk[0])} + 'shl':dMdpk[6],'L1/L2':np.zeros_like(dMdpk[0])} if Ka2: pos2 = refl[5+im]+lamRatio*tanth # + 360/pi * Dlam/lam * tan(th) iBeg2 = np.searchsorted(x,pos2-fmin) @@ -4313,7 +4320,7 @@ def cellVaryDerv(pfx,SGData,dpdA): dMdpk2[7] = refl[11+im]*dMdipk2[0] dervDict = {'int':dMdpk2[0],'pos':dMdpk2[1],'alp':dMdpk2[2],'bet':dMdpk2[3],'sig':dMdpk2[4],'gam':dMdpk2[5], 'shl':dMdpk[7],'L1/L2':dMdpk2[7]*refl[9]} - + elif 'T' in histType: lenBF = iFin-iBeg if lenBF < 0: #bad peak coeff @@ -4322,7 +4329,7 @@ def cellVaryDerv(pfx,SGData,dpdA): dMdipk = G2pwd.getdEpsVoigt(refl[5+im],refl[12+im],refl[13+im],refl[6+im],refl[7+im],ma.getdata(x[iBeg:iFin])) for i in range(6): dMdpk[i] += refl[11+im]*refl[9+im]*dMdipk[i] - dervDict = {'int':dMdpk[0],'pos':dMdpk[1],'alp':dMdpk[2],'bet':dMdpk[3],'sig':dMdpk[4],'gam':dMdpk[5]} + dervDict = {'int':dMdpk[0],'pos':dMdpk[1],'alp':dMdpk[2],'bet':dMdpk[3],'sig':dMdpk[4],'gam':dMdpk[5]} elif 'E' in histType: lenBF = iFin-iBeg if lenBF < 0: #bad peak coeff @@ -4331,7 +4338,7 @@ def cellVaryDerv(pfx,SGData,dpdA): dMdipk = G2pwd.getdPsVoigt(refl[5+im],refl[6+im]*10**4,refl[7+im]*100.,ma.getdata(x[iBeg:iFin])) for i in range(4): dMdpk[i] = refl[9+im]*dMdipk[i] - dervDict = {'int':dMdpk[0],'pos':-dMdpk[1],'sig':dMdpk[2]*1.e4,'gam':dMdpk[3]*100.} + dervDict = {'int':dMdpk[0],'pos':-dMdpk[1],'sig':dMdpk[2]*1.e4,'gam':dMdpk[3]*100.} if Phase['General'].get('doPawley'): dMdpw = np.zeros(len(x)) try: @@ -4476,15 +4483,15 @@ def cellVaryDerv(pfx,SGData,dpdA): dMdv[varylist.index(phfx+name)][iBeg:iFin] += parmDict[phfx+'Scale']*dFdvDict[phfx+name][iref]*dervDict['int']/refl[9+im] if Ka2 and iFin2-iBeg2: dMdv[varylist.index(phfx+name)][iBeg2:iFin2] += parmDict[phfx+'Scale']*dFdvDict[phfx+name][iref]*dervDict2['int']/refl[9+im] - elif phfx+name in dependentVars: + elif phfx+name in dependentVars: depDerivDict[phfx+name][iBeg:iFin] += parmDict[phfx+'Scale']*dFdvDict[phfx+name][iref]*dervDict['int']/refl[9+im] if Ka2 and iFin2-iBeg2: - depDerivDict[phfx+name][iBeg2:iFin2] += parmDict[phfx+'Scale']*dFdvDict[phfx+name][iref]*dervDict2['int']/refl[9+im] + depDerivDict[phfx+name][iBeg2:iFin2] += parmDict[phfx+'Scale']*dFdvDict[phfx+name][iref]*dervDict2['int']/refl[9+im] if not Phase['General'].get('doPawley') and not parmDict.get(phfx+'LeBail') and 'E' not in calcControls[hfx+'histType']: #do atom derivatives - for RB,F,X & U so far - how do I scale mixed phase constraints? corr = 0. corr2 = 0. - if refl[9+im]: + if refl[9+im]: corr = dervDict['int']/refl[9+im] if Ka2 and iFin2-iBeg2: corr2 = dervDict2['int']/refl[9+im] @@ -4500,7 +4507,7 @@ def cellVaryDerv(pfx,SGData,dpdA): dMdv[:,ma.getmaskarray(x)] = 0. # instead of masking, zero out masked values #G2mv.Dict2Deriv(varylist,depDerivDict,dMdv) return dMdv,depDerivDict - + def UserRejectHKL(ref,im,userReject): if ref[5+im]/ref[6+im] < userReject['minF/sig']: return False @@ -4511,13 +4518,13 @@ def UserRejectHKL(ref,im,userReject): elif abs(ref[5+im]-ref[7+im])/ref[6+im] > userReject['MaxDF/F']: return False return True - + def dervHKLF(Histogram,Phase,calcControls,varylist,parmDict,rigidbodyDict): '''Loop over reflections in a HKLF histogram and compute derivatives of the fitting - model (M) with respect to all parameters. Independent and dependant dM/dp arrays + model (M) with respect to all parameters. Independent and dependant dM/dp arrays are returned to either dervRefine or HessRefine. - :returns: + :returns: ''' hId = Histogram['hId'] hfx = ':%d:'%(hId) @@ -4544,7 +4551,7 @@ def dervHKLF(Histogram,Phase,calcControls,varylist,parmDict,rigidbodyDict): if len(TwinLaw) > 1: dFdvDict = StructureFactorDervTw2(refDict,G,hfx,pfx,SGData,calcControls,parmDict) else: #correct!! - if Phase['General']['Type'] == 'magnetic': + if Phase['General']['Type'] == 'magnetic': dFdvDict = MagStructureFactorDerv(refDict,G,hfx,pfx,SGData,calcControls,parmDict) dFdvDict.update(MagStructureFactorDerv2(refDict,G,hfx,pfx,SGData,calcControls,parmDict)) else: @@ -4601,10 +4608,10 @@ def dervHKLF(Histogram,Phase,calcControls,varylist,parmDict,rigidbodyDict): if phfx+'Scale' in varylist: dMdvh[varylist.index(phfx+'Scale')][iref] = w*ref[7+im]*ref[11+im]/parmDict[phfx+'Scale'] #OK elif phfx+'Scale' in dependentVars: - depDerivDict[phfx+'Scale'][iref] = w*ref[7+im]*ref[11+im]/parmDict[phfx+'Scale'] #OK + depDerivDict[phfx+'Scale'][iref] = w*ref[7+im]*ref[11+im]/parmDict[phfx+'Scale'] #OK for item in ['Ep','Es','Eg']: #OK! if phfx+item in varylist and phfx+item in dervDict: - dMdvh[varylist.index(phfx+item)][iref] = w*dervDict[phfx+item]/ref[11+im] + dMdvh[varylist.index(phfx+item)][iref] = w*dervDict[phfx+item]/ref[11+im] elif phfx+item in dependentVars and phfx+item in dervDict: depDerivDict[phfx+item][iref] = w*dervDict[phfx+item]/ref[11+im] for item in ['BabA','BabU']: @@ -4613,7 +4620,7 @@ def dervHKLF(Histogram,Phase,calcControls,varylist,parmDict,rigidbodyDict): elif phfx+item in dependentVars: depDerivDict[phfx+item][iref] = w*dFdvDict[phfx+item][iref]*parmDict[phfx+'Scale']*ref[11+im] return dMdvh,depDerivDict,wdf - + def dervRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg): '''Loop over histograms and compute derivatives of the fitting @@ -4669,7 +4676,7 @@ def dervRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dl if np.any(pVals): dpdv = penaltyDeriv(pNames,pVals,HistoPhases,calcControls,parmDict,varylist) dMdV = np.concatenate((dMdV.T,(np.sqrt(pWt)*dpdv).T)).T - + return dMdV def HessRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg): @@ -4725,9 +4732,9 @@ def HessRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dl if dMdvh is None: dMdvh = dmdv depDerivDict = depDerivs - else: + else: dMdvh += dmdv - for key in depDerivs.keys(): depDerivDict[key] += depDerivs[key] + for key in depDerivs.keys(): depDerivDict[key] += depDerivs[key] MPpool.terminate() else: dMdvh,depDerivDict = getPowderProfileDerv([parmDict,x[xB:xF], @@ -4798,10 +4805,10 @@ def HessRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dl Hess += np.inner(dpdv*pWt,dpdv) return Vec,Hess -def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg=None): +def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg=None): '''Computes the point-by-point discrepancies between every data point in every histogram and the observed value. Used in the Jacobian, Hessian & numeric least-squares to compute function - + :returns: an np array of differences between observed and computed diffraction values. ''' Values2Dict(parmDict, varylist, values) @@ -4895,7 +4902,7 @@ def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg TwinLaw = calcControls[phfx+'TwinLaw'] im = 0 if parmDict[phfx+'Scale'] < 0.: - parmDict[phfx+'Scale'] = .001 + parmDict[phfx+'Scale'] = .001 if Phase['General'].get('Modulated',False): SSGData = Phase['General']['SSGData'] im = 1 #offset in SS reflection list @@ -4956,7 +4963,7 @@ def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg sumSSwYo[ind] += (w*ref[5+im])**2 #w*Fo^2 sumSSwdf2[ind] += df[i]**2 SSnobs[ind] += 1 - maxH = max(maxH,ind) + maxH = max(maxH,ind) else: if ref[3+im]: ref[3+im] = -abs(ref[3+im]) #mark as rejected @@ -4988,10 +4995,10 @@ def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg sumSSFo2[ind] += ref[5+im] sumSSdF[ind] += abs(Fo-Fc) sumSSdF2[ind] += abs(ref[5+im]-ref[7+im]) - sumSSwYo[ind] += (w*Fo)**2 + sumSSwYo[ind] += (w*Fo)**2 sumSSwdf2[ind] += df[i]**2 - SSnobs[ind] += 1 - maxH = max(maxH,ind) + SSnobs[ind] += 1 + maxH = max(maxH,ind) else: if ref[3+im]: ref[3+im] = -abs(ref[3+im]) #mark as rejected @@ -5002,7 +5009,7 @@ def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg if (Scale < 0.8 or Scale > 1.2) and phfx+'Scale' in varylist: print ('New scale: %.4f'%(Scale*parmDict[phfx+'Scale'])) indx = varylist.index(phfx+'Scale') - values[indx] = Scale*parmDict[phfx+'Scale'] + values[indx] = Scale*parmDict[phfx+'Scale'] Histogram['Residuals']['Nobs'] = nobs Histogram['Residuals']['sumwYo'] = sumwYo SumwYo += sumwYo @@ -5016,7 +5023,7 @@ def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg Histogram['Residuals'][phfx+'SSRf'] = 100.*sumSSdF[:maxH+1]/sumSSFo[:maxH+1] Histogram['Residuals'][phfx+'SSRf^2'] = 100.*sumSSdF2[:maxH+1]/sumSSFo2[:maxH+1] Histogram['Residuals'][phfx+'SSNref'] = SSnobs[:maxH+1] - Histogram['Residuals']['SSwR'] = np.sqrt(sumSSwdf2[:maxH+1]/sumSSwYo[:maxH+1])*100. + Histogram['Residuals']['SSwR'] = np.sqrt(sumSSwdf2[:maxH+1]/sumSSwYo[:maxH+1])*100. Nobs += nobs Nrej += nrej Next += Nexti @@ -5068,18 +5075,18 @@ def errRefine(values,HistoPhases,parmDict,varylist,calcControls,pawleyLookup,dlg return M def calcMassFracs(varyList,covMatrix,Phases,hist,hId): - '''Compute mass fractions and their uncertainties for all - phases in a histogram. Computed using the covariance matrix, - along with the derivatives for the mass fraction equations. + '''Compute mass fractions and their uncertainties for all + phases in a histogram. Computed using the covariance matrix, + along with the derivatives for the mass fraction equations. - :param list varyList: list of varied parameters + :param list varyList: list of varied parameters :param np.array covMatrix: covariance matrix, order of rows and columns must match varyList - :param dict Phases: data structure (from tree or .gpx) with all + :param dict Phases: data structure (from tree or .gpx) with all phase information :param str hist: name of selected histogram :param int hId: number of current histogram - :returns: valDict,sigDict which contain the mass fraction values and + :returns: valDict,sigDict which contain the mass fraction values and sigmas, keyed by "ph:h:WgtFrac" ''' # Compute mass normalizer & assemble list of refined PF terms diff --git a/GSASII/ISODISTORT.py b/GSASII/ISODISTORT.py index fb5e311f8..e28a5d624 100644 --- a/GSASII/ISODISTORT.py +++ b/GSASII/ISODISTORT.py @@ -8,13 +8,15 @@ except: print('Module requests not installed, access to ISODISTORT not possible') import copy -import GSASIIscriptable as G2sc +from . import GSASIIscriptable as G2sc +from . import GSASIIctrlGUI as G2G #import tempfile isouploadsite = 'https://stokes.byu.edu/iso/isodistortuploadfile.php' isoformsite = 'https://iso.byu.edu/iso/isodistortform.php' def HandleError(out): - open('out.html','wb').write(out.encode("utf-8")) + with open('out.html','wb') as fp: + fp.write(out.encode("utf-8")) url = os.path.realpath('out.html') try: os.startfile(url) @@ -23,20 +25,20 @@ def HandleError(out): subp.call(['open', url]) except: print('Could not open URL') - + def UploadCIF(cifname): - #upload cif file to BYU web site + #upload cif file to BYU web site ciffile = open(cifname,'rb') up1 = {'toProcess':(cifname,ciffile),} out1 = requests.post(isouploadsite,files=up1).text ciffile.close() - - #retrieve BYU temp file name for cif file - + + #retrieve BYU temp file name for cif file + pos = out1.index('')+pos @@ -168,14 +171,14 @@ def GetISODISTORT(Phase): pos = out3[posF:].index('RADIO')+posF except ValueError: break - + if parentcif == 'ISOin.cif' or childcif == 'ISOin.cif': os.remove('ISOin.cif') - + return radio,data2 def GetISOcif(out4,method): - + try: pos = out4.index('

','').replace('','').replace('','').replace('','') @@ -139,13 +135,9 @@ def GetNonStdSubgroupsmag(SGData, kvec,star=False,landau=False,maximal=False): :returns: (error,text) error: if True no error or False; where text containts a possible web page text ''' - print(''' + print(f''' For use of k-SUBGROUPSMAG, please cite: - Symmetry-Based Computational Tools for Magnetic Crystallography, - J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and M.I. Aroyo - Annu. Rev. Mater. Res. 2015. 45,217-48. - doi: 10.1146/annurev-matsci-070214-021008 - ''') +{G2G.GetCite('Bilbao: k-SUBGROUPSMAG',wrap=70,indent=5)}''') def getSpGrp(item): return item.replace('','').replace('','').replace('','').replace('','') @@ -279,12 +271,6 @@ def parseBilbaoCheckLattice(page): found.append((acell,cellmat)) return found -GetStdSGsetCite = '''Using Bilbao Crystallographic Server utility IDENTIFY GROUP. Please cite: -Symmetry-Based Computational Tools for Magnetic Crystallography, -J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and -M.I. Aroyo, Annu. Rev. Mater. Res. 2015. 45,217-48. -doi: 10.1146/annurev-matsci-070214-021008''' - def GetStdSGset(SGData=None, oprList=[]): '''Determine the standard setting for a space group from either a list of symmetry operators or a space group object using the @@ -312,8 +298,9 @@ def GetStdSGset(SGData=None, oprList=[]): elif SGData: SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(SGData) oprList = [x.lower() for x in SymOpList] - - print('\n'+GetStdSGsetCite+'\n') + print('Using Bilbao Crystallographic Server utility IDENTIFY GROUP. '+ + 'Please cite:\n'+ + G2G.GetCite('Bilbao: k-SUBGROUPSMAG',wrap=70,indent=5)) postdict = {'tipog':'gesp','generators':'\n'.join(oprList)} page = GSASIIpath.postURL(Site,postdict,timeout=timeout) if not page: @@ -391,8 +378,10 @@ def GetSupergroup(SGnum,dlg=None): out1 = GSASIIpath.postURL(Site,{'gnum':SGnum,'show':'show','click':click} ,timeout=timeout) if not out1: return None - #open(f'/tmp/{click}.html','w').write(out1) - #open(f'/tmp/last.html','w').write(out1) + #with open(f'/tmp/{click}.html','w') as fp: + # fp.write(out1) + #with open(f'/tmp/last.html','w') as fp: + # fp.write(out1) if dlg: dlg.Update(2+i,newmsg=f'processing {line[1]}, #{i+1} of {len(xforms)}') mvlist = [] for s in [i.split('')[0] for i in out1.split('
')[1::2]]:
@@ -478,13 +467,6 @@ def applySym(xform,cell):
             ))
     return cellsList
 
-BilbaoSymSearchCite = '''Using the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) 
-program; Please cite:
-C. Capillas, E.S. Tasci, G. de la Flor, D. Orobengoa, J.M. Perez-Mato 
-and M.I. Aroyo. "A new computer tool at the Bilbao Crystallographic 
-Server to detect and characterize pseudosymmetry". Z. Krist. (2011), 
-226(2), 186-196 DOI:10.1524/zkri.2011.1321.'''
-
 def BilbaoSymSearch1(sgnum, phase, maxdelta=2, angtol=None,
                          pagelist=None, keepCell=False):
     '''Perform a search for a supergroup consistent with a phase
@@ -525,7 +507,9 @@ def BilbaoSymSearch1(sgnum, phase, maxdelta=2, angtol=None,
         contain possible supergroup settings.
     '''
     
-    print('\n'+BilbaoSymSearchCite+'\n')
+    print(f'''\nUsing the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) 
+program; Please cite:
+{G2G.GetCite('Bilbao: PSEUDO',wrap=70,indent=5)}\n''')
 
     postdict = {
         "formulae":'',
@@ -574,7 +558,8 @@ def BilbaoSymSearch1(sgnum, phase, maxdelta=2, angtol=None,
     return scanBilbaoSymSearch1(page0,postdict)+[savedcookies]
         
 def scanBilbaoSymSearch1(page0,postdict):
-    #open(f'/tmp/pseudosym0.html','w').write(page0)
+    #open(f'/tmp/pseudosym0.html','w') as fp:
+    #    fp.write(page0)
     valsdict = {} # base for all supergroups 
     csdict = {}   # supergroups w/default selection value
     rowdict = {}  # information in table row for each supergroup
@@ -811,12 +796,9 @@ def createStdSetting(cifFile,rd):
         return False
     files = {'cifile': open(cifFile,'rb')}
     values = {'strtidy':''}
-    print('''Submitting structure to Bilbao "CIF to Standard Setting" web service
-Please cite:
-Symmetry-Based Computational Tools for Magnetic Crystallography,
-J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and 
-M.I. Aroyo, Annu. Rev. Mater. Res. 2015. 45,217-48.
-doi: 10.1146/annurev-matsci-070214-021008''')
+    print(f'''Submitting structure to Bilbao "CIF to Standard Setting" (strtidy)
+web service. Please cite:
+{G2G.GetCite('Bilbao: PSEUDOLATTICE',wrap=70,indent=5)}''')
     r0 = requests.post(bilbaoSite+cif2std, files=files, data=values)
     structure = r0.text[r0.text.lower().find('
')+5:r0.text.lower().find('
')].strip() spnum,celllist,natom = structure.split('\n')[:3] diff --git a/GSASII/__init__.py b/GSASII/__init__.py new file mode 100644 index 000000000..c61c0c4df --- /dev/null +++ b/GSASII/__init__.py @@ -0,0 +1,34 @@ +import os +import sys + +sys.path.append('.') + +class PathHackingException(Exception): pass + +def make_path_watcher(): + import sys + import traceback + + init_path = tuple(sys.path) + def no_path_hacking(event_name, args): + if event_name != 'import': + return + module, filename, path, meta_path, path_hooks = args + if path is not None and tuple(path) != init_path: + lines = list(traceback.format_stack()) + for line in lines: + print(line.strip()) + print(set(path) - set(init_path)) + #sys.exit(1) + raise PathHackingException(lines[-2].strip()) + return no_path_hacking + +if os.environ.get("GSASII_NOPATHHACKING", ''): + sys.addaudithook(make_path_watcher()) + +del sys, make_path_watcher, os + + +from .GSASIIGUI import main # noqa: E402 + +__all__ = ['main'] diff --git a/GSASII/__main__.py b/GSASII/__main__.py new file mode 100644 index 000000000..8273c4ff5 --- /dev/null +++ b/GSASII/__main__.py @@ -0,0 +1,3 @@ +from . import main + +main() diff --git a/GSASII/atmdata.py b/GSASII/atmdata.py index 6dba7b5ef..553c47f4b 100644 --- a/GSASII/atmdata.py +++ b/GSASII/atmdata.py @@ -258,44 +258,17 @@ } #Orbital form factors from fitting to Table 2.2D in ITXC Vol IV; terms are : a1,b1,a2,b2,a3,b3,a4,b4,c. Slater/Clementi terms from JANA2006 atom.dat file OrbFF = { - 'H':{'ZSlater':1.915,'NSlater':[1,1,2,3,4,5,6,7],'SZE':[0.9577,], - 'popCore':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[1,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.99997002, 1.0, 1], [3e-05, 2.0, 1]]}, - 'Sl core':{'fa':[0. ,0. ,0. ,0.],'fb':[0. ,0. ,0. ,0.],'fc':0.,'Ne':0}, + 'H':{'Sl core':{'fa':[0. ,0. ,0. ,0.],'fb':[0. ,0. ,0. ,0.],'fc':0.,'Ne':0}, + 'Sl val':{'fa':[ .493002, .322912, .140191, .040810],'fb':[ 10.5109, 26.1257, 3.14236, 57.7997],'fc': .003038,'Ne':1}, #Rw: 0.036 'Sl 1s':{'fa':[ .493002, .322912, .140191, .040810],'fb':[ 10.5109, 26.1257, 3.14236, 57.7997],'fc': .003038,'Ne':1}}, #Rw: 0.036 - 'Li':{'ZSlater':1.221,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[2.7217,0.6103,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [1,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.14134, 4.3069, 1], [0.87421, 2.4573, 1], [-0.00221, 7.4527, 2], [0.00693, 1.8504, 2], - [-0.0003, 0.7667, 2], [0.00077, 0.6364, 2], [-0.00529, 6.785, 3]], - '2s': [[-0.02248, 4.3069, 1], [-0.13579001, 2.4573, 1], [-3e-05, 7.4527, 2], [-0.07647, 1.8504, 2], - [0.34053001, 0.7667, 2], [0.71569002, 0.6365, 2], [0.00039, 6.785, 3]]}, - 'Sl core':{'fa':[1.02166 ,0.51562 ,0.43645 ,0.00000],'fb':[2.78445 ,0.82277 ,7.47390 ,0.00000],'fc':0.02579,'Ne':1}, #Rw: 0.036 + 'Li':{'Sl core':{'fa':[1.02166 ,0.51562 ,0.43645 ,0.00000],'fb':[2.78445 ,0.82277 ,7.47390 ,0.00000],'fc':0.02579,'Ne':1}, #Rw: 0.036 + 'Sl val':{'fa':[0.01288 ,-23.81142 ,0.83789 ,23.95868],'fb':[1.16312 ,34.39299 ,137.70976 ,34.69474],'fc':0.00043,'Ne':1}, #Rw: 0.336 'Sl 2s':{'fa':[0.01288 ,-23.81142 ,0.83789 ,23.95868],'fb':[1.16312 ,34.39299 ,137.70976 ,34.69474],'fc':0.00043,'Ne':1}}, #Rw: 0.336 - 'Be':{'ZSlater':1.842,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[3.7120,0.9210,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [2,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.28839001, 5.7667, 1], [0.46643999, 3.7116, 1], [0.24793001, 4.4548, 2], [0.00393, 1.2918, 2], [0.00849, 0.8554, 2], - [-0.00113, 9.9677, 3], [0.05031, 3.7164, 3]], - '2s': [[-0.02019, 5.7531, 1], [-0.15869001, 3.7155, 1], [-0.03506, 4.4661, 2], [0.38819, 1.2924, 2], [0.68549001, 0.8554, 2], - [0.00039, 9.967, 3], [-0.05987, 3.713, 3]]}, - 'Sl core':{'fa':[0.82472 ,1.09276 ,0.00000 ,0.00000],'fb':[2.99431 ,0.82563 ,0.00000 ,0.00000],'fc':0.07974,'Ne':1}, #Rw: 0.174 + 'Be':{'Sl core':{'fa':[0.82472 ,1.09276 ,0.00000 ,0.00000],'fb':[2.99431 ,0.82563 ,0.00000 ,0.00000],'fc':0.07974,'Ne':1}, #Rw: 0.174 + 'Sl val':{'fa':[-0.11643 ,0.02468 ,0.40918 ,0.68050],'fb':[7.15639 ,0.89108 ,88.71237 ,38.42358],'fc':0.00176,'Ne':2}, #Rw: 0.058 'Sl 2s':{'fa':[-0.11643 ,0.02468 ,0.40918 ,0.68050],'fb':[7.15639 ,0.89108 ,88.71237 ,38.42358],'fc':0.00176,'Ne':2}}, #Rw: 0.058 - 'B':{'ZSlater':2.464,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[4.7069,1.2325,1.2306,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [2,1,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.38192001, 7.0227, 1], [0.42017999, 3.9483, 1], [0.23757, 5.7386, 2], [0.00766, 1.5435, 2], - [0.00903, 1.0802, 2], [-0.00148, 12.73, 3], [-0.00188, 2.7645, 3]], - '2s': [[-0.017045, 7.0178, 1], [0.325928, 3.9468, 1], [-0.046811, 5.742, 2], [-0.48416099, 1.5438, 2], - [-0.51874, 1.0804, 2], [-0.000488, 12.7297, 3], [-0.072242, 2.7646, 3]], - '2p': [[0.00769, 5.7416, 2], [0.0452, 2.6341, 2], [0.18422, 1.834, 2], [0.39469999, 1.1919, 2], - [0.43277001, 0.8494, 2]]}, - 'Sl core':{'fa':[1.02279 ,0.97261 ,0.00000 ,0.00000],'fb':[1.64294 ,0.38004 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.270 + 'B':{'Sl core':{'fa':[1.02279 ,0.97261 ,0.00000 ,0.00000],'fb':[1.64294 ,0.38004 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.270 + 'Sl val':{'fa':[0.02291 ,0.38760 ,-0.08188 ,0.66820],'fb':[0.71195 ,58.86439 ,3.45996 ,22.63218],'fc':0.00256,'Ne':3}, #Rw: 0.102 'Sl 2s':{'fa':[1.41960 ,-1.01783 ,0.58512 ,0.00000],'fb':[13.24136 ,10.26945 ,44.06080 ,0.00000],'fc':0.01230,'Ne':2}, #Rw: 1.346 'Sl 2p':{'fa':[-0.00850 ,0.50208 ,0.50519 ,0.00000],'fb':[1.47810 ,22.56168 ,65.03299 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.215 'He core':{'fa':[1.00580 ,0.99264 ,0.00000 ,0.00000],'fb':[1.66638 ,0.38391 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.145 @@ -303,35 +276,16 @@ ' 2p':{'fa':[-0.00665 ,0.52806 ,0.47766 ,0.00000],'fb':[1.12887 ,23.38385 ,67.08957 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.163 ' 2p':{'fa':[0.23370 ,-0.25633 ,-0.03996 ,0.06198],'fb':[9.13064 ,59.11602 ,132.76844 ,2.57310],'fc':0.00058,'Ne':1}, #Rw: 0.140 ' 2s,2p':{'fa':[0.32761 ,0.05313 ,0.11289 ,-0.49850],'fb':[111.68564 ,10477.89929 ,669.03654 ,13.06583],'fc':0.00479,'Ne':1}}, #Rw: 0.961 - 'C':{'ZSlater':3.130,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[5.7026,1.5357,1.5946,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [2,2,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.36311001, 8.5674, 1], [0.43799901, 4.888, 1], [0.23403899, 6.9922, 2], [0.005501, 2.2638, 2], [0.015065, 1.4753, 2], - [-0.005056, 1.1641, 2], [-1.5e-05, 15.4683, 3]], - '2s': [[-0.066035, 8.4936, 1], [0.441836, 4.8791, 1], [-0.087353, 7.0499, 2], [-0.393509, 2.2639, 2], [-0.57860899, 1.4752, 2], - [-0.125934, 1.1635, 2], [-0.000496, 15.466, 3]], - '2p': [[0.007068, 7.0471, 2], [0.071982, 3.2247, 2], [0.23192, 2.1828, 2], [0.410597, 1.4397, 2], - [0.34987, 1.0234, 2]],}, - 'Sl core':{'fa':[0.88920 ,1.10840 ,0.00000 ,0.00000],'fb':[1.22645 ,0.29346 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.132 + 'C':{'Sl core':{'fa':[0.88920 ,1.10840 ,0.00000 ,0.00000],'fb':[1.22645 ,0.29346 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.132 + 'Sl val':{'fa':[0.38986 ,-0.20699 ,0.16639 ,0.64602],'fb':[39.32736 ,1.39105 ,1.13694 ,14.52597],'fc':0.00397,'Ne':4}, #Rw: 0.117 'Sl 2s':{'fa':[1.32138 ,-0.94791 ,0.60974 ,0.00000],'fb':[8.22747 ,6.27454 ,28.01030 ,0.00000],'fc':0.01566,'Ne':2}, #Rw: 0.832 - 'Sl 2p':{'fa':[-0.00821 ,0.50456 ,0.50212 ,0.00000],'fb':[0.83360 ,13.26140 ,40.01779 ,0.00000],'fc':0.00000,'Ne':2}, #Rw: 0.255 - 'He core':{'fa':[0.88409 ,1.11512 ,0.00000 ,0.00000],'fb':[1.22350 ,0.29361 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.072 + 'Sl 2p':{'fa':[0.27875 ,0.53799 ,0.19586 ,-0.01243],'fb':[10.27847 ,24.77761 ,57.86792 ,1.25173],'fc':-0.00027,'Ne':2}, #Rw: 0.020 'He core':{'fa':[0.88409 ,1.11512 ,0.00000 ,0.00000],'fb':[1.22350 ,0.29361 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.072 ' 2s':{'fa':[0.74236 ,-0.47982 ,0.39412 ,0.33499],'fb':[14.55721 ,1.52572 ,1.28134 ,34.89579],'fc':0.00815,'Ne':2}, #Rw: 0.048 ' 2p':{'fa':[-0.00542 ,0.53117 ,0.47306 ,0.00000],'fb':[0.58201 ,13.99332 ,43.31547 ,0.00000],'fc':0.00000,'Ne':2}, #Rw: 0.196 ' 2p':{'fa':[0.06976 ,0.21511 ,-0.22790 ,-0.05803],'fb':[1.67556 ,5.56560 ,35.84610 ,79.49824],'fc':0.00104,'Ne':1}, #Rw: 0.096 ' 2s,2p':{'fa':[0.32364 ,0.04858 ,0.11278 ,-0.49171],'fb':[72.74030 ,8508.01790 ,462.96637 ,8.09011],'fc':0.00648,'Ne':1}}, #Rw: 0.841 - 'N':{'ZSlater':3.811,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[6.6995,1.8343,1.9526,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [2,3,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.30996999, 10.3409, 1], [0.50752997, 5.9073, 1], [0.20963, 8.3825, 2], [0.02966, 2.758, 2], - [-0.07656, 1.803, 2], [0.07353, 1.4848, 2], [0.00149, 17.9932, 3]], - '2s': [[-0.06167, 9.9051, 1], [0.43689999, 5.7436, 1], [-0.07645, 8.3086, 2], [-0.37468001, 2.7616, 2], - [-0.52263999, 1.8227, 2], [-0.20704, 1.4197, 2], [-0.00046, 17.9816, 3]], - '2p': [[0.00643, 8.349, 2], [0.083, 3.8827, 2], [0.26010001, 2.5921, 2], [0.41826999, 1.6946, 2], - [0.30836001, 1.1912, 2]],}, - 'Sl core':{'fa':[0.77852 ,1.21985 ,0.00000 ,0.00000],'fb':[0.96196 ,0.23414 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.069 + 'N':{'Sl core':{'fa':[0.77852 ,1.21985 ,0.00000 ,0.00000],'fb':[0.96196 ,0.23414 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.069 + 'Sl val':{'fa':[0.75488 ,0.21655 ,0.63679 ,-0.61470],'fb':[4.24516 ,35.14457 ,13.94612 ,3.58910],'fc':0.00630,'Ne':5}, #Rw: 0.094 'Sl 2s':{'fa':[0.69620 ,-0.27060 ,0.55504 ,0.00000],'fb':[7.22650 ,3.31048 ,20.49667 ,0.00000],'fc':0.01835,'Ne':2}, #Rw: 0.441 'Sl 2p':{'fa':[-0.00793 ,0.50756 ,0.49862 ,0.00000],'fb':[0.52552 ,8.81179 ,27.40632 ,0.00000],'fc':0.00000,'Ne':3}, #Rw: 0.285 'He core':{'fa':[0.78650 ,1.21310 ,0.00000 ,0.00000],'fb':[0.94486 ,0.23212 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.036 @@ -339,35 +293,17 @@ ' 2p':{'fa':[-0.00463 ,0.54585 ,0.45753 ,0.00000],'fb':[0.30326 ,9.50870 ,31.02729 ,0.00000],'fc':0.00000,'Ne':3}, #Rw: 0.209 ' 2p':{'fa':[0.07810 ,0.20001 ,-0.20883 ,-0.07084],'fb':[1.21643 ,3.81092 ,23.92523 ,53.94780],'fc':0.00159,'Ne':1}, #Rw: 0.054 ' 2s,2p':{'fa':[-0.48583 ,0.32128 ,0.11040 ,0.04521],'fb':[5.48551 ,52.12781 ,349.24771 ,6847.87203],'fc':0.00832,'Ne':1}}, #Rw: 0.724 - 'O':{'ZSlater':4.497,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[7.6982,2.1303,2.3079,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [2,4,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.35854, 11.389, 1], [0.46329001, 6.5892, 1], [0.21278, 9.4576, 2], [-0.01355, 3.2487, 2], [0.02844, 2.1613, 2], - [0.0014, 1.6418, 2], [0.00083, 20.5052, 3]], - '2s': [[0.07478, 9.9028, 1], [0.19686, 5.872, 1], [0.0524, 8.2891, 2], [-0.51068997, 3.0304, 2], [-0.52007002, 1.9109, 2], - [-0.07553, 1.5629, 2], [-0.00389, 17.9767, 3]], - '2p': [[0.00583, 9.6471, 2], [0.1266, 4.3321, 2], [0.32925999, 2.7505, 2], [0.39488, 1.7525, 2], - [0.2321, 1.2465, 2]],}, - 'Sl core':{'fa':[0.68692 ,1.31215 ,0.00000 ,0.00000],'fb':[0.78075 ,0.19151 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.040 + 'O':{'Sl core':{'fa':[0.68692 ,1.31215 ,0.00000 ,0.00000],'fb':[0.78075 ,0.19151 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.040 + 'Sl val':{'fa':[-0.05805 ,0.16787 ,0.54779 ,0.33660],'fb':[1.62550 ,30.93539 ,12.23189 ,5.08896],'fc':0.00564,'Ne':6}, #Rw: 0.035 'Sl 2s':{'fa':[0.67997 ,-0.17420 ,0.47272 ,0.00000],'fb':[6.17545 ,2.00431 ,16.10236 ,0.00000],'fc':0.02075,'Ne':2}, #Rw: 0.221 - 'Sl 2p':{'fa':[-0.00632 ,0.50781 ,0.49626 ,0.00000],'fb':[0.27883 ,6.41456 ,21.48168 ,0.00000],'fc':0.00000,'Ne':4}, #Rw: 0.358 + 'Sl 2p':{'fa':[0.29692 ,0.19243 ,0.52071 ,-0.01127],'fb':[5.00547 ,32.64197 ,12.88336 ,0.39407],'fc':0.00105,'Ne':4}, #Rw: 0.000 'He core':{'fa':[0.70952 ,1.29027 ,0.00000 ,0.00000],'fb':[0.75508 ,0.18794 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.019 ' 2s':{'fa':[0.72731 ,-0.14156 ,0.39516 ,0.00000],'fb':[6.79666 ,1.80663 ,17.32356 ,0.00000],'fc':0.01877,'Ne':2}, #Rw: 0.090 ' 2p':{'fa':[-0.00408 ,0.55404 ,0.44868 ,0.00000],'fb':[0.12557 ,6.88485 ,23.35385 ,0.00000],'fc':0.00000,'Ne':4}, #Rw: 0.222 ' 2p':{'fa':[-0.07385 ,0.18904 ,-0.20106 ,0.08383],'fb':[40.51074 ,2.77536 ,17.35282 ,0.92076],'fc':0.00206,'Ne':1}, #Rw: 0.037 ' 2s,2p':{'fa':[-0.48170 ,0.31734 ,0.10882 ,0.04413],'fb':[3.95931 ,39.05538 ,261.86509 ,4921.30838],'fc':0.00992,'Ne':1}}, #Rw: 0.621 - 'F':{'ZSlater':5.186,'NSlater':[2,2,2,3,4,5,6,7],'SZE':[8.6988,2.4245,2.6604,], - 'popCore':[[2,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [2,5,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.37283, 12.6673, 1], [0.45184001, 7.4034, 1], [0.21072, 10.6749, 2], [-0.01414, 3.7546, 2], - [0.02326, 2.5009, 2], [0.00831, 1.8581, 2], [-0.00031, 23.2503, 3]], - '2s': [[-0.05201, 12.6074, 1], [0.42704999, 7.4115, 1], [-0.05938, 10.7414, 2], [-0.36032, 3.7555, 2], - [-0.51626998, 2.5011, 2], [-0.23784, 1.8587, 2], [-0.00046, 23.2475, 3]], - '2p': [[0.00495, 11.0134, 2], [0.13077, 4.9964, 2], [0.33814999, 3.1533, 2], - [0.39725, 1.9728, 2], [0.22358, 1.3651, 2]],}, - 'Sl core':{'fa':[0.60671 ,1.39256 ,0.00000 ,0.00000],'fb':[0.65297 ,0.16017 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.026 + 'F':{'Sl core':{'fa':[0.60671 ,1.39256 ,0.00000 ,0.00000],'fb':[0.65297 ,0.16017 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.026 + 'Sl val':{'fa':[0.50603 ,0.14439 ,-0.03620 ,0.38049],'fb':[10.32054 ,26.21202 ,0.98564 ,4.28790],'fc':0.00516,'Ne':7}, #Rw: 0.022 'Sl 2s':{'fa':[0.69775 ,-0.14319 ,0.42213 ,0.00000],'fb':[5.10620 ,1.35095 ,12.93102 ,0.00000],'fc':0.02273,'Ne':2}, #Rw: 0.125 'Sl 2p':{'fa':[0.43932 ,0.54161 ,0.01924 ,0.00000],'fb':[16.85347 ,5.25245 ,61.83086 ,0.00000],'fc':0.00000,'Ne':5}, #Rw: 0.957 'He core':{'fa':[0.65125 ,1.34863 ,0.00000 ,0.00000],'fb':[0.61716 ,0.15480 ,0.00000 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.010 @@ -375,56 +311,16 @@ ' 2p':{'fa':[0.03811 ,0.52299 ,0.43897 ,0.00000],'fb':[45.06899 ,5.17796 ,15.91403 ,0.00000],'fc':0.00000,'Ne':5}, #Rw: 0.321 ' 2p':{'fa':[-0.07581 ,0.18382 ,-0.19513 ,0.08491],'fb':[31.66771 ,2.08634 ,13.18751 ,0.70155],'fc':0.00223,'Ne':1}, #Rw: 0.039 ' 2s,2p':{'fa':[-0.47873 ,0.31301 ,0.10816 ,0.04402],'fb':[2.99490 ,30.21838 ,198.76427 ,3467.96796],'fc':0.01113,'Ne':1}}, #Rw: 0.569 - 'Na':{'ZSlater':1.735,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[10.7077,3.0704,3.5275,0.8676,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [1,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.43523201, 15.7894, 1], [0.29707, 9.0739, 1], [0.30252999, 12.7889, 2], [0.01449, 4.754, 2], - [0.000724, 3.1586, 2], [0.00011, 2.4186, 2], [0.006344, 28.3979, 3], [0.012505, 1.3318, 3], - [-0.02557, 0.8982, 3], [0.015654, 0.6792, 3]], - '2s': [[0.04523, 15.3319, 1], [-0.42601001, 9.0929, 1], [0.04761, 13.201, 2], [0.34977999, 4.7458, 2], - [0.6085, 3.1522, 2], [0.15554, 2.4053, 2], [0.00071, 28.4273, 3], [-0.00167, 1.3179, 3], - [0.00146, 0.8911, 3], [0.00115, 0.6679, 3]], - '3s': [[0.01133, 15.3319, 1], [-0.07232, 9.0902, 1], [0.01134, 13.2013, 2], [0.05821, 4.7444, 2], - [0.09008, 3.1517, 2], [0.04117, 2.4048, 2], [-6e-05, 28.4273, 3], [-0.18368, 1.3187, 3], - [-0.4711, 0.8918, 3], [-0.40836, 0.6683, 3]], - '2p': [[0.0047, 13.6175, 2], [0.15803, 6.219, 2], [0.38856, 3.8384, 2], [0.48881, 2.3631, 2], - [0.04003, 1.5319, 2]],}, - 'Sl core':{'fa':[1.26440 ,2.87097 ,5.12016 ,0.00000],'fb':[0.35897 ,9.88914 ,3.47631 ,0.00000],'fc':0.73850,'Ne':1}, #Rw: 0.083 + 'Na':{'Sl core':{'fa':[1.26440 ,2.87097 ,5.12016 ,0.00000],'fb':[0.35897 ,9.88914 ,3.47631 ,0.00000],'fc':0.73850,'Ne':1}, #Rw: 0.083 + 'Sl val':{'fa':[-1.60817 ,2.01651 ,0.58999 ,0.00000],'fb':[52.78862 ,63.32671 ,184.69862 ,0.00000],'fc':0.00161,'Ne':1}, #Rw: 1.520 'Sl 3s':{'fa':[-1.60817 ,2.01651 ,0.58999 ,0.00000],'fb':[52.78862 ,63.32671 ,184.69862 ,0.00000],'fc':0.00161,'Ne':1}, #Rw: 1.520 }, - 'Mg':{'ZSlater':2.219,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[11.7166,3.4315,4.0333,1.1096,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [2,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.38809901, 17.497, 1], [0.380683, 10.0348, 1], [0.26272401, 14.2599, 2], [0.008354, 5.1723, 2], [0.012883, 3.5041, 2], - [-0.010386, 2.5474, 2], [0.005664, 29.8346, 3], [0.015309, 1.7842, 3], [-0.024776, 1.1843, 3], [0.014185, 0.8725, 3]], - '2s': [[0.05062, 17.0234, 1], [-0.44349, 10.0752, 1], [0.04972, 14.6763, 2], [0.35957, 5.1542, 2], [0.69590998, 3.4864, 2], - [0.05842, 2.525, 2], [0.00075, 29.902, 3], [-0.0064, 1.7567, 3], [-0.00562, 1.1659, 3], [-0.00415, 0.8244, 3]], - '3s': [[0.01523, 17.0241, 1], [-0.09303, 10.0729, 1], [0.01555, 14.6751, 2], [0.07318, 5.152, 2], [0.10169, 3.487, 2], - [0.07946, 2.5245, 2], [-0.00037, 29.9018, 3], [-0.23284, 1.7558, 3], [-0.49467, 1.1678, 3], [-0.37818, 0.8243, 3]], - '2p': [[0.00469, 14.9022, 2], [0.17601, 6.8071, 2], [0.42002001, 4.1433, 2], [0.45569, 2.7149, 2], [0.01236, 1.4623, 2]]}, - 'Sl core':{'fa':[5.32477 ,2.58867 ,1.23297 ,0.00000],'fb':[2.81709 ,7.41361 ,0.37596 ,0.00000],'fc':0.85024,'Ne':1}, #Rw: 0.047 + 'Mg':{'Sl core':{'fa':[5.32477 ,2.58867 ,1.23297 ,0.00000],'fb':[2.81709 ,7.41361 ,0.37596 ,0.00000],'fc':0.85024,'Ne':1}, #Rw: 0.047 + 'Sl val':{'fa':[-1.83151 ,2.34437 ,0.48480 ,0.00000],'fb':[32.95627 ,39.68086 ,120.94497 ,0.00000],'fc':0.00242,'Ne':2}, #Rw: 1.813 'Sl 3s':{'fa':[-1.83151 ,2.34437 ,0.48480 ,0.00000],'fb':[32.95627 ,39.68086 ,120.94497 ,0.00000],'fc':0.00242,'Ne':2}, #Rw: 1.813 }, - 'Al':{'ZSlater':2.505,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[12.7293,3.7971,4.5365,1.3500,1.0575,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [2,1,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.38653001, 18.6021, 1], [0.412653, 10.8603, 1], [0.233808, 15.3696, 2], [-0.019903, 5.7821, 2], - [0.073742, 4.0277, 2], [-0.064121, 2.8823, 2], [0.004596, 33.5503, 3], [0.043541, 2.129, 3], - [-0.039615, 1.4178, 3], [0.017009, 1.0521, 3]], - '2s': [[0.0522, 18.1792, 1], [-0.45533001, 10.8862, 1], [0.0494, 15.759, 2], [0.30269, 5.7626, 2], - [0.74860001, 4.0086, 2], [0.06373, 2.8675, 2], [0.00068, 33.5797, 3], [-0.00499, 2.1106, 3], - [-0.00445, 1.3998, 3], [-0.00376, 1.0003, 3]], - '3s': [[0.01909, 18.1792, 1], [-0.118, 10.8836, 1], [0.01859, 15.7593, 2], [0.07727, 5.7602, 2], - [0.12561999, 4.0088, 2], [0.13441999, 2.8676, 2], [-0.0004, 33.5797, 3], [-0.30414999, 2.111, 3], - [-0.54753, 1.4008, 3], [-0.28481001, 1.0014, 3]], - '2p': [[0.0161, 14.4978, 2], [0.20685001, 6.6568, 2], [0.47284999, 4.2187, 2], [0.3396, 3.0018, 2], - [0.02346, 11.0819, 3], [0.00413, 1.6784, 3], [-0.00092, 1.0788, 3], [-0.00043, 0.7494, 3]], - '3p': [[-0.00198, 14.4976, 2], [-0.0495, 6.6568, 2], [-0.05834, 4.2183, 2], [-0.08908, 3.0028, 2], - [-0.00111, 11.0822, 3], [0.23559, 1.6791, 3], [0.49553999, 1.0792, 3], [0.35898, 0.7494, 3]],}, - 'Sl core':{'fa':[2.28764 ,5.50545 ,1.26107 ,0.00000],'fb':[5.86343 ,2.35655 ,0.40136 ,0.00000],'fc':0.94401,'Ne':1}, #Rw: 0.026 + 'Al':{'Sl core':{'fa':[2.28764 ,5.50545 ,1.26107 ,0.00000],'fb':[5.86343 ,2.35655 ,0.40136 ,0.00000],'fc':0.94401,'Ne':1}, #Rw: 0.026 + 'Sl val':{'fa':[0.79466 ,-0.20209 ,0.33945 ,0.06786],'fb':[44.59170 ,8.19068 ,113.06023 ,2.78603],'fc':-0.00026,'Ne':3}, #Rw: 0.099 'Sl 3s':{'fa':[0.86076 ,-0.27013 ,0.31189 ,0.09777],'fb':[39.04559 ,7.76123 ,83.50646 ,3.07070],'fc':-0.00039,'Ne':2}, #Rw: 0.129 'Sl 3p':{'fa':[0.70285 ,-0.13750 ,0.40931 ,0.02525],'fb':[64.56693 ,13.02907 ,143.82608 ,1.90383],'fc':-0.00013,'Ne':1}, #Rw: 0.052 'Ne core':{'fa':[2.84774 ,5.32035 ,1.82996 ,0.00000],'fb':[5.49992 ,2.09003 ,0.10872 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.034 @@ -432,21 +328,8 @@ ' 3p':{'fa':[0.72023 ,-0.13572 ,0.38876 ,0.02663],'fb':[65.86894 ,12.71510 ,146.55594 ,1.99217],'fc':-0.00011,'Ne':1}, #Rw: 0.039 ' 3p':{'fa':[0.38882 ,-0.03863 ,-0.37408 ,0.02340],'fb':[21.00005 ,1.96402 ,128.65676 ,1.09496],'fc':0.00120,'Ne':1}, #Rw: 0.380 ' 3s,3p':{'fa':[0.84762 ,-1.35399 ,0.38895 ,0.11561],'fb':[15.83232 ,20.14684 ,234.90180 ,4133.85923],'fc':-0.00312,'Ne':1}}, #Rw: 1.808 - 'Si':{'ZSlater':2.859,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[13.7460,4.1649,5.0367,1.5705,1.2884,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [2,2,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.39033699, 19.5088, 1], [0.43463799, 11.7585, 1], [0.207461, 16.9621, 2], [0.006778, 6.3693, 2], [0.003486, 4.5748, 2], - [0.002075, 3.3712, 2], [-0.003633, 36.5791, 3], [0.000844, 2.4997, 3], [-0.004697, 1.6627, 3], [0.001627, 1.1814, 3]], - '2s': [[0.05028, 19.5014, 1], [-0.45644999, 11.759, 1], [0.0444, 16.9662, 2], [0.23655, 6.3708, 2], [0.78151, 4.5748, 2], - [0.0944, 3.3716, 2], [0.00116, 36.5767, 3], [-0.00602, 2.4996, 3], [0.00209, 1.6627, 3], [0.00217, 1.1812, 3]], - '3s': [[0.02036, 19.5017, 1], [-0.13309, 11.7542, 1], [0.01764, 16.9664, 2], [0.0752, 6.3694, 2], [0.122, 4.575, 2], - [0.20300999, 3.372, 2], [0.00017, 36.5764, 3], [-0.32126999, 2.5013, 3], [-0.56151003, 1.6635, 3], [-0.27893999, 1.1827, 3]], - '2p': [[0.01597, 15.7334, 2], [0.20438001, 7.2926, 2], [0.50146002, 4.6494, 2], [0.30935001, 3.3945, 2], [0.02287, 12.0746, 3], - [-0.00032, 2.0349, 3], [0.00055, 1.3221, 3], [0.00362, 0.9143, 3]], - '3p': [[-0.00225, 15.7304, 2], [-0.05777, 7.2926, 2], [-0.06812, 4.6515, 2], [-0.11262, 3.3985, 2], [-0.0016, 12.0786, 3], - [0.2642, 2.0355, 3], [0.52223998, 1.3223, 3], [0.31432, 0.9141, 3]],}, - 'Sl core':{'fa':[1.95728 ,1.36269 ,5.65746 ,0.00000],'fb':[4.85246 ,0.43043 ,2.02828 ,0.00000],'fc':1.02149,'Ne':1}, #Rw: 0.015 + 'Si':{'Sl core':{'fa':[1.95728 ,1.36269 ,5.65746 ,0.00000],'fb':[4.85246 ,0.43043 ,2.02828 ,0.00000],'fc':1.02149,'Ne':1}, #Rw: 0.015 + 'Sl val':{'fa':[0.35466 ,0.79262 ,0.07347 ,-0.22068],'fb':[80.38419 ,33.18496 ,2.08191 ,6.62263],'fc':-0.00039,'Ne':4}, #Rw: 0.065 'Sl 3s':{'fa':[-0.33492 ,0.89461 ,0.29991 ,0.14111],'fb':[5.77388 ,28.11507 ,60.03004 ,2.53813],'fc':-0.00079,'Ne':2}, #Rw: 0.103 'Sl 3p':{'fa':[56.81649 ,-56.61599 ,0.77125 ,0.02732],'fb':[18.26060 ,18.16977 ,72.12858 ,1.18997],'fc':-0.00072,'Ne':2}, #Rw: 0.335 'Ne core':{'fa':[2.66113 ,5.48368 ,1.85416 ,0.00000],'fb':[4.43254 ,1.75474 ,0.09817 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.024 @@ -454,24 +337,8 @@ ' 3p':{'fa':[0.74460 ,-0.16018 ,0.03846 ,0.37703],'fb':[42.90408 ,8.56330 ,1.61029 ,98.66280],'fc':-0.00014,'Ne':2}, #Rw: 0.043 ' 3p':{'fa':[0.40315 ,-0.03893 ,-0.37135 ,0.01295],'fb':[13.21903 ,2.76916 ,84.84031 ,0.13601],'fc':-0.00494,'Ne':1}, #Rw: 0.481 ' 3s,3p':{'fa':[0.11926 ,-2.07633 ,0.39780 ,1.55565],'fb':[2485.18638 ,13.50091 ,156.38812 ,11.56651],'fc':-0.00464,'Ne':1}}, #Rw: 1.866 - 'P':{'ZSlater':3.232,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[14.7668,4.5345,5.5342,1.7805,1.5066,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [2,3,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.39703101, 20.8423, 1], [0.42565799, 12.639, 1], [0.20983499, 18.1642, 2], [0.007226, 6.9865, 2], - [0.00427, 5.1341, 2], [0.001311, 3.7312, 2], [-0.004278, 39.5463, 3], [0.001839, 2.956, 3], - [-0.005481, 1.9408, 3], [0.002028, 1.3618, 3]], - '2s': [[0.049123, 20.8259, 1], [-0.45943901, 12.6392, 1], [0.04109, 18.1768, 2], [0.173786, 6.9872, 2], - [0.827618, 5.1346, 2], [0.110068, 3.7317, 2], [0.001415, 39.5405, 3], [-0.006902, 2.9559, 3], - [0.006505, 1.9407, 3], [0.003038, 1.3616, 3]], - '3s': [[0.023562, 20.8264, 1], [-0.149158, 12.633, 1], [0.019848, 18.1768, 2], [0.068192, 6.9864, 2], - [0.122791, 5.1342, 2], [0.271193, 3.7324, 2], [0.000133, 39.5397, 3], [-0.32465801, 2.9576, 3], - [-0.58230901, 1.9417, 3], [-0.291556, 1.3637, 3]], - '2p': [[0.001376, 20.2976, 2], [0.164067, 9.3164, 2], [0.49320301, 5.5171, 2], [0.39368501, 3.9504, 2], - [-0.006241, 15.5639, 3], [-0.000611, 2.2826, 3], [0.002051, 1.4968, 3], [0.003802, 1.0527, 3]], - '3p': [[0.00072, 20.2974, 2], [-0.049247, 9.3161, 2], [-0.08754, 5.5198, 2], [-0.13596401, 3.9519, 2], - [0.004519, 15.5633, 3], [0.32620901, 2.2844, 3], [0.52188098, 1.4956, 3], [0.255577, 1.053, 3]],}, - 'Sl core':{'fa':[1.61960 ,5.75059 ,1.54657 ,0.00000],'fb':[4.15302 ,1.78495 ,0.45650 ,0.00000],'fc':1.08250,'Ne':1}, #Rw: 0.009 + 'P':{'Sl core':{'fa':[1.61960 ,5.75059 ,1.54657 ,0.00000],'fb':[4.15302 ,1.78495 ,0.45650 ,0.00000],'fc':1.08250,'Ne':1}, #Rw: 0.009 + 'Sl val':{'fa':[0.80464 ,0.35626 ,0.07878 ,-0.23949],'fb':[25.49952 ,60.05538 ,1.62887 ,5.43085],'fc':-0.00049,'Ne':5}, #Rw: 0.000 'Sl 3s':{'fa':[18.80354 ,-18.19638 ,0.37082 ,-5.90006],'fb':[13.92611 ,13.55485 ,45.28728 ,-0.00132],'fc':5.92246,'Ne':2}, #Rw: 1.520 'Sl 3p':{'fa':[0.38258 ,0.76606 ,-0.19542 ,0.04675],'fb':[66.39818 ,29.62086 ,6.68917 ,1.28395],'fc':-0.00021,'Ne':3}, #Rw: 0.046 'Ne core':{'fa':[2.43072 ,5.67960 ,1.88891 ,0.00000],'fb':[3.70122 ,1.50616 ,0.09080 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.017 @@ -479,21 +346,8 @@ ' 3p':{'fa':[0.76870 ,-0.17850 ,0.35944 ,0.05019],'fb':[31.04925 ,6.16448 ,73.01578 ,1.36166],'fc':-0.00009,'Ne':3}, #Rw: 0.048 ' 3p':{'fa':[0.43387 ,-0.03549 ,-0.35150 ,-0.05244],'fb':[9.60211 ,128.36177 ,54.78845 ,2.87021],'fc':0.00552,'Ne':1}, #Rw: 0.341 ' 3s,3p':{'fa':[0.12027 ,0.40253 ,1.62393 ,-2.15061],'fb':[1752.60261 ,115.08685 ,8.54248 ,10.00502],'fc':-0.00630,'Ne':1}}, #Rw: 1.872 - 'S':{'ZSlater':3.612,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[15.7918,4.9053,6.0303,1.9842,1.7172,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [2,4,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.38502601, 22.2994, 1], [0.44669801, 13.5725, 1], [0.200673, 19.4953, 2], [0.0032, 7.5145, 2], [0.008694, 5.7223, 2], - [-0.000435, 4.2264, 2], [-0.004745, 42.1813, 3], [0.000602, 3.3089, 3], [-0.002422, 2.1708, 3], [-0.000117, 1.5143, 3]], - '2s': [[0.05849, 22.2949, 1], [-0.48181, 13.5705, 1], [0.04962, 19.4966, 2], [0.12429, 7.5162, 2], [0.83920997, 5.7265, 2], - [0.14474, 4.226, 2], [0.00079, 42.1788, 3], [0.0018, 3.3089, 3], [0.00352, 2.1708, 3], [0.00088, 1.5141, 3]], - '3s': [[0.02636, 22.2949, 1], [-0.16230001, 13.5666, 1], [0.02167, 19.4969, 2], [0.0612, 7.5144, 2], [0.11117, 5.7224, 2], - [0.32532001, 4.2278, 2], [0.00012, 42.1787, 3], [-0.34187001, 3.3102, 3], [-0.60044003, 2.1731, 3], [-0.27131, 1.5136, 3]], - '2p': [[0.00021, 22.6414, 2], [0.14571001, 10.4198, 2], [0.49818999, 6.1152, 2], [0.40467, 4.4146, 2], [-0.00803, 17.3451, 3], - [0.00304, 2.6496, 3], [0.00046, 1.6975, 3], [0.00132, 1.1477, 3]], - '3p': [[0.00097, 22.6414, 2], [-0.04753, 10.4197, 2], [-0.09078, 6.116, 2], [-0.1585, 4.4159, 2], [0.00518, 17.3448, 3], - [0.34178001, 2.6506, 3], [0.51861, 1.6973, 3], [0.26256001, 1.1474, 3]],}, - 'Sl core':{'fa':[1.76432 ,1.36676 ,5.74412 ,0.00000],'fb':[0.46292 ,3.59234 ,1.57926 ,0.00000],'fc':1.12443,'Ne':1}, #Rw: 0.005 + 'S':{'Sl core':{'fa':[1.76432 ,1.36676 ,5.74412 ,0.00000],'fb':[0.46292 ,3.59234 ,1.57926 ,0.00000],'fc':1.12443,'Ne':1}, #Rw: 0.005 + 'Sl val':{'fa':[0.08706 ,0.81011 ,-0.24960 ,0.35255],'fb':[1.34530 ,20.31979 ,4.34135 ,49.72613],'fc':-0.00048,'Ne':6}, #Rw: 0.058 'Sl 3s':{'fa':[4.11356 ,-3.97162 ,0.76799 ,0.09167],'fb':[8.11527 ,7.34136 ,26.66481 ,1.26784],'fc':-0.00265,'Ne':2}, #Rw: 0.002 'Sl 3p':{'fa':[0.37978 ,0.76483 ,-0.20090 ,0.05610],'fb':[53.96553 ,22.87939 ,4.98628 ,1.10042],'fc':-0.00014,'Ne':4}, #Rw: 0.057 'Ne core':{'fa':[2.25142 ,5.82493 ,1.92308 ,0.00000],'fb':[3.13346 ,1.30411 ,0.08444 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.009 @@ -501,24 +355,8 @@ ' 3p':{'fa':[0.78079 ,-0.19489 ,0.35376 ,0.06012],'fb':[23.60737 ,4.74081 ,56.27456 ,1.15500],'fc':-0.00003,'Ne':4}, #Rw: 0.042 ' 3p':{'fa':[0.44652 ,-0.34219 ,-0.04904 ,-0.06203],'fb':[7.29518 ,40.38538 ,89.68068 ,2.26351],'fc':0.00673,'Ne':1}, #Rw: 0.248 ' 3s,3p':{'fa':[0.12160 ,0.40567 ,-2.36572 ,1.83473],'fb':[1297.59621 ,88.59864 ,7.74396 ,6.67364],'fc':-0.00798,'Ne':1}}, #Rw: 1.849 - 'Cl':{'ZSlater':3.994,'NSlater':[4,4,4,6,8,10,12,14],'SZE':[16.8214,5.2778,6.5262,2.1835,1.9223,], - 'popCore':[[2,], [2,6,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [2,5,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.254906, 24.0464, 1], [0.716353, 14.4229, 1], [0.079344, 20.653, 2], [-0.136391, 9.4767, 2], - [0.241669, 6.447, 2], [-0.181016, 4.7701, 2], [0.001773, 44.4212, 3], [0.075657, 3.5965, 3], - [-0.048379, 2.4038, 3], [0.017564, 1.7128, 3]], - '2s': [[0.06101, 23.7917, 1], [-0.48997, 14.4903, 1], [0.05014, 20.8324, 2], [0.02314, 9.4344, 2], - [0.87580001, 6.4166, 2], [0.21031, 4.7668, 2], [0.00094, 44.4485, 3], [-0.00104, 3.5704, 3], - [0.00581, 2.3743, 3], [0.00458, 1.661, 3]], - '3s': [[0.03038, 23.7918, 1], [-0.17687, 14.487, 1], [0.02471, 20.8327, 2], [0.02115, 9.4341, 2], - [0.15011001, 6.4164, 2], [0.33958, 4.77, 2], [6e-05, 44.4484, 3], [-0.35834, 3.574, 3], - [-0.59885001, 2.3744, 3], [-0.25099999, 1.6643, 3]], - '2p': [[-0.00064, 24.4396, 2], [0.13409001, 11.491, 2], [0.50809002, 6.6718, 2], [0.40522999, 4.855, 2], - [-0.00973, 18.9811, 3], [0.00375, 2.9926, 3], [0.00063, 1.897, 3], [0.00121, 1.2586, 3]], - '3p': [[0.00118, 24.4396, 2], [-0.0457, 11.4909, 2], [-0.09663, 6.6725, 2], [-0.17481001, 4.8563, 2], - [0.00551, 18.9808, 3], [0.35528001, 2.9938, 3], [0.52271003, 1.8966, 3], [0.25698, 1.2587, 3]],}, - 'Sl core':{'fa':[5.94138 ,2.07884 ,1.97886 ,0.00000],'fb':[1.14046 ,2.69789 ,0.08040 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.011 + 'Cl':{'Sl core':{'fa':[5.94138 ,2.07884 ,1.97886 ,0.00000],'fb':[1.14046 ,2.69789 ,0.08040 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.011 + 'Sl val':{'fa':[-0.26056 ,0.34803 ,0.09553 ,0.81702],'fb':[3.54261 ,41.01784 ,1.14067 ,16.57649],'fc':-0.00038,'Ne':7}, #Rw: 0.061 'Sl 3s':{'fa':[-0.05299 ,1.20253 ,-0.40075 ,0.24946],'fb':[17.46981 ,17.46969 ,2.49805 ,1.38538],'fc':-0.00353,'Ne':2}, #Rw: 0.826 'Sl 3p':{'fa':[7.45384 ,-7.56680 ,0.26853 ,0.84206],'fb':[2.07462 ,2.11040 ,49.50765 ,20.25130],'fc':0.00233,'Ne':5}, #Rw: 0.215 'Ne core':{'fa':[2.02052 ,6.00416 ,1.97496 ,0.00000],'fb':[2.72686 ,1.15147 ,0.08080 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.006 @@ -526,447 +364,150 @@ ' 3p':{'fa':[0.33651 ,-0.20872 ,0.79927 ,0.07251],'fb':[45.61323 ,3.67583 ,18.89159 ,1.02639],'fc':0.00021,'Ne':5}, #Rw: 0.039 ' 3p':{'fa':[0.45430 ,-0.34114 ,-0.05325 ,-0.06785],'fb':[5.77822 ,31.44511 ,70.81299 ,1.79338],'fc':0.00790,'Ne':1}, #Rw: 0.172 ' 3s,3p':{'fa':[0.12214 ,0.40823 ,-3.16237 ,2.62886],'fb':[1016.72145 ,70.86925 ,6.11711 ,5.47190],'fc':-0.00962,'Ne':1}}, #Rw: 1.801 - 'K':{'ZSlater':1.839,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[18.8959,6.0296,7.5184,2.6111,2.3985,0.0000,0.9196,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [1,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.17936802, 16.9531, 1], [-0.134941, 23.731, 2], [0.002236, 6.772, 2], [-0.070201, 20.4662, 3], - [-0.029141, 4.0089, 3], [-0.024908, 2.4125, 3], [0.014891, 8.4311, 4], [0.042287, 3.7938, 4], - [0.009044, 1.4319, 4], [-0.008453, 0.9466, 4], [0.003888, 0.6646, 4]], - '2s': [[-0.34784001, 16.9325, 1], [-0.01253, 23.6706, 2], [1.15868998, 6.7754, 2], [-0.02817, 20.5369, 3], - [0.02961, 4.0061, 3], [0.00094, 2.4111, 3], [-0.09316, 8.4225, 4], [-0.00425, 3.7899, 4], - [-0.00292, 1.4301, 4], [-0.00062, 0.945, 4], [-0.0003, 0.6607, 4]], - '3s': [[-0.11908, 16.9318, 1], [-0.00725, 23.6708, 2], [0.46799999, 6.7737, 2], [-0.01454, 20.5359, 3], - [-0.40634999, 4.008, 3], [-0.5643, 2.4145, 3], [0.03181, 8.4214, 4], [-0.18407001, 3.7924, 4], - [-0.00706, 1.4301, 4], [0.002, 0.945, 4], [-0.00072, 0.6607, 4]], - '4s': [[0.02179, 16.9317, 1], [0.00149, 23.6708, 2], [-0.08966, 6.772, 2], [0.00289, 20.5358, 3], - [0.09535, 4.0061, 3], [0.10454, 2.4117, 3], [-0.00876, 8.4209, 4], [0.04572, 3.79, 4], - [-0.17184, 1.4321, 4], [-0.51783001, 0.9468, 4], [-0.40160999, 0.6626, 4]], - '2p': [[0.00298, 26.2044, 2], [0.36813, 11.1014, 2], [0.38022, 5.609, 2], [0.00611, 2.4374, 2], - [0.30748001, 8.7156, 3], [0.00117, 1.9702, 3], [0.00072, 1.3184, 3]], - '3p': [[0.00096, 26.2044, 2], [0.08189, 11.1006, 2], [0.39710999, 5.6108, 2], [-0.76919001, 2.4383, 2], - [0.0204, 8.7159, 3], [-0.40380001, 1.9698, 3], [-0.03971, 1.3184, 3]],}, - 'Sl core':{'fa':[2.83761 ,-88.17777 ,7.49955 ,94.40063],'fb':[24.21431 ,8.67788 ,0.78574 ,8.84148],'fc':1.43711,'Ne':1}, #Rw: 0.048 + 'K':{'Sl core':{'fa':[2.83761 ,-88.17777 ,7.49955 ,94.40063],'fb':[24.21431 ,8.67788 ,0.78574 ,8.84148],'fc':1.43711,'Ne':1}, #Rw: 0.048 + 'Sl val':{'fa':[1.12879 ,-0.69544 ,0.57306 ,-0.00994],'fb':[201.83060 ,21.00274 ,17.06331 ,4.01658],'fc':0.00058,'Ne':1}, #Rw: 0.552 'Sl 4s':{'fa':[1.12879 ,-0.69544 ,0.57306 ,-0.00994],'fb':[201.83060 ,21.00274 ,17.06331 ,4.01658],'fc':0.00058,'Ne':1}, #Rw: 0.552 }, - 'Ca':{'ZSlater':2.233,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[19.9409,6.4071,8.0162,2.8429,2.6631,0.0000,1.1163,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.19518697, 17.7, 1], [-0.141675, 24.7442, 2], [0.002428, 7.2537, 2], [-0.085376, 21.1264, 3], - [0.041982, 3.6316, 3], [-0.023081, 2.6302, 3], [0.008867, 8.8079, 4], [-0.028595, 6.0353, 4], - [0.009198, 1.5903, 4], [-0.008943, 1.0691, 4], [0.00408, 0.7814, 4]], - '2s': [[-0.35642999, 17.6686, 1], [-0.01166, 24.5284, 2], [1.15061998, 7.2629, 2], [-0.02715, 21.3547, 3], - [-0.00036, 3.6185, 3], [0.00717, 2.6126, 3], [-0.08228, 8.7839, 4], [0.02766, 6.0116, 4], - [0.00585, 1.577, 4], [0.00221, 1.0566, 4], [0.00065, 0.7584, 4]], - '3s': [[-0.12801, 17.6671, 1], [-0.00739, 24.5295, 2], [0.48541999, 7.2537, 2], [-0.01479, 21.3494, 3], - [-0.63814998, 3.6234, 3], [-0.43235999, 2.6174, 3], [0.0347, 8.781, 4], [-0.06742, 6.0113, 4], - [-0.00937, 1.5768, 4], [-0.01093, 1.0565, 4], [-0.00724, 0.7584, 4]], - '4s': [[0.03384, 17.667, 1], [0.00203, 24.5295, 2], [-0.13018, 7.2526, 2], [0.00403, 21.3493, 3], - [0.19644, 3.6165, 3], [0.12079, 2.6152, 3], [-0.01257, 8.7808, 4], [0.0234, 6.011, 4], - [-0.33388999, 1.5823, 4], [-0.52197999, 1.0583, 4], [-0.25573999, 0.7602, 4]], - '2p': [[0.00326, 28.8924, 2], [0.35084999, 11.9109, 2], [0.40454999, 6.0967, 2], [0.01155, 2.5605, 2], - [0.29488999, 9.4445, 3], [-0.00077, 2.1783, 3], [0.00064, 1.3295, 3]], - '3p': [[-0.00098, 28.8923, 2], [-0.08128, 11.912, 2], [-0.43213001, 6.0981, 2], [0.87671, 2.5618, 2], - [-0.01941, 9.4438, 3], [0.31156, 2.1776, 3], [0.02016, 1.3296, 3]],}, - 'Sl core':{'fa':[9.04970 ,7.15959 ,1.69492 ,0.00000],'fb':[11.45715 ,0.71059 ,0.04104 ,0.00000],'fc':0.04790,'Ne':1}, #Rw: 0.253 + 'Ca':{'Sl core':{'fa':[9.04970 ,7.15959 ,1.69492 ,0.00000],'fb':[11.45715 ,0.71059 ,0.04104 ,0.00000],'fc':0.04790,'Ne':1}, #Rw: 0.253 + 'Sl val':{'fa':[2.75746 ,-3.01842 ,0.52082 ,0.73912],'fb':[17.33451 ,18.57194 ,174.73278 ,89.01157],'fc':0.00074,'Ne':2}, #Rw: 0.655 'Sl 4s':{'fa':[2.75746 ,-3.01842 ,0.52082 ,0.73912],'fb':[17.33451 ,18.57194 ,174.73278 ,89.01157],'fc':0.00074,'Ne':2}, #Rw: 0.655 }, - 'Sc':{'ZSlater':3.228,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[20.9923,6.7873,8.5177,3.0500,2.8851,2.4230,1.2092,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,1,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.95034498, 21.7439, 1], [-0.008683, 18.5935, 2], [0.026966, 8.5329, 2], [0.061286, 26.6654, 3], - [0.016874, 6.3635, 3], [-0.012884, 4.4092, 3], [0.004266, 2.9847, 3], [-0.017881, 10.0578, 4], - [-0.003566, 1.7343, 4], [0.004541, 1.1552, 4], [-0.002268, 0.8093, 4]], - '2s': [[-0.28558001, 21.7245, 1], [-0.14284, 18.5962, 2], [1.00378001, 8.5347, 2], [-0.00892, 26.6637, 3], - [0.08139, 6.3628, 3], [-0.00178, 4.4087, 3], [0.01333, 2.9836, 3], [0.06112, 10.0559, 4], - [0.00298, 1.7339, 4], [0.00117, 1.1548, 4], [0.00018, 0.8074, 4]], - '3s': [[-0.10432, 21.7237, 1], [-0.0606, 18.5963, 2], [0.42749, 8.5332, 2], [-0.00378, 26.6637, 3], - [0.1031, 6.363, 3], [-0.56377, 4.4138, 3], [-0.62858999, 2.9894, 3], [0.08111, 10.0582, 4], - [-0.01316, 1.7335, 4], [-0.01126, 1.1547, 4], [-0.00947, 0.8074, 4]], - '4s': [[0.02777, 21.7237, 1], [0.01636, 18.5965, 2], [-0.11537, 8.5331, 2], [0.001, 26.6635, 3], - [-0.03133, 6.3631, 3], [0.17967001, 4.4061, 3], [0.18049, 2.9864, 3], [-0.02432, 10.0558, 4], - [-0.30811, 1.7403, 4], [-0.52433002, 1.1567, 4], [-0.28417, 0.8108, 4]], - '2p': [[0.00328, 31.2143, 2], [0.3427, 12.6343, 2], [0.42750999, 6.5444, 2], [0.00649, 2.7946, 2], - [0.27948999, 10.1172, 3], [-0.0001, 2.3662, 3], [0.01096, 1.454, 3]], - '3p': [[-0.00097, 31.2091, 2], [-0.0799, 12.6891, 2], [-0.45473999, 6.5487, 2], [0.88863999, 2.7954, 2], - [-0.01957, 10.0779, 3], [0.31038001, 2.367, 3], [0.02106, 1.4539, 3]], - '3d': [[0.01532, 9.8555, 3], [0.15785, 4.8478, 3], [0.35648999, 2.9219, 3], [0.43551999, 1.7366, 3], - [0.20553, 1.0538, 3]],}, - 'Sl core':{'fa':[1.89966 ,9.08043 ,6.97400 ,0.00000],'fb':[0.05406 ,9.76559 ,0.63421 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.247 + 'Sc':{'Sl core':{'fa':[1.89966 ,9.08043 ,6.97400 ,0.00000],'fb':[0.05406 ,9.76559 ,0.63421 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.247 + 'Sl val':{'fa':[0.15612 ,0.85379 ,-0.02456 ,0.01615],'fb':[7.89880 ,104.40755 ,1.05850 ,0.21525],'fc':-0.00659,'Ne':3}, #Rw: 0.762 'Sl 3d':{'fa':[0.54899 ,-0.01826 ,0.46666 ,0.00000],'fb':[12.34528 ,0.98687 ,41.44711 ,0.00000],'fc':0.00055,'Ne':1}, #Rw: 0.328 'Sl 4s':{'fa':[0.26486 ,-6.79062 ,0.93554 ,6.58798],'fb':[12.60926 ,30.11118 ,127.69467 ,31.96276],'fc':0.00088,'Ne':2}, #Rw: 0.704 'Ca core':{'fa':[2.45935 ,8.79533 ,7.54402 ,0.05080],'fb':[106.26830 ,9.08492 ,0.56866 ,-0.28527],'fc':1.13537,'Ne':1}, #Rw: 0.082 ' 3d':{'fa':[0.51152 ,0.35909 ,-0.02576 ,0.15513],'fb':[25.52486 ,9.81972 ,1.38703 ,65.26506],'fc':-0.00008,'Ne':1}, #Rw: 0.019 ' 3d':{'fa':[0.19852 ,-0.20593 ,0.08130 ,-0.07408],'fb':[4.76490 ,30.51707 ,1.77828 ,73.78079],'fc':0.00023,'Ne':1}, #Rw: 0.047 ' 3d':{'fa':[0.02076 ,-0.15191 ,0.11695 ,0.02091],'fb':[88.93985 ,13.21564 ,1.51597 ,0.18920],'fc':-0.00669,'Ne':1}}, #Rw: 0.194 - 'Ti':{'ZSlater':4.034,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[22.0496,7.1688,9.0202,3.2536,3.0978,2.7474,1.2862,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,2,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.95518899, 22.7527, 1], [-0.019842, 19.4571, 2], [0.029704, 8.9857, 2], [0.065529, 27.9379, 3], [0.018407, 6.6285, 3], - [-0.014859, 4.6768, 3], [0.005129, 3.1633, 3], [-0.019766, 10.678, 4], [-0.003792, 1.855, 4], [0.004438, 1.2217, 4], - [-0.002154, 0.841, 4]], - '2s': [[-0.28946, 22.7276, 1], [-0.14304, 19.4661, 2], [1.01271999, 8.9917, 2], [-0.00967, 27.9382, 3], [0.07743, 6.6277, 3], - [-0.00425, 4.6765, 3], [0.01518, 3.162, 3], [0.05917, 10.6743, 4], [0.00275, 1.8546, 4], [0.00063, 1.221, 4], [-0.0001, 0.8395, 4]], - '3s': [[-0.10744, 22.7316, 1], [-0.06247, 19.4594, 2], [0.44158, 8.9864, 2], [-0.00403, 27.9365, 3], [0.09871, 6.628, 3], - [-0.58841002, 4.6823, 3], [-0.61128998, 3.1675, 3], [0.08517, 10.6779, 4], [-0.01131, 1.8543, 4], - [-0.01072, 1.2209, 4], [-0.0095, 0.8395, 4]], - '4s': [[0.02788, 22.7314, 1], [0.0164, 19.4597, 2], [-0.11615, 8.9861, 2], [0.00111, 27.9364, 3], [-0.03135, 6.6281, 3], - [0.18536, 4.6737, 3], [0.16818, 3.1658, 3], [-0.02422, 10.6755, 4], [-0.29752001, 1.8615, 4], [-0.52960002, 1.2237, 4], - [-0.29255, 0.8429, 4]], - '2p': [[0.00331, 33.3948, 2], [0.33837, 13.3585, 2], [0.43380001, 6.9835, 2], [0.00715, 3.0697, 2], [0.27535, 10.7611, 3], - [-0.00065, 2.5804, 3], [0.0123, 1.6117, 3]], '3p': [[0.00102, 33.3883, 2], [0.07885, 13.4242, 2], [0.47220999, 6.9883, 2], - [-0.86669999, 3.0712, 2], [0.01983, 10.7126, 3], [-0.34277999, 2.5799, 3], [-0.02422, 1.6117, 3]], - '3d': [[0.01622, 10.476, 3], [0.17105, 5.2357, 3], [0.37316999, 3.1862, 3], [0.42438999, 1.9271, 3], [0.17606001, 1.1916, 3]],}, - 'Sl core':{'fa':[9.07177 ,6.20369 ,2.66516 ,0.00000],'fb':[8.51406 ,0.61262 ,0.10885 ,0.00000],'fc':0.01554,'Ne':1}, #Rw: 0.242 + 'Ti':{'Sl core':{'fa':[9.07177 ,6.20369 ,2.66516 ,0.00000],'fb':[8.51406 ,0.61262 ,0.10885 ,0.00000],'fc':0.01554,'Ne':1}, #Rw: 0.242 + 'Sl val':{'fa':[0.32523 ,0.42429 ,-0.02265 ,0.27191],'fb':[47.59754 ,121.47652 ,1.48503 ,7.76561],'fc':0.00061,'Ne':4}, #Rw: 0.150 'Sl 3d':{'fa':[0.56995 ,-0.02165 ,0.44909 ,0.00000],'fb':[9.77549 ,0.83700 ,31.50551 ,0.00000],'fc':0.00083,'Ne':2}, #Rw: 0.278 'Sl 4s':{'fa':[-66.78473 ,0.53201 ,0.74421 ,66.50666],'fb':[14.08368 ,57.00356 ,127.06636 ,14.04100],'fc':0.00087,'Ne':2}, #Rw: 0.010 'Ca core':{'fa':[1.35843 ,1.22596 ,8.73868 ,7.39226],'fb':[132.76497 ,59.64265 ,7.75733 ,0.50765],'fc':1.28327,'Ne':1}, #Rw: 0.035 ' 3d':{'fa':[0.41273 ,0.60289 ,-0.01689 ,0.00000],'fb':[34.46134 ,10.33460 ,0.79051 ,0.00000],'fc':0.00000,'Ne':2}, #Rw: 0.200 ' 3d':{'fa':[0.20026 ,-0.20806 ,0.08250 ,-0.07492],'fb':[3.75108 ,23.54754 ,1.45725 ,58.26529],'fc':0.00022,'Ne':1}, #Rw: 0.042 ' 3d':{'fa':[0.02007 ,-0.15336 ,0.12160 ,0.07181],'fb':[71.03861 ,10.15785 ,1.19155 ,0.02665],'fc':-0.06010,'Ne':1}}, #Rw: 0.209 - 'Ti+3':{'ZSlater':4.034,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[22.0496,7.1688,9.0202,3.2536,3.0978,2.7474,1.2862,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,2,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[0.51072 ,8.84924 ,7.34114 ,0.03628],'fb':[20.38024 ,7.65941 ,0.50147 ,0.50297],'fc':1.26386,'Ne':1}, #Rw: 0.040 + 'Ti+3':{'Ar core':{'fa':[0.51072 ,8.84924 ,7.34114 ,0.03628],'fb':[20.38024 ,7.65941 ,0.50147 ,0.50297],'fc':1.26386,'Ne':1}, #Rw: 0.040 ' 3d':{'fa':[0.42906 ,0.60388 ,-0.03327 ,0.00000],'fb':[20.87218 ,8.33300 ,1.04768 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.057 ' 3d':{'fa':[0.27533 ,-0.31389 ,0.03915 ,0.00000],'fb':[2.72653 ,19.90943 ,0.99383 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.388 ' 3d':{'fa':[0.05073 ,-0.20761 ,0.15149 ,0.00000],'fb':[28.43729 ,7.75608 ,1.01113 ,0.00000],'fc':0.00534,'Ne':1}}, #Rw: 0.335 - 'V':{'ZSlater':4.730,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[23.1135,7.5519,9.5235,3.4524,3.3058,3.0377,1.3558,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,3,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.96315098, 23.7433, 1], [-0.027945, 20.2895, 2], [-0.017305, 9.4805, 2], [0.08461, 29.1545, 3], [0.055029, 6.9473, 3], - [-0.044495, 4.9135, 3], [0.018183, 3.3275, 3], [-0.024348, 11.2894, 4], [-0.007297, 1.9557, 4], [0.006736, 1.2739, 4], - [-0.003038, 0.8713, 4]], - '2s': [[-0.29313001, 23.7192, 1], [-0.14402001, 20.3028, 2], [1.01490998, 9.4886, 2], [-0.01032, 29.1533, 3], [0.07628, 6.9461, 3], - [-0.00438, 4.9128, 3], [0.01752, 3.3256, 3], [0.06139, 11.2822, 4], [0.00175, 1.9549, 4], [-0.0002, 1.2721, 4], [-0.00043, 0.8656, 4]], - '3s': [[-0.1109, 23.7267, 1], [-0.06481, 20.2913, 2], [0.45387, 9.4805, 2], [-0.0043, 29.15, 3], [0.08672, 6.9464, 3], - [-0.60966998, 4.9187, 3], [-0.58894998, 3.3311, 3], [0.09147, 11.2871, 4], [-0.00878, 1.9547, 4], [-0.0097, 1.272, 4], - [-0.00861, 0.8656, 4]], - '4s': [[0.02768, 23.7265, 1], [0.01632, 20.2915, 2], [-0.11485, 9.4802, 2], [0.00121, 29.1499, 3], [-0.02907, 6.9464, 3], - [0.18904001, 4.9097, 3], [0.15244, 3.3303, 3], [-0.02447, 11.2845, 4], [-0.29719001, 1.9633, 4], [-0.53342998, 1.2755, 4], - [-0.29117, 0.869, 4]], - '2p': [[0.00327, 35.8157, 2], [0.3321, 14.1153, 2], [0.44187, 7.4343, 2], [0.00882, 3.2973, 2], [0.27122, 11.4237, 3], - [-0.00136, 2.7501, 3], [0.0122, 1.7235, 3]], - '3p': [[0.00106, 35.8087, 2], [0.07732, 14.1824, 2], [0.48385, 7.4387, 2], [-0.86928999, 3.2973, 2], [0.02055, 11.3724, 3], - [-0.34542999, 2.7535, 3], [-0.02606, 1.723, 3]], - '3d': [[0.01767, 10.9645, 3], [0.19656, 5.5047, 3], [0.40217, 3.3128, 3], [0.39969999, 2.0071, 3], [0.14267001, 1.2616, 3]],}, - 'Sl core':{'fa':[9.01460 ,4.27099 ,4.67369 ,0.00000],'fb':[7.54649 ,0.69766 ,0.18507 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.233 + 'V':{'Sl core':{'fa':[9.01460 ,4.27099 ,4.67369 ,0.00000],'fb':[7.54649 ,0.69766 ,0.18507 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.233 + 'Sl val':{'fa':[0.39534 ,0.28148 ,0.34305 ,-0.02140],'fb':[104.81406 ,32.37307 ,7.15538 ,1.04877],'fc':0.00066,'Ne':5}, #Rw: 0.171 'Sl 3d':{'fa':[0.58212 ,-0.02321 ,0.43785 ,0.00000],'fb':[8.14828 ,0.66234 ,26.14320 ,0.00000],'fc':0.00153,'Ne':3}, #Rw: 0.266 'Sl 4s':{'fa':[-5.20457 ,4.98825 ,0.46617 ,0.74908],'fb':[12.16472 ,11.76781 ,137.67470 ,67.16658],'fc':0.00082,'Ne':2}, #Rw: 0.586 'Ca core':{'fa':[2.39935 ,8.86683 ,7.32052 ,-0.07396],'fb':[89.41608 ,7.01429 ,0.46667 ,-0.14199],'fc':1.47040,'Ne':1}, #Rw: 0.087 ' 3d':{'fa':[0.39989 ,0.61694 ,-0.01802 ,0.00000],'fb':[28.98845 ,8.62988 ,0.67701 ,0.00000],'fc':0.00000,'Ne':3}, #Rw: 0.194 ' 3d':{'fa':[0.19877 ,-0.20649 ,0.08529 ,-0.07775],'fb':[3.09840 ,19.03090 ,1.24317 ,47.75912],'fc':0.00025,'Ne':1}, #Rw: 0.046 ' 3d':{'fa':[0.02002 ,-0.15223 ,0.12719 ,0.00000],'fb':[58.21967 ,8.39173 ,0.91458 ,0.00000],'fc':0.00502,'Ne':1}}, #Rw: 0.264 - 'V+2':{'ZSlater':4.730,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[23.1135,7.5519,9.5235,3.4524,3.3058,3.0377,1.3558,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,3,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[0.89192 ,8.45151 ,0.00000 ,7.41699],'fb':[16.54553 ,6.80163 ,0.00000 ,0.45033],'fc':1.24040,'Ne':1}, #Rw: 0.030 + 'V+2':{'Ar core':{'fa':[0.89192 ,8.45151 ,0.00000 ,7.41699],'fb':[16.54553 ,6.80163 ,0.00000 ,0.45033],'fc':1.24040,'Ne':1}, #Rw: 0.030 ' 3d':{'fa':[0.43364 ,0.58796 ,-0.02230 ,0.00000],'fb':[24.23318 ,8.08025 ,0.77104 ,0.00000],'fc':0.00000,'Ne':3}, #Rw: 0.122 ' 3d':{'fa':[0.20443 ,-0.21449 ,0.09510 ,-0.08536],'fb':[3.16448 ,17.65824 ,1.27345 ,37.09749],'fc':0.00035,'Ne':1}, #Rw: 0.035 ' 3d':{'fa':[0.03032 ,-0.16864 ,0.13302 ,0.00000],'fb':[41.52763 ,8.16513 ,0.91258 ,0.00000],'fc':0.00529,'Ne':1}}, #Rw: 0.246 - 'V+3':{'ZSlater':4.730,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[23.1135,7.5519,9.5235,3.4524,3.3058,3.0377,1.3558,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,3,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[0.55851 ,8.82793 ,0.00000 ,7.40178],'fb':[17.70386 ,6.73731 ,0.00000 ,0.44328],'fc':1.21271,'Ne':1}, #Rw: 0.037 + 'V+3':{'Ar core':{'fa':[0.55851 ,8.82793 ,0.00000 ,7.40178],'fb':[17.70386 ,6.73731 ,0.00000 ,0.44328],'fc':1.21271,'Ne':1}, #Rw: 0.037 ' 3d':{'fa':[0.42595 ,0.60640 ,-0.03271 ,0.00000],'fb':[18.48530 ,7.13795 ,0.88356 ,0.00000],'fc':0.00000,'Ne':2}, #Rw: 0.064 ' 3d':{'fa':[0.23472 ,-0.24697 ,0.10010 ,-0.08808],'fb':[2.90274 ,13.69761 ,1.20107 ,26.36991],'fc':0.00023,'Ne':1}, #Rw: 0.025 ' 3d':{'fa':[0.04419 ,-0.19991 ,0.14982 ,0.00000],'fb':[26.59181 ,6.57160 ,0.87243 ,0.00000],'fc':0.00587,'Ne':1}}, #Rw: 0.223 - 'Cr':{'ZSlater':5.632,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[24.1844,7.9369,10.0291,3.6368,3.4908,3.1092,1.3501,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [1,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.96753299, 24.7482, 1], [-0.042229, 21.1347, 2], [0.007756, 9.9967, 2], [0.083244, 30.427, 3], [0.056263, 7.3637, 3], - [-0.040254, 5.1841, 3], [0.015363, 3.4409, 3], [-0.039941, 11.9585, 4], [-0.005363, 2.1136, 4], [0.004203, 1.3255, 4], - [-0.001909, 0.8731, 4]], - '2s': [[0.29922, 24.59, 1], [0.13617, 21.3308, 2], [-0.99230999, 10.1049, 2], [0.01452, 30.485, 3], [-0.07987, 7.363, 3], - [0.02639, 5.1832, 3], [-0.04366, 3.439, 3], [-0.0911, 11.9118, 4], [0.02306, 2.113, 4], [0.00819, 1.3244, 4], - [-0.00111, 0.8703, 4]], - '3s': [[-0.11206, 24.7293, 1], [-0.06579, 21.1384, 2], [0.45482001, 9.9975, 2], [-0.00444, 30.4213, 3], [0.08933, 7.3631, 3], - [-0.63301003, 5.1878, 3], [-0.57508999, 3.4446, 3], [0.09247, 11.9567, 4], [-0.0043, 2.1124, 4], [-0.00193, 1.3241, 4], - [-0.00373, 0.8703, 4]], - '4s': [[-0.02271, 24.7292, 1], [-0.01382, 21.1386, 2], [0.09567, 9.9971, 2], [-0.00094, 30.4212, 3], [0.03006, 7.363, 3], - [-0.17767, 5.1825, 3], [-0.12294, 3.4414, 3], [0.02049, 11.9538, 4], [0.23587, 2.1153, 4], [0.53798002, 1.3323, 4], - [0.35141, 0.8793, 4]], - '2p': [[0.0034, 37.428, 2], [0.33711001, 14.7905, 2], [0.42319, 7.8393, 2], [0.01442, 3.6201, 2], [0.28163999, 11.9899, 3], - [-0.00512, 2.9492, 3], [0.01114, 1.9545, 3]], - '3p': [[0.00115, 37.4231, 2], [0.07773, 14.8399, 2], [0.48976001, 7.8421, 2], [-0.82529998, 3.6169, 2], [0.02288, 11.9478, 3], - [-0.38374999, 2.9574, 3], [-0.05018, 1.9524, 3]], - '3d': [[0.01704, 11.4817, 3], [0.19578999, 5.8142, 3], [0.40029001, 3.4544, 3], [0.39452001, 1.973, 3], [0.18735, 1.1431, 3]],}, - 'Sl core':{'fa':[8.71797 ,2.13371 ,7.11171 ,0.00000],'fb':[7.01360 ,1.18522 ,0.24411 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.232 + 'Cr':{'Sl core':{'fa':[8.71797 ,2.13371 ,7.11171 ,0.00000],'fb':[7.01360 ,1.18522 ,0.24411 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.232 + 'Sl val':{'fa':[0.24028 ,0.39392 ,0.38462 ,-0.02060],'fb':[91.76859 ,22.20348 ,6.52497 ,0.71961],'fc':0.00086,'Ne':6}, #Rw: 0.149 'Sl 3d':{'fa':[-0.02676 ,0.48805 ,0.17754 ,0.36070],'fb':[0.83620 ,16.32743 ,47.74919 ,5.90154],'fc':0.00015,'Ne':5}, #Rw: 0.051 'Sl 4s':{'fa':[0.77744 ,1.34167 ,0.39073 ,-1.51043],'fb':[73.27208 ,10.28027 ,150.06087 ,11.27736],'fc':0.00042,'Ne':1}, #Rw: 0.006 'Ca core':{'fa':[1.01413 ,0.72512 ,7.44630 ,8.60496],'fb':[118.63004 ,20.96598 ,0.40378 ,6.12395],'fc':1.20679,'Ne':1}, #Rw: 0.025 ' 3d':{'fa':[0.40184 ,0.60727 ,-0.01134 ,0.00000],'fb':[33.60621 ,8.25323 ,0.42386 ,0.00000],'fc':0.00000,'Ne':5}, #Rw: 0.335 ' 3d':{'fa':[0.22280 ,-0.17614 ,0.02855 ,-0.07066],'fb':[2.26998 ,21.79534 ,0.43307 ,62.72573],'fc':-0.00446,'Ne':1}, #Rw: 0.199 ' 3d':{'fa':[0.01650 ,-0.10113 ,0.11525 ,-0.03589],'fb':[73.81915 ,7.27337 ,0.82702 ,17.10239],'fc':0.00523,'Ne':1}}, #Rw: 0.185 - 'Cr+2':{'ZSlater':5.632,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[24.1844,7.9369,10.0291,3.6368,3.4908,3.1092,1.3501,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [1,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[1.00854 ,6.96540 ,7.44282 ,1.40530],'fb':[14.16747 ,5.98217 ,0.39882 ,5.97643],'fc':1.17873,'Ne':1}, #Rw: 0.026 + 'Cr+2':{'Ar core':{'fa':[1.00854 ,6.96540 ,7.44282 ,1.40530],'fb':[14.16747 ,5.98217 ,0.39882 ,5.97643],'fc':1.17873,'Ne':1}, #Rw: 0.026 ' 3d':{'fa':[0.42540 ,0.59665 ,-0.02279 ,0.00000],'fb':[21.30733 ,6.93503 ,0.66249 ,0.00000],'fc':0.00000,'Ne':4}, #Rw: 0.129 ' 3d':{'fa':[0.24914 ,-0.27085 ,0.02278 ,0.00000],'fb':[1.98204 ,20.62100 ,0.61997 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.713 ' 3d':{'fa':[0.02733 ,-0.16541 ,0.13253 ,0.00000],'fb':[38.15173 ,6.88374 ,0.78384 ,0.00000],'fc':0.00557,'Ne':1}}, #Rw: 0.183 - 'Cr+3':{'ZSlater':5.632,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[24.1844,7.9369,10.0291,3.6368,3.4908,3.1092,1.3501,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [1,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[0.75928 ,7.16493 ,7.43312 ,1.49805],'fb':[14.23001 ,5.91416 ,0.39221 ,5.91445],'fc':1.14558,'Ne':1}, #Rw: 0.026 + 'Cr+3':{'Ar core':{'fa':[0.75928 ,7.16493 ,7.43312 ,1.49805],'fb':[14.23001 ,5.91416 ,0.39221 ,5.91445],'fc':1.14558,'Ne':1}, #Rw: 0.026 ' 3d':{'fa':[0.42506 ,0.60713 ,-0.03261 ,0.00000],'fb':[16.50850 ,6.18105 ,0.76102 ,0.00000],'fc':0.00000,'Ne':3}, #Rw: 0.070 ' 3d':{'fa':[0.27289 ,-0.29952 ,0.02732 ,0.00000],'fb':[1.90452 ,15.77484 ,0.64252 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.482 ' 3d':{'fa':[0.03979 ,-0.19399 ,0.14799 ,0.00000],'fb':[24.84471 ,5.68654 ,0.75863 ,0.00000],'fc':0.00621,'Ne':1}}, #Rw: 0.122 - 'Mn':{'ZSlater':5.949,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[25.2616,8.3216,10.5320,3.8440,3.7128,3.5711,1.4831,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.97280103, 25.7218, 1], [-0.062024, 21.8538, 2], [0.045828, 10.7523, 2], [0.079513, 31.6143, 3], [0.033882, 7.9537, 3], - [-0.019707, 5.4802, 3], [0.010584, 3.6821, 3], [-0.048232, 12.6299, 4], [-0.005387, 2.1708, 4], [0.005113, 1.3891, 4], - [-0.002376, 0.9216, 4]], - '2s': [[-0.30068001, 25.6968, 1], [-0.15147001, 21.8617, 2], [0.98559999, 10.7574, 2], [-0.012, 31.6116, 3], [0.09349, 7.9532, 3], - [-0.00417, 5.4792, 3], [0.01691, 3.6808, 3], [0.09222, 12.6256, 4], [0.00305, 2.1701, 4], [0.00046, 1.3884, 4], [-0.00015, 0.9204, 4]], - '3s': [[0.11457, 25.6998, 1], [0.07602, 21.8569, 2], [-0.45254001, 10.7527, 2], [0.00166, 31.6095, 3], [-0.10186, 7.9541, 3], - [0.62573999, 5.4845, 3], [0.58192998, 3.69, 3], [-0.10071, 12.6284, 4], [0.00722, 2.1701, 4], [0.00314, 1.3884, 4], - [0.00296, 0.9204, 4]], - '4s': [[0.02585, 25.6998, 1], [0.01633, 21.8567, 2], [-0.1022, 10.7524, 2], [0.00027, 31.6095, 3], [-0.02845, 7.9534, 3], - [0.17914, 5.4786, 3], [0.13383, 3.6836, 3], [-0.02811, 12.6273, 4], [-0.28174999, 2.1754, 4], [-0.53320998, 1.3931, 4], - [-0.31099001, 0.9265, 4]], - '2p': [[0.00355, 39.7731, 2], [0.33284, 15.4987, 2], [0.43577, 8.2704, 2], [0.01281, 3.9147, 2], [0.27250001, 12.6539, 3], - [-0.00666, 3.2245, 3], [0.01684, 2.106, 3]], - '3p': [[0.00123, 39.7637, 2], [0.07818, 15.5773, 2], [0.50133002, 8.2742, 2], [-0.80340999, 3.91, 2], [0.02583, 12.5894, 3], - [-0.41608, 3.2363, 3], [-0.04652, 2.1033, 3]], - '3d': [[0.01835, 12.1111, 3], [0.21244, 6.2275, 3], [0.41553, 3.7654, 3], [0.38543001, 2.2764, 3], [0.12575001, 1.4253, 3]],}, - 'Sl core':{'fa':[8.24920 ,1.89250 ,7.83416 ,0.00000],'fb':[6.39367 ,1.88181 ,0.24647 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.184 + 'Mn':{'Sl core':{'fa':[8.24920 ,1.89250 ,7.83416 ,0.00000],'fb':[6.39367 ,1.88181 ,0.24647 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.184 + 'Sl val':{'fa':[0.30840 ,0.29273 ,0.41830 ,-0.02218],'fb':[86.67584 ,20.24538 ,5.62675 ,0.59151],'fc':0.00170,'Ne':7}, #Rw: 0.172 'Sl 3d':{'fa':[0.59971 ,-0.02730 ,0.42002 ,0.00000],'fb':[6.06123 ,0.34679 ,19.48500 ,0.00000],'fc':0.00595,'Ne':5}, #Rw: 0.250 'Sl 4s':{'fa':[-4.00545 ,0.76842 ,0.41708 ,3.81910],'fb':[9.74978 ,60.25650 ,126.12450 ,9.39562],'fc':0.00064,'Ne':2}, #Rw: 0.489 'Ca core':{'fa':[1.61413 ,8.83112 ,7.46480 ,0.95702],'fb':[100.51727 ,5.41189 ,0.35652 ,38.20331],'fc':1.13046,'Ne':1}, #Rw: 0.023 ' 3d':{'fa':[0.38696 ,0.63127 ,-0.01944 ,0.00000],'fb':[21.81018 ,6.36048 ,0.50771 ,0.00000],'fc':0.00000,'Ne':5}, #Rw: 0.192 ' 3d':{'fa':[0.20591 ,-0.07602 ,0.07633 ,-0.20614],'fb':[2.16518 ,36.08445 ,0.87748 ,13.91487],'fc':-0.00003,'Ne':1}, #Rw: 0.055 ' 3d':{'fa':[0.01812 ,-0.15158 ,0.12811 ,0.00000],'fb':[45.58913 ,5.96872 ,0.67658 ,0.00000],'fc':0.00537,'Ne':1}}, #Rw: 0.228 - 'Mn+2':{'ZSlater':5.949,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[25.2616,8.3216,10.5320,3.8440,3.7128,3.5711,1.4831,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[1.16182 ,6.89288 ,7.47904 ,1.36627],'fb':[12.17385 ,5.28184 ,0.35349 ,5.28205],'fc':1.10040,'Ne':1}, #Rw: 0.021 + 'Mn+2':{'Ar core':{'fa':[1.16182 ,6.89288 ,7.47904 ,1.36627],'fb':[12.17385 ,5.28184 ,0.35349 ,5.28205],'fc':1.10040,'Ne':1}, #Rw: 0.021 ' 3d':{'fa':[0.41893 ,0.60332 ,-0.02304 ,0.00000],'fb':[18.95348 ,6.03459 ,0.56959 ,0.00000],'fc':0.00000,'Ne':5}, #Rw: 0.134 ' 3d':{'fa':[0.19793 ,-0.08970 ,0.09634 ,-0.20486],'fb':[2.30031 ,28.81541 ,0.96138 ,12.99056],'fc':0.00033,'Ne':1}, #Rw: 0.030 ' 3d':{'fa':[0.02488 ,-0.16239 ,0.13195 ,0.00000],'fb':[35.47753 ,5.91009 ,0.67792 ,0.00000],'fc':0.00559,'Ne':1}}, #Rw: 0.208 - 'Mn+3':{'ZSlater':5.949,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[25.2616,8.3216,10.5320,3.8440,3.7128,3.5711,1.4831,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[-1.72987 ,8.76758 ,7.47214 ,2.40816],'fb':[19.04011 ,5.30848 ,0.34981 ,17.45871],'fc':1.08166,'Ne':1}, #Rw: 0.019 + 'Mn+3':{'Ar core':{'fa':[-1.72987 ,8.76758 ,7.47214 ,2.40816],'fb':[19.04011 ,5.30848 ,0.34981 ,17.45871],'fc':1.08166,'Ne':1}, #Rw: 0.019 ' 3d':{'fa':[0.41997 ,0.61149 ,-0.03191 ,0.00000],'fb':[14.93225 ,5.44321 ,0.65407 ,0.00000],'fc':0.00000,'Ne':4}, #Rw: 0.074 ' 3d':{'fa':[0.21947 ,-0.08769 ,0.10474 ,-0.23683],'fb':[2.19611 ,21.76528 ,0.93787 ,10.71281],'fc':0.00033,'Ne':1}, #Rw: 0.027 ' 3d':{'fa':[0.03612 ,-0.18900 ,0.14649 ,0.00000],'fb':[23.32826 ,4.97350 ,0.66572 ,0.00000],'fc':0.00640,'Ne':1}}, #Rw: 0.117 - 'Mn+4':{'ZSlater':5.949,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[25.2616,8.3216,10.5320,3.8440,3.7128,3.5711,1.4831,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,5,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[-0.94535 ,9.01484 ,7.46855 ,1.43207],'fb':[20.34339 ,5.20701 ,0.34171 ,18.02925],'fc':1.02965,'Ne':1}, #Rw: 0.021 + 'Mn+4':{'Ar core':{'fa':[-0.94535 ,9.01484 ,7.46855 ,1.43207],'fb':[20.34339 ,5.20701 ,0.34171 ,18.02925],'fc':1.02965,'Ne':1}, #Rw: 0.021 ' 3d':{'fa':[0.41258 ,0.62778 ,-0.04060 ,0.00000],'fb':[12.37238 ,4.99320 ,0.69918 ,0.00000],'fc':0.00000,'Ne':3}, #Rw: 0.044 ' 3d':{'fa':[0.23874 ,-0.09331 ,0.11446 ,-0.26025],'fb':[2.10170 ,17.07094 ,0.92018 ,8.90205],'fc':0.00035,'Ne':1}, #Rw: 0.022 ' 3d':{'fa':[0.04714 ,-0.21352 ,0.15954 ,0.00000],'fb':[17.25719 ,4.30610 ,0.64357 ,0.00000],'fc':0.00682,'Ne':1}}, #Rw: 0.073 - 'Fe':{'ZSlater':6.509,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[26.3464,8.7083,11.0372,4.0379,3.9136,3.8248,1.5430,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,6,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.97986698, 26.7335, 1], [-0.10124, 22.7356, 2], [0.07752, 11.1575, 2], [0.09669, 32.8697, 3], [0.03498, 8.2271, 3], - [-0.0198, 5.702, 3], [0.00994, 3.8406, 3], [-0.06242, 13.2064, 4], [-0.00474, 2.2686, 4], [0.00436, 1.4386, 4], - [-0.00199, 0.9475, 4]], - '2s': [[-0.304003, 26.6902, 1], [-0.148443, 22.7668, 2], [0.99863398, 11.1752, 2], [-0.013148, 32.8699, 3], [0.090178, 8.2266, 3], - [-0.011523, 5.7012, 3], [0.021227, 3.8391, 3], [0.084607, 13.1951, 4], [0.000314, 2.2676, 4], [-0.002177, 1.4378, 4], - [-0.001122, 0.9462, 4]], - '3s': [[0.117289, 26.7105, 1], [0.070408, 22.7391, 2], [-0.46195999, 11.1581, 2], [0.005098, 32.8594, 3], [-0.07711, 8.2268, 3], - [0.63688201, 5.7061, 3], [0.56583601, 3.8468, 3], [-0.107495, 13.2075, 4], [-0.000343, 2.2676, 4], [0.002728, 1.4378, 4], - [0.003742, 0.9462, 4]], - '4s': [[0.025695, 26.7103, 1], [0.01527, 22.7394, 2], [-0.101936, 11.1579, 2], [0.001471, 32.8592, 3], [-0.028579, 8.2265, 3], - [0.180214, 5.7, 3], [0.119361, 3.8432, 3], [-0.022991, 13.2036, 4], [-0.285227, 2.2755, 4], [-0.536533, 1.4431, 4], - [-0.30648401, 0.952, 4]], - '2p': [[0.003817, 42.329, 2], [0.32225901, 16.1407, 2], [0.49480399, 8.7195, 2], [-0.020901, 4.0983, 2], [0.234972, 13.3868, 3], - [0.036566, 3.3693, 3], [-0.013667, 2.1929, 3]], - '3p': [[0.001274, 42.2925, 2], [0.076649, 16.3337, 2], [0.50792801, 8.7223, 2], [-0.82426101, 4.0963, 2], [0.02631, 13.2521, 3], - [-0.401458, 3.3764, 3], [-0.041994, 2.1904, 3]], - '3d': [[0.01803, 12.747, 3], [0.214103, 6.6254, 3], [0.412689, 4.0192, 3], [0.38428101, 2.4051, 3], [0.134867, 1.4745, 3]],}, - 'Sl core':{'fa':[6.67657 ,3.20171 ,8.10822 ,0.00000],'fb':[6.30660 ,2.80957 ,0.23556 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.138 + 'Fe':{'Sl core':{'fa':[6.67657 ,3.20171 ,8.10822 ,0.00000],'fb':[6.30660 ,2.80957 ,0.23556 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.138 + 'Sl val':{'fa':[0.27733 ,0.43130 ,-0.02287 ,0.30963],'fb':[79.16751 ,4.99442 ,0.40907 ,17.50962],'fc':0.00353,'Ne':8}, #Rw: 0.168 'Sl 3d':{'fa':[0.60396 ,-0.04121 ,0.41340 ,0.00000],'fb':[5.39447 ,0.14106 ,17.96068 ,0.00000],'fc':0.02204,'Ne':6}, #Rw: 0.270 'Sl 4s':{'fa':[0.77277 ,2.58399 ,0.40293 ,-2.76043],'fb':[56.26928 ,8.36059 ,119.29860 ,8.79463],'fc':0.00054,'Ne':2}, #Rw: 0.435 'Ca core':{'fa':[1.65317 ,8.85265 ,7.49168 ,0.91492],'fb':[93.72732 ,4.87560 ,0.32105 ,33.82382],'fc':1.08487,'Ne':1}, #Rw: 0.023 ' 3d':{'fa':[0.38322 ,0.63539 ,-0.01983 ,0.00000],'fb':[19.32105 ,5.56964 ,0.43818 ,0.00000],'fc':0.00000,'Ne':6}, #Rw: 0.191 ' 3d':{'fa':[0.20315 ,-0.07628 ,0.07872 ,-0.20553],'fb':[1.89653 ,31.96897 ,0.78168 ,12.11466],'fc':-0.00001,'Ne':1}, #Rw: 0.052 ' 3d':{'fa':[0.01753 ,-0.15083 ,0.12819 ,0.00000],'fb':[40.63038 ,5.19593 ,0.58820 ,0.00000],'fc':0.00513,'Ne':1}}, #Rw: 0.261 - 'Fe+2':{'ZSlater':6.509,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[26.3464,8.7083,11.0372,4.0379,3.9136,3.8248,1.5430,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,6,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[1.29240 ,6.83229 ,7.51853 ,1.31917],'fb':[10.54686 ,4.72679 ,0.31694 ,4.70185],'fc':1.03759,'Ne':1}, #Rw: 0.024 + 'Fe+2':{'Ar core':{'fa':[1.29240 ,6.83229 ,7.51853 ,1.31917],'fb':[10.54686 ,4.72679 ,0.31694 ,4.70185],'fc':1.03759,'Ne':1}, #Rw: 0.024 ' 3d':{'fa':[0.41207 ,0.61010 ,-0.02301 ,0.00000],'fb':[17.04963 ,5.32110 ,0.49103 ,0.00000],'fc':0.00000,'Ne':6}, #Rw: 0.135 ' 3d':{'fa':[0.19748 ,-0.08594 ,0.09396 ,-0.20580],'fb':[1.98421 ,26.44917 ,0.84020 ,11.53016],'fc':0.00033,'Ne':1}, #Rw: 0.032 ' 3d':{'fa':[0.02315 ,-0.16014 ,0.13144 ,0.00000],'fb':[32.80304 ,5.15446 ,0.59369 ,0.00000],'fc':0.00559,'Ne':1}}, #Rw: 0.219 - 'Fe+3':{'ZSlater':6.509,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[26.3464,8.7083,11.0372,4.0379,3.9136,3.8248,1.5430,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,6,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[-2.93845 ,8.66009 ,7.51845 ,3.75535],'fb':[15.42042 ,4.73883 ,0.31234 ,14.58249],'fc':1.00443,'Ne':1}, #Rw: 0.014 + 'Fe+3':{'Ar core':{'fa':[-2.93845 ,8.66009 ,7.51845 ,3.75535],'fb':[15.42042 ,4.73883 ,0.31234 ,14.58249],'fc':1.00443,'Ne':1}, #Rw: 0.014 ' 3d':{'fa':[0.41415 ,0.61649 ,-0.03112 ,0.00000],'fb':[13.61852 ,4.84385 ,0.56443 ,0.00000],'fc':0.00000,'Ne':5}, #Rw: 0.079 ' 3d':{'fa':[0.21878 ,-0.08740 ,0.10017 ,-0.23182],'fb':[1.90191 ,19.97230 ,0.81779 ,9.62028],'fc':0.00027,'Ne':1}, #Rw: 0.025 ' 3d':{'fa':[0.03336 ,-0.18470 ,0.14488 ,0.00000],'fb':[21.90387 ,4.40841 ,0.58721 ,0.00000],'fc':0.00646,'Ne':1}}, #Rw: 0.110 - 'Co':{'ZSlater':7.047,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[27.4389,9.0963,11.5433,4.2311,4.1131,4.0730,1.6010,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,7,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.98454797, 27.7485, 1], [-0.119816, 23.6438, 2], [0.087371, 11.4482, 2], [0.105973, 34.038, 3], [0.018059, 8.324, 3], - [-0.010464, 5.8675, 3], [0.005955, 3.9722, 3], [-0.051818, 13.7162, 4], [-0.003011, 2.3516, 4], [0.002778, 1.4794, 4], - [-0.001208, 0.9689, 4]], - '2s': [[-0.30693999, 27.7082, 1], [-0.14523999, 23.6586, 2], [1.02988005, 11.4593, 2], [-0.01329, 34.0467, 3], [0.07003, 8.3231, 3], - [-0.00032, 5.867, 3], [0.00997, 3.9714, 3], [0.06368, 13.7119, 4], [0.00633, 2.3512, 4], [0.00552, 1.479, 4], [-0.00103, 0.9682, 4]], - '3s': [[0.11927, 27.7203, 1], [0.06976, 23.6466, 2], [-0.48280001, 11.4497, 2], [0.00555, 34.0342, 3], [-0.04334, 8.3237, 3], - [0.65819001, 5.8725, 3], [0.53219998, 3.9781, 3], [-0.10615, 13.7188, 4], [-0.00389, 2.3511, 4], [0.00032, 1.4789, 4], - [0.00236, 0.9682, 4]], - '4s': [[0.025, 27.72, 1], [0.01445, 23.647, 2], [-0.10209, 11.4491, 2], [0.00156, 34.034, 3], [-0.02105, 8.3235, 3], [0.18208, 5.8662, 3], - [0.10185, 3.9746, 3], [-0.02184, 13.7147, 4], [-0.29163, 2.3591, 4], [-0.53622001, 1.4846, 4], [-0.30162999, 0.9748, 4]], - '2p': [[-0.00382, 44.2683, 2], [-0.32894999, 16.9275, 2], [-0.44216999, 9.1295, 2], [-0.01403, 4.3896, 2], [-0.26774999, 13.9379, 3], - [0.01061, 3.5956, 3], [-0.02053, 2.3607, 3]], - '3p': [[-0.00138, 44.2552, 2], [-0.07764, 17.0216, 2], [-0.51160997, 9.1338, 2], [0.80474001, 4.3835, 2], [-0.02992, 13.8576, 3], - [0.42038, 3.6094, 3], [0.0514, 2.3574, 3]], - '3d': [[0.01771, 13.3893, 3], [0.2137, 7.0309, 3], [0.40801999, 4.2924, 3], [0.38760999, 2.5553, 3], [0.14058, 1.5352, 3]],}, - 'Sl core':{'fa':[4.87800 ,4.91215 ,8.20215 ,0.00000],'fb':[6.36211 ,3.16906 ,0.21978 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.096 + 'Co':{'Sl core':{'fa':[4.87800 ,4.91215 ,8.20215 ,0.00000],'fb':[6.36211 ,3.16906 ,0.21978 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.096 + 'Sl val':{'fa':[0.25152 ,0.32036 ,0.44531 ,-0.02677],'fb':[73.15314 ,15.39509 ,4.47403 ,0.24419],'fc':0.00847,'Ne':9}, #Rw: 0.164 'Sl 3d':{'fa':[0.40886 ,2.63417 ,0.60731 ,0.00000],'fb':[16.33386 ,-0.00150 ,4.80833 ,0.00000],'fc':-2.65229,'Ne':7}, #Rw: 0.278 'Sl 4s':{'fa':[0.77326 ,-2.29653 ,2.12948 ,0.39315],'fb':[52.89744 ,7.97415 ,7.52914 ,113.45540],'fc':0.00043,'Ne':2}, #Rw: 0.388 'Ca core':{'fa':[1.62897 ,8.87010 ,7.51987 ,0.92686],'fb':[89.27464 ,4.42793 ,0.29169 ,31.68325],'fc':1.05131,'Ne':1}, #Rw: 0.028 ' 3d':{'fa':[0.38040 ,0.63824 ,-0.01989 ,0.00000],'fb':[17.26171 ,4.92967 ,0.37569 ,0.00000],'fc':0.00000,'Ne':7}, #Rw: 0.186 ' 3d':{'fa':[0.22240 ,-0.07556 ,0.05864 ,-0.20411],'fb':[1.58315 ,28.68983 ,0.57132 ,10.76539],'fc':-0.00129,'Ne':1}, #Rw: 0.053 ' 3d':{'fa':[0.01678 ,-0.14960 ,0.12822 ,0.00000],'fb':[37.30800 ,4.57424 ,0.51390 ,0.00000],'fc':0.00464,'Ne':1}}, #Rw: 0.269 - 'Co+2':{'ZSlater':7.047,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[27.4389,9.0963,11.5433,4.2311,4.1131,4.0730,1.6010,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,7,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[1.44362 ,6.31532 ,7.57992 ,1.73033],'fb':[9.37432 ,4.21362 ,0.28179 ,4.21194],'fc':0.93094,'Ne':1}, #Rw: 0.013 + 'Co+2':{'Ar core':{'fa':[1.44362 ,6.31532 ,7.57992 ,1.73033],'fb':[9.37432 ,4.21362 ,0.28179 ,4.21194],'fc':0.93094,'Ne':1}, #Rw: 0.013 ' 3d':{'fa':[0.40416 ,0.61756 ,-0.02255 ,0.00000],'fb':[15.49886 ,4.74809 ,0.41822 ,0.00000],'fc':0.00000,'Ne':7}, #Rw: 0.138 ' 3d':{'fa':[0.20172 ,-0.08876 ,0.08808 ,-0.20103],'fb':[1.72169 ,23.76097 ,0.71953 ,10.14711],'fc':0.00002,'Ne':1}, #Rw: 0.038 ' 3d':{'fa':[0.02183 ,-0.15775 ,0.13092 ,0.00000],'fb':[30.41751 ,4.56569 ,0.51791 ,0.00000],'fc':0.00503,'Ne':1}}, #Rw: 0.240 - 'Co+3':{'ZSlater':7.047,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[27.4389,9.0963,11.5433,4.2311,4.1131,4.0730,1.6010,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,7,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[-1.63979 ,8.55392 ,7.57039 ,2.59067],'fb':[13.38516 ,4.25679 ,0.28014 ,12.17257],'fc':0.92450,'Ne':1}, #Rw: 0.010 + 'Co+3':{'Ar core':{'fa':[-1.63979 ,8.55392 ,7.57039 ,2.59067],'fb':[13.38516 ,4.25679 ,0.28014 ,12.17257],'fc':0.92450,'Ne':1}, #Rw: 0.010 ' 3d':{'fa':[0.40756 ,0.62208 ,-0.03013 ,0.00000],'fb':[12.50767 ,4.35176 ,0.48672 ,0.00000],'fc':0.00000,'Ne':6}, #Rw: 0.082 ' 3d':{'fa':[0.21398 ,-0.08936 ,0.10200 ,-0.22680],'fb':[1.70137 ,18.26511 ,0.73243 ,8.61540],'fc':0.00020,'Ne':1}, #Rw: 0.026 ' 3d':{'fa':[0.03110 ,-0.18072 ,0.14340 ,0.00000],'fb':[20.61112 ,3.94450 ,0.51880 ,0.00000],'fc':0.00623,'Ne':1}}, #Rw: 0.119 - 'Ni':{'ZSlater':7.270,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[28.5391,9.4848,12.0514,4.4131,4.2947,4.1510,1.5712,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,8,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.99016702, 28.7488, 1], [-0.13675199, 24.5187, 2], [0.092508, 11.7003, 2], [0.11521, 35.2747, 3], [0.021278, 8.3195, 3], - [-0.01427, 5.8939, 3], [0.007772, 4.0433, 3], [-0.056903, 14.1338, 4], [-0.003682, 2.4341, 4], [0.003267, 1.5232, 4], - [-0.001478, 0.9922, 4]], - '2s': [[0.31028, 28.7024, 1], [0.14018001, 24.5551, 2], [-1.06276, 11.7193, 2], [0.01418, 35.2743, 3], [-0.06289, 8.3194, 3], - [0.01483, 5.8932, 3], [-0.02078, 4.0418, 3], [-0.0377, 14.1278, 4], [-0.00237, 2.4332, 4], [0.00181, 1.5225, 4], - [0.00106, 0.9912, 4]], - '3s': [[-0.12122, 28.7271, 1], [-0.06915, 24.5226, 2], [0.50781, 11.7018, 2], [-0.00608, 35.2609, 3], [-0.02702, 8.3193, 3], - [-0.68516999, 5.8983, 3], [-0.46349999, 4.0485, 3], [0.10599, 14.1387, 4], [0.01024, 2.4333, 4], [0.00184, 1.5225, 4], - [9e-05, 0.9912, 4]], - '4s': [[0.0239, 28.7266, 1], [0.01348, 24.5237, 2], [-0.1014, 11.7009, 2], [0.00166, 35.2606, 3], [-0.00485, 8.319, 3], - [0.1823, 5.8933, 3], [0.07479, 4.0445, 3], [-0.02091, 14.1323, 4], [-0.29519001, 2.4411, 4], [-0.53408003, 1.5288, 4], - [-0.30083999, 0.9987, 4]], - '2p': [[-0.004081, 46.7866, 2], [-0.32127801, 17.5821, 2], [-0.48498899, 9.568, 2], [0.009119, 4.5919, 2], [-0.240392, 14.6566, 3], - [-0.017004, 3.754, 3], [-0.002353, 2.4675, 3]], - '3p': [[-0.00144, 46.7515, 2], [-0.07653, 17.7663, 2], [-0.51700002, 9.5754, 2], [0.81519002, 4.5909, 2], [-0.03037, 14.5124, 3], - [0.41503999, 3.76, 3], [0.04874, 2.4644, 3]], - '3d': [[0.0181, 13.9019, 3], [0.22296999, 7.3593, 3], [0.41721001, 4.4625, 3], [0.37711999, 2.6355, 3], [0.13478, 1.5852, 3]],}, - 'Sl core':{'fa':[3.76888 ,8.24756 ,5.97868 ,0.00000],'fb':[6.27424 ,0.20387 ,3.15838 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.064 + 'Ni':{'Sl core':{'fa':[3.76888 ,8.24756 ,5.97868 ,0.00000],'fb':[6.27424 ,0.20387 ,3.15838 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.064 + 'Sl val':{'fa':[0.45813 ,0.32802 ,-0.05604 ,0.22982],'fb':[4.04295 ,13.76552 ,0.07582 ,68.01663],'fc':0.03895,'Ne':10}, #Rw: 0.160 'Sl 3d':{'fa':[0.00766 ,0.40061 ,0.61371 ,0.00000],'fb':[-0.25167 ,14.98790 ,4.34627 ,0.00000],'fc':-0.02395,'Ne':8}, #Rw: 0.279 'Sl 4s':{'fa':[0.77503 ,-16.69742 ,0.38239 ,16.53952],'fb':[49.99568 ,7.05091 ,108.46954 ,7.00106],'fc':0.00030,'Ne':2}, #Rw: 0.330 'Ca core':{'fa':[1.68472 ,8.88334 ,7.51924 ,0.87257],'fb':[83.24872 ,4.03231 ,0.26676 ,27.79372],'fc':1.03679,'Ne':1}, #Rw: 0.024 ' 3d':{'fa':[0.37191 ,0.64613 ,-0.01925 ,0.00000],'fb':[15.75278 ,4.43475 ,0.31176 ,0.00000],'fc':0.00000,'Ne':8}, #Rw: 0.184 ' 3d':{'fa':[0.23119 ,-0.07450 ,0.04997 ,-0.20366],'fb':[1.37175 ,26.08614 ,0.43101 ,9.64890],'fc':-0.00293,'Ne':1}, #Rw: 0.061 ' 3d':{'fa':[0.01616 ,-0.14817 ,0.12845 ,0.00000],'fb':[34.11461 ,4.07647 ,0.44799 ,0.00000],'fc':0.00358,'Ne':1}}, #Rw: 0.275 - 'Ni+2':{'ZSlater':7.270,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[28.5391,9.4848,12.0514,4.4131,4.2947,4.1510,1.5712,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,8,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[1.50924 ,6.26439 ,7.62597 ,1.73305],'fb':[8.45013 ,3.81974 ,0.25509 ,3.82100],'fc':0.86745,'Ne':1}, #Rw: 0.007 + 'Ni+2':{'Ar core':{'fa':[1.50924 ,6.26439 ,7.62597 ,1.73305],'fb':[8.45013 ,3.81974 ,0.25509 ,3.82100],'fc':0.86745,'Ne':1}, #Rw: 0.007 ' 3d':{'fa':[0.39621 ,0.62478 ,-0.02184 ,0.00000],'fb':[14.19324 ,4.27583 ,0.35304 ,0.00000],'fc':0.00000,'Ne':8}, #Rw: 0.140 ' 3d':{'fa':[0.22488 ,-0.08247 ,0.06233 ,-0.20301],'fb':[1.42665 ,22.27684 ,0.51256 ,9.31316],'fc':-0.00170,'Ne':1}, #Rw: 0.039 ' 3d':{'fa':[0.02087 ,-0.15513 ,0.13063 ,0.00000],'fb':[28.12007 ,4.09750 ,0.44840 ,0.00000],'fc':0.00365,'Ne':1}}, #Rw: 0.281 - 'Ni+3':{'ZSlater':7.270,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[28.5391,9.4848,12.0514,4.4131,4.2947,4.1510,1.5712,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,8,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[-1.40584 ,8.43585 ,7.62591 ,2.50065],'fb':[11.47714 ,3.84181 ,0.25235 ,10.33579],'fc':0.84320,'Ne':1}, #Rw: 0.007 + 'Ni+3':{'Ar core':{'fa':[-1.40584 ,8.43585 ,7.62591 ,2.50065],'fb':[11.47714 ,3.84181 ,0.25235 ,10.33579],'fc':0.84320,'Ne':1}, #Rw: 0.007 ' 3d':{'fa':[0.40021 ,0.62813 ,-0.02882 ,0.00000],'fb':[11.56230 ,3.94342 ,0.41588 ,0.00000],'fc':0.00000,'Ne':7}, #Rw: 0.087 ' 3d':{'fa':[0.23603 ,-0.08467 ,0.07462 ,-0.22496],'fb':[1.41765 ,17.12234 ,0.55317 ,7.96539],'fc':-0.00101,'Ne':1}, #Rw: 0.029 ' 3d':{'fa':[0.02965 ,-0.176686 ,0.14188 ,0.00000],'fb':[19.28747 ,3.58410 ,0.45330 ,0.00000],'fc':0.00514,'Ne':1}}, #Rw: 0.137 - 'Cu':{'ZSlater':8.290,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[29.6482,9.8754,12.5597,4.6058,4.4931,4.3970,1.6229,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,10,], [1,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.00202596, 29.6349, 1], [-0.17460801, 25.2073, 2], [0.101792, 12.5522, 2], [0.1311, 36.5216, 3], [0.011053, 9.254, 3], - [-0.004394, 6.2707, 3], [0.003015, 4.1997, 3], [-0.049931, 14.8956, 4], [-0.000962, 2.4797, 4], [0.000611, 1.4755, 4], - [-0.000145, 0.9424, 4]], - '2s': [[0.31386, 29.6853, 1], [0.14636999, 25.2656, 2], [-1.02417004, 12.5656, 2], [0.01548, 36.3621, 3], [-0.08348, 9.2484, 3], - [0.01224, 6.2647, 3], [-0.01798, 4.188, 3], [-0.07061, 14.8843, 4], [-0.00729, 2.4698, 4], [-0.00083, 1.4684, 4], [9e-05, 0.9292, 4]], - '3s': [[-0.12195, 29.6968, 1], [-0.07188, 25.2502, 2], [0.48488, 12.5553, 2], [-0.00632, 36.3551, 3], [0.01504, 9.2484, 3], - [-0.68781, 6.27, 3], [-0.48267001, 4.1975, 3], [0.11687, 14.8939, 4], [0.00113, 2.4699, 4], [-0.00626, 1.4684, 4], - [-0.00359, 0.9292, 4]], - '4s': [[0.02071, 29.6964, 1], [0.01187, 25.2508, 2], [-0.08476, 12.555, 2], [0.0017, 36.3548, 3], [-0.01385, 9.2482, 3], - [0.16756999, 6.2639, 3], [0.06744, 4.1901, 3], [-0.02026, 14.889, 4], [-0.24368, 2.4737, 4], [-0.53094, 1.4801, 4], - [-0.35865, 0.9416, 4]], - '2p': [[0.00389, 48.7959, 2], [0.32791001, 18.4616, 2], [0.42205, 10.0001, 2], [0.02047, 4.7454, 2], [0.28467, 15.1547, 3], - [0.00017, 3.8218, 3], [-0.00585, 2.5167, 3]], - '3p': [[0.00156, 48.7951, 2], [0.07728, 18.4708, 2], [0.50906003, 9.9987, 2], [-0.83749998, 4.7371, 2], [0.03508, 15.1421, 3], - [-0.38758001, 3.8376, 3], [-0.05394, 2.513, 3]], - '3d': [[0.01921, 14.1927, 3], [0.23745, 7.5405, 3], [0.42493001, 4.4636, 3], [0.36717001, 2.5106, 3], [0.15243, 1.4047, 3]]}, - 'Sl core':{'fa':[7.67745 ,7.83516 ,1.67782 ,0.00000],'fb':[0.22786 ,3.41830 ,7.76529 ,0.00000],'fc':0.80909,'Ne':1}, #Rw: 0.008 + 'Cu':{'Sl core':{'fa':[7.67745 ,7.83516 ,1.67782 ,0.00000],'fb':[0.22786 ,3.41830 ,7.76529 ,0.00000],'fc':0.80909,'Ne':1}, #Rw: 0.008 + 'Sl val':{'fa':[0.14758 ,0.40329 ,0.46494 ,-0.28888],'fb':[66.21544 ,12.34460 ,3.67614 ,0.01172],'fc':0.27186,'Ne':11}, #Rw: 0.159 'Sl 3d':{'fa':[0.58212 ,0.38367 ,0.04382 ,0.00000],'fb':[4.16440 ,14.32262 ,48.41928 ,0.00000],'fc':-0.00948,'Ne':10}, #Rw: 0.553 'Sl 4s':{'fa':[0.73800 ,0.37319 ,1.08513 ,-1.19652],'fb':[60.38975 ,131.24766 ,6.50555 ,7.05036],'fc':0.00006,'Ne':1}, #Rw: 0.285 }, - 'Cu+2':{'ZSlater':8.290,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[29.6482,9.8754,12.5597,4.6058,4.4931,4.3970,1.6229,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,10,], [1,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Ar core':{'fa':[1.60130 ,5.94946 ,7.68014 ,1.97912],'fb':[7.62483 ,3.46911 ,0.23089 ,3.46960],'fc':0.78989,'Ne':1}, #Rw: 0.005 + 'Cu+2':{'Ar core':{'fa':[1.60130 ,5.94946 ,7.68014 ,1.97912],'fb':[7.62483 ,3.46911 ,0.23089 ,3.46960],'fc':0.78989,'Ne':1}, #Rw: 0.005 ' 3d':{'fa':[0.38805 ,0.63180 ,-0.02070 ,0.00000],'fb':[13.08204 ,3.88361 ,0.28731 ,0.00000],'fc':0.00000,'Ne':9}, #Rw: 0.139 ' 3d':{'fa':[0.23115 ,-0.08327 ,0.05555 ,-0.20028],'fb':[1.25371 ,20.40557 ,0.40580 ,8.37761],'fc':-0.00313,'Ne':1}, #Rw: 0.042 ' 3d':{'fa':[0.01991 ,-0.15279 ,0.13071 ,0.00000],'fb':[26.18505 ,3.69564 ,0.39165 ,0.00000],'fc':0.00219,'Ne':1}}, #Rw: 0.247 - 'Zn':{'ZSlater':8.582,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[30.7660,10.2670,13.0673,4.8084,4.7071,4.7954,1.7681,], - 'popCore':[[2,], [2,6,], [2,6,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,10,], [2,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[0.99719602, 30.7338, 1], [-0.15169699, 26.0591, 2], [0.117199, 13.2539, 2], [0.104826, 37.7399, 3], [0.040846, 9.8601, 3], - [-0.023948, 6.6342, 3], [0.009317, 4.4684, 3], [-0.075564, 15.6268, 4], [-0.003844, 2.6396, 4], [0.003372, 1.6279, 4], - [-0.001557, 1.0419, 4]], - '2s': [[0.31748, 30.6503, 1], [0.14562, 26.1234, 2], [-0.99765998, 13.2915, 2], [0.01767, 37.7744, 3], [-0.10839, 9.8599, 3], - [0.03491, 6.6338, 3], [-0.03923, 4.4665, 3], [-0.08865, 15.6018, 4], [0.00663, 2.6387, 4], [0.0107, 1.6263, 4], [0.00369, 1.0391, 4]], - '3s': [[-0.12225, 30.7027, 1], [-0.08795, 26.0605, 2], [0.48864001, 13.2542, 2], [0.00084, 37.7395, 3], [0.04937, 9.8597, 3], - [-0.68351001, 6.6379, 3], [-0.51168001, 4.4816, 3], [0.11298, 15.6277, 4], [0.0069, 2.6386, 4], - [0.01936, 1.6258, 4], [0.01206, 1.0393, 4]], - '4s': [[0.02112, 30.7026, 1], [0.01225, 26.0605, 2], [-0.08219, 13.254, 2], [0.0017, 37.7395, 3], [-0.02159, 9.8589, 3], - [0.16121, 6.6346, 3], [0.07298, 4.4693, 3], [-0.01814, 15.6259, 4], [-0.28751001, 2.6455, 4], [-0.53631997, 1.634, 4], - [-0.31029999, 1.0488, 4]], - '2p': [[-0.00426, 51.338, 2], [-0.32326999, 19.1171, 2], [-0.44694, 10.4277, 2], [-0.0196, 5.0046, 2], [-0.26453999, 15.8804, 3], - [0.01927, 4.0676, 3], [-0.0262, 2.6704, 3]], - '3p': [[-0.00164, 51.3226, 2], [-0.07711, 19.2118, 2], [-0.51613998, 10.4331, 2], [0.82872999, 4.9995, 2], [-0.03635, 15.7933, 3], - [0.40454999, 4.0797, 3], [0.04851, 2.6675, 3]], - '3d': [[0.01819, 15.0375, 3], [0.22920001, 8.0847, 3], [0.41670001, 4.9163, 3], [0.37457001, 2.8888, 3], [0.13542999, 1.7015, 3]],}, - 'Sl core':{'fa':[7.05273 ,2.65247 ,8.29169 ,0.00000],'fb':[2.86332 ,5.89422 ,0.17570 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.028 + 'Zn':{'Sl core':{'fa':[7.05273 ,2.65247 ,8.29169 ,0.00000],'fb':[2.86332 ,5.89422 ,0.17570 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.028 + 'Sl val':{'fa':[0.19936 ,0.34714 ,0.46986 ,-0.36748],'fb':[58.93003 ,10.92474 ,3.27855 ,0.00879],'fc':0.34989,'Ne':12}, #Rw: 0.159 'Sl 3d':{'fa':[0.61175 ,0.40436 ,0.99848 ,0.00000],'fb':[3.49349 ,12.35160 ,-0.00291 ,0.00000],'fc':-1.01687,'Ne':10}, #Rw: 0.308 'Sl 4s':{'fa':[9.02589 ,-9.16756 ,0.77300 ,0.36842],'fb':[5.82292 ,5.88787 ,44.96686 ,99.72660],'fc':0.00006,'Ne':2}, #Rw: 0.216 }, - 'Ga':{'ZSlater':3.577,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[31.8926,10.6601,13.5719,5.0151,4.9262,5.1893,1.9587,1.4480,], - 'popCore':[[2,], [2,6,], [2,6,10,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [2,1,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.01241302, 31.5939, 1], [-0.23257001, 26.8186, 2], [0.118894, 13.9031, 2], [0.163901, 39.2901, 3], [-0.25480101, 10.3916, 3], - [-0.059118, 7.0362, 3], [0.15479299, 4.7701, 3], [0.15995599, 16.3734, 4], [-0.010545, 2.8387, 4], [-0.031956, 1.8235, 4], - [-0.015233, 1.2004, 4]], - '2s': [[-0.31142601, 31.7211, 1], [-0.200812, 26.8688, 2], [1.01692998, 13.8975, 2], [0.014586, 39.0082, 3], [0.06222, 10.3962, 3], - [0.040809, 7.0298, 3], [-0.014447, 4.7722, 3], [0.098783, 16.367, 4], [0.00315, 2.8376, 4], [0.008396, 1.8239, 4], [-0.007642, 1.2007, 4]], - '3s': [[0.124861, 31.7059, 1], [0.087297, 26.8958, 2], [-0.48441899, 13.9064, 2], [0.000752, 39.0025, 3], [-0.072279, 10.3983, 3], - [0.65697497, 7.0351, 3], [0.55111498, 4.7865, 3], [-0.114756, 16.353, 4], [-0.008549, 2.8374, 4], [-0.012483, 1.8234, 4], - [-0.010555, 1.2003, 4]], - '4s': [[0.026358, 31.7059, 1], [0.015292, 26.8955, 2], [-0.100253, 13.9061, 2], [0.002126, 39.0025, 3], [-0.027101, 10.3973, 3], - [0.181914, 7.0304, 3], [0.111837, 4.7761, 3], [-0.024065, 16.3515, 4], [-0.330221, 2.8494, 4], [-0.54739201, 1.8281, 4], - [-0.25180799, 1.2096, 4]], - '2p': [[0.099647, 31.1632, 2], [0.55950803, 11.6758, 2], [0.204864, 25.0741, 3], [0.113455, 8.273, 3], [-0.123658, 5.5158, 3], - [0.082166, 3.7962, 3], [0.154716, 20.7703, 4], [0.00932, 2.2936, 4], [-0.031412, 1.3764, 4], [0.000344, 0.8688, 4]], - '3p': [[-0.024036, 30.9664, 2], [-0.38737199, 11.6879, 2], [-0.03569, 25.4366, 3], [0.266559, 8.2585, 3], [0.629794, 5.5154, 3], - [0.268462, 3.801, 3], [-0.021008, 20.5275, 4], [0.009841, 2.288, 4], [-0.002935, 1.3752, 4], [-0.003775, 0.8682, 4]], - '4p': [[-0.004589, 30.9647, 2], [-0.062126, 11.684, 2], [-0.005717, 25.4398, 3], [0.048794, 8.26, 3], [0.130208, 5.5104, 3], - [0.01378, 3.7964, 3], [-0.003959, 20.5257, 4], [-0.27227801, 2.2951, 4], [-0.52990502, 1.3799, 4], [-0.32708001, 0.8712, 4]], - '3d': [[0.017851, 15.7464, 3], [0.22862899, 8.5617, 3], [0.42101699, 5.2705, 3], [0.37716499, 3.1774, 3], [0.10816, 1.9709, 3]],}, - 'Sl core':{'fa':[5.43266 ,8.05782 ,14.48635 ,0.00000],'fb':[8.81007 ,0.16169 ,2.88582 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.087 + 'Ga':{'Sl core':{'fa':[5.43266 ,8.05782 ,14.48635 ,0.00000],'fb':[8.81007 ,0.16169 ,2.88582 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.087 + 'Sl val':{'fa':[0.35470 ,0.78829 ,-1.68050 ,1.53699],'fb':[105.47363 ,39.19051 ,5.39881 ,5.03813],'fc':-0.00004,'Ne':3}, #Rw: 0.267 'Sl 4s':{'fa':[13.03082 ,0.36092 ,-13.22697 ,0.83511],'fb':[5.16510 ,70.73518 ,5.22122 ,33.03643],'fc':-0.00005,'Ne':2}, #Rw: 0.283 'Sl 4p':{'fa':[0.37085 ,0.08075 ,0.73075 ,-0.18257],'fb':[146.15833 ,4.49689 ,64.58051 ,9.12092],'fc':0.00005,'Ne':1}, #Rw: 0.187 'Cu core':{'fa':[0.91470 ,6.24386 ,7.33905 ,12.71697],'fb':[17.02325 ,6.55290 ,0.19227 ,2.77804],'fc':0.78515,'Ne':1}, #Rw: 0.002 @@ -974,29 +515,8 @@ ' 4p':{'fa':[0.73692 ,-0.18836 ,0.36819 ,0.08296],'fb':[66.09642 ,9.53116 ,148.61990 ,4.73551],'fc':0.00010,'Ne':1}, #Rw: 0.071 ' 4p':{'fa':[0.39058 ,0.06313 ,-0.37037 ,-0.08233],'fb':[20.08873 ,1.49645 ,128.01652 ,2.07359],'fc':-0.00026,'Ne':1}, #Rw: 0.401 ' 4s,4p':{'fa':[0.12058 ,0.38072 ,-1.84017 ,1.33639],'fb':[3236.91243 ,213.87143 ,18.42041 ,15.60176],'fc':-0.00234,'Ne':1}}, #Rw: 1.623 - 'Ge':{'ZSlater':3.799,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[33.0286,11.0535,14.0811,5.2266,5.1515,5.5694,2.1345,1.6649,], - 'popCore':[[2,], [2,6,], [2,6,10,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [2,2,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.01163995, 32.7798, 1], [-0.22, 27.8042, 2], [0.1194, 14.4017, 2], [0.15527, 40.2566, 3], [0.01719, 10.8283, 3], - [-0.00712, 7.4453, 3], [0.00401, 5.092, 3], [-0.06229, 17.0734, 4], [-0.00144, 3.0307, 4], [0.0009, 1.9993, 4], [-0.0007, 1.3475, 4]], - '2s': [[-0.32306999, 32.6885, 1], [-0.14292, 27.8414, 2], [0.98624998, 14.4239, 2], [-0.02036, 40.2861, 3], [0.12585001, 10.8277, 3], - [-0.03559, 7.4436, 3], [0.03195, 5.0889, 3], [0.09052, 17.0523, 4], [0.00445, 3.0283, 4], [-0.00676, 1.9965, 4], - [-0.00412, 1.3445, 4]], - '3s': [[-0.12936001, 32.7211, 1], [-0.07438, 27.8037, 2], [0.48148999, 14.4025, 2], [-0.00764, 40.2598, 3], [0.07966, 10.8275, 3], - [-0.61967999, 7.4485, 3], [-0.59776998, 5.1016, 3], [0.11991, 17.0747, 4], [0.01571, 3.0284, 4], [0.00527, 1.9966, 4], - [0.00019, 1.3445, 4]], - '4s': [[-0.03035, 32.7208, 1], [-0.01731, 27.804, 2], [0.11468, 14.4021, 2], [-0.00238, 40.2593, 3], [0.03237, 10.8267, 3], - [-0.19158, 7.4439, 3], [-0.14993, 5.0958, 3], [0.02756, 17.0699, 4], [0.37156999, 3.0455, 4], [0.53836, 1.9986, 4], - [0.22115999, 1.3547, 4]], - '2p': [[0.09501, 32.7269, 2], [0.58171999, 12.1685, 2], [0.19842, 26.0084, 3], [0.13845, 8.7596, 3], [-0.19959, 5.8578, 3], - [0.16452, 4.0846, 3], [0.13699999, 21.8787, 4], [-0.05285, 2.5545, 4], [-0.0192, 1.5946, 4], [0.04578, 1.0301, 4]], - '3p': [[-0.02291, 32.3736, 2], [-0.39669999, 12.2271, 2], [-0.03278, 26.604, 3], [0.24445, 8.7248, 3], [0.63925999, 5.845, 3], - [0.27812999, 4.0917, 3], [-0.02018, 21.4935, 4], [0.00971, 2.5444, 4], [-0.00115, 1.5876, 4], [-0.00508, 1.028, 4]], - '4p': [[-0.00505, 32.372, 2], [-0.07675, 12.2231, 2], [-0.0061, 26.6069, 3], [0.05452, 8.7261, 3], [0.15356, 5.8401, 3], - [0.03222, 4.086, 3], [-0.00515, 21.4919, 4], [-0.30335999, 2.5515, 4], [-0.54084998, 1.5909, 4], [-0.27963001, 1.028, 4]], - '3d': [[0.01747, 16.4717, 3], [0.22723, 9.0342, 3], [0.42005, 5.6349, 3], [0.38345999, 3.4833, 3], [0.08972, 2.1989, 3]],}, - 'Sl core':{'fa':[14.66906 ,5.29703 ,8.01630 ,0.00000],'fb':[2.60434 ,7.35487 ,0.14875 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.060 + 'Ge':{'Sl core':{'fa':[14.66906 ,5.29703 ,8.01630 ,0.00000],'fb':[2.60434 ,7.35487 ,0.14875 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.060 + 'Sl val':{'fa':[0.37266 ,0.79042 ,-1.45240 ,1.28909],'fb':[81.62145 ,33.06912 ,5.00117 ,4.54499],'fc':-0.00016,'Ne':4}, #Rw: 0.275 'Sl 4s':{'fa':[-16.92424 ,0.85432 ,0.39103 ,16.67893],'fb':[4.66923 ,25.58652 ,53.56000 ,4.62017],'fc':-0.00026,'Ne':2}, #Rw: 0.318 'Sl 4p':{'fa':[12.21679 ,0.82261 ,-12.34723 ,0.30775],'fb':[5.54061 ,47.39092 ,5.59152 ,105.63565],'fc':0.00001,'Ne':2}, #Rw: 0.217 'Cu core':{'fa':[0.81196 ,6.29273 ,7.36475 ,12.84610],'fb':[13.69391 ,5.60548 ,0.17328 ,2.50373],'fc':0.68428,'Ne':1}, #Rw: 0.002 @@ -1004,30 +524,8 @@ ' 4p':{'fa':[0.78554 ,-0.40691 ,0.34753 ,0.27361],'fb':[47.40616 ,6.79200 ,107.16185 ,4.83647],'fc':0.00005,'Ne':2}, #Rw: 0.082 ' 4p':{'fa':[0.43404 ,-0.38353 ,-0.06571 ,0.01710],'fb':[14.03355 ,89.07599 ,3.38551 ,0.73644],'fc':-0.00103,'Ne':1}, #Rw: 0.465 ' 4s,4p':{'fa':[0.12106 ,0.40399 ,-2.67932 ,2.14923],'fb':[2456.37280 ,155.77467 ,14.07462 ,12.48455],'fc':-0.00338,'Ne':1}}, #Rw: 1.998 - 'As':{'ZSlater':4.071,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[34.1746,11.4486,14.5919,5.4420,5.3807,5.9382,2.3033,1.8569,], - 'popCore':[[2,], [2,6,], [2,6,10,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [2,3,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.01903999, 33.7454, 1], [-0.24855299, 28.5481, 2], [0.121855, 15.1543, 2], [0.17022, 41.4612, 3], [0.006713, 11.3325, 3], - [-0.000893, 7.7313, 3], [-0.002368, 5.3725, 3], [-0.045979, 17.6927, 4], [0.002564, 3.217, 4], [-0.002967, 2.1497, 4], - [0.001305, 1.48, 4]], - '2s': [[-0.326516, 33.6578, 1], [-0.144871, 28.6039, 2], [0.96749902, 15.1889, 2], [-0.021997, 41.5034, 3], [0.14267699, 11.3332, 3], - [-0.049873, 7.7304, 3], [0.042326, 5.3674, 3], [0.107921, 17.6605, 4], [0.002598, 3.2157, 4], [-0.012346, 2.1464, 4], - [-0.006851, 1.474, 4]], - '3s': [[-0.131708, 33.7075, 1], [-0.077199, 28.5488, 2], [0.47905001, 15.154, 2], [-0.008246, 41.4604, 3], [0.080505, 11.3329, 3], - [-0.60946798, 7.7372, 3], [-0.60527098, 5.3804, 3], [0.13061801, 17.6955, 4], [0.013796, 3.2157, 4], [0.004821, 2.1465, 4], - [-0.001459, 1.474, 4]], - '4s': [[0.033947, 33.7071, 1], [0.019952, 28.5492, 2], [-0.12579601, 15.1541, 2], [0.002615, 41.4602, 3], [-0.031095, 11.3318, 3], - [0.19981, 7.7303, 3], [0.179316, 5.3766, 3], [-0.03482, 17.6913, 4], [-0.41400501, 3.2377, 4], [-0.52776599, 2.1464, 4], - [-0.19364101, 1.4855, 4]], - '2p': [[0.095181, 34.0732, 2], [0.55022597, 12.7545, 2], [0.196946, 27.3573, 3], [0.149895, 9.3554, 3], [-0.16322599, 6.2472, 3], - [0.108286, 4.4088, 3], [0.15818501, 22.7579, 4], [0.002483, 2.8001, 4], [-0.046532, 1.7845, 4], [0.020045, 1.1725, 4]], - '3p': [[-0.022234, 33.8336, 2], [-0.40157399, 12.7728, 2], [-0.031109, 27.7881, 3], [0.202843, 9.332, 3], [0.65834999, 6.2471, 3], - [0.293865, 4.4169, 3], [-0.021704, 22.4534, 4], [0.018549, 2.7882, 4], [-0.007529, 1.7796, 4], [-0.012497, 1.1707, 4]], - '4p': [[-0.004014, 33.8306, 2], [-0.097389, 12.7685, 2], [-0.004412, 27.7931, 3], [0.055578, 9.3348, 3], [0.168685, 6.2381, 3], - [0.058431, 4.4089, 3], [-0.001642, 22.4504, 4], [-0.326749, 2.793, 4], [-0.54493201, 1.781, 4], [-0.25295401, 1.1685, 4]], - '3d': [[0.01793, 17.0582, 3], [0.23755001, 9.3964, 3], [0.43230999, 5.8581, 3], [0.37338999, 3.6978, 3], [0.06544, 2.3473, 3]],}, - 'Sl core':{'fa':[5.14010 ,7.97435 ,14.87405 ,0.00000],'fb':[6.29084 ,0.13765 ,2.36344 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.043 + 'As':{'Sl core':{'fa':[5.14010 ,7.97435 ,14.87405 ,0.00000],'fb':[6.29084 ,0.13765 ,2.36344 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.043 + 'Sl val':{'fa':[0.40744 ,0.78205 ,-4.04825 ,3.85818],'fb':[62.53153 ,27.43002 ,4.46306 ,4.29231],'fc':-0.00034,'Ne':5}, #Rw: 0.002 'Sl 4s':{'fa':[-41.29015 ,24.45513 ,0.98976 ,16.84341],'fb':[5.94816 ,5.58302 ,31.68084 ,6.57505],'fc':-0.00018,'Ne':2}, #Rw: 0.477 'Sl 4p':{'fa':[-5.85196 ,0.87602 ,5.69116 ,0.28495],'fb':[4.92462 ,36.80945 ,4.80787 ,81.52362],'fc':-0.00015,'Ne':3}, #Rw: 0.002 'Cu core':{'fa':[0.65354 ,6.15628 ,7.35219 ,13.19852],'fb':[11.72466 ,4.97954 ,0.15876 ,2.28484],'fc':0.63916,'Ne':1}, #Rw: 0.002 @@ -1035,30 +533,8 @@ ' 4p':{'fa':[0.82119 ,-0.93565 ,0.33577 ,0.77860],'fb':[36.69877 ,5.33300 ,83.24580 ,4.55235],'fc':-0.00009,'Ne':3}, #Rw: 0.084 ' 4p':{'fa':[2.22062 ,-1.67402 ,-0.18004 ,-0.36940],'fb':[13.00481 ,14.25381 ,6.24808 ,70.42919],'fc':0.00345,'Ne':1}, #Rw: 1.007 ' 4s,4p':{'fa':[0.12453 ,0.41803 ,-2.98518 ,2.43660],'fb':[1823.69878 ,120.63285 ,11.51502 ,10.27357],'fc':-0.00431,'Ne':1}}, #Rw: 2.143 - 'Se':{'ZSlater':4.358,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[35.3313,11.8468,15.1040,5.6608,5.6131,6.3009,2.4668,2.0349,], - 'popCore':[[2,], [2,6,], [2,6,10,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [2,4,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.02451301, 34.7523, 1], [-0.27177399, 29.3772, 2], [0.126003, 15.7401, 2], [0.18327899, 42.759, 3], [-0.007574, 11.7499, 3], - [0.007574, 8.024, 3], [-0.006158, 5.6568, 3], [-0.032946, 18.3255, 4], [0.003842, 3.4123, 4], [-0.003765, 2.2942, 4], - [0.001583, 1.5903, 4]], - '2s': [[-0.32926801, 34.6762, 1], [-0.14417399, 29.4177, 2], [0.965662, 15.7664, 2], [-0.023213, 42.792, 3], [0.142231, 11.7507, 3], - [-0.045026, 8.0224, 3], [0.035851, 5.6521, 3], [0.111786, 18.2994, 4], [0.008303, 3.4109, 4], [-0.009802, 2.2896, 4], - [-0.006888, 1.5845, 4]], - '3s': [[-0.13397001, 34.7126, 1], [-0.07783, 29.378, 2], [0.482685, 15.74, 2], [-0.008804, 42.7588, 3], [0.074439, 11.75, 3], - [-0.59407699, 8.0266, 3], [-0.61739802, 5.6672, 3], [0.13692699, 18.332, 4], [0.017172, 3.411, 4], [-0.001581, 2.2897, 4], - [-0.004863, 1.5845, 4]], - '4s': [[-0.037278, 34.7126, 1], [-0.021836, 29.3785, 2], [0.13731501, 15.7401, 2], [-0.002992, 42.7575, 3], [0.030725, 11.7494, 3], - [-0.20677599, 8.0222, 3], [-0.207936, 5.6629, 3], [0.039675, 18.3239, 4], [0.45256701, 3.4365, 4], [0.52383798, 2.2878, 4], - [0.165261, 1.5956, 4]], - '2p': [[0.093809, 35.6129, 2], [0.53705198, 13.3192, 2], [0.197864, 28.3057, 3], [0.200702, 10.1136, 3], [-0.25617799, 6.6989, 3], - [0.200396, 4.7411, 3], [0.151209, 23.878, 4], [-0.05504, 3.0447, 4], [-0.028165, 1.9337, 4], [0.049943, 1.2511, 4]], - '3p': [[-0.020365, 35.2189, 2], [-0.41787201, 13.3748, 2], [-0.025132, 28.955, 3], [0.16494299, 10.0638, 3], [0.66362602, 6.6768, 3], - [0.32516199, 4.7556, 3], [-0.017751, 23.4261, 4], [0.016608, 3.0297, 4], [-0.00382, 1.9223, 4], [-0.012141, 1.2452, 4]], - '4p': [[-0.00326, 35.2165, 2], [-0.117802, 13.3694, 2], [-0.00128, 28.9584, 3], [0.05537, 10.0639, 3], [0.178812, 6.6717, 3], - [0.083021, 4.7453, 3], [0.002822, 23.4245, 4], [-0.362786, 3.0363, 4], [-0.53575701, 1.9218, 4], [-0.24036001, 1.2462, 4]], - '3d': [[0.01794, 17.6943, 3], [0.24260999, 9.8026, 3], [0.44466001, 6.1155, 3], [0.36252001, 3.9304, 3], [0.04969, 2.4906, 3]],}, - 'Sl core':{'fa':[7.93060 ,15.14374 ,4.91490 ,0.00000],'fb':[0.12777 ,2.16052 ,5.51130 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.033 + 'Se':{'Sl core':{'fa':[7.93060 ,15.14374 ,4.91490 ,0.00000],'fb':[0.12777 ,2.16052 ,5.51130 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.033 + 'Sl val':{'fa':[0.37595 ,0.83032 ,1.82397 ,-2.03001],'fb':[56.39135 ,23.74850 ,3.74278 ,4.08992],'fc':-0.00060,'Ne':6}, #Rw: 0.265 'Sl 4s':{'fa':[0.47831 ,-7.74226 ,7.39977 ,0.86482],'fb':[34.24639 ,3.84435 ,3.72161 ,16.61899],'fc':-0.00094,'Ne':2}, #Rw: 0.004 'Sl 4p':{'fa':[0.34026 ,0.83927 ,1.33521 ,-1.51454],'fb':[65.14528 ,29.01337 ,4.00453 ,4.46722],'fc':-0.00038,'Ne':4}, #Rw: 0.221 'Cu core':{'fa':[0.65789 ,6.18816 ,7.40820 ,13.23947],'fb':[9.68710 ,4.31666 ,0.14279 ,2.07159],'fc':0.50637,'Ne':1}, #Rw: 0.001 @@ -1066,30 +542,8 @@ ' 4p':{'fa':[0.33526 ,-1.51331 ,0.84444 ,1.33375],'fb':[66.99555 ,4.54390 ,29.49512 ,4.07361],'fc':-0.00030,'Ne':4}, #Rw: 0.087 ' 4p':{'fa':[2.19012 ,-1.49241 ,-0.36786 ,-0.33367],'fb':[10.48764 ,12.05046 ,56.97851 ,5.97758],'fc':0.00439,'Ne':1}, #Rw: 1.031 ' 4s,4p':{'fa':[0.12738 ,0.42922 ,-3.14493 ,2.58177],'fb':[1426.11704 ,97.27476 ,9.74521 ,8.70099],'fc':-0.00525,'Ne':1}}, #Rw: 2.259 - 'Br':{'ZSlater':4.650,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[36.4987,12.2470,15.6176,5.8822,5.8479,6.6579,2.6266,2.2041,], - 'popCore':[[2,], [2,6,], [2,6,10,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [2,5,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.03017902, 35.7556, 1], [-0.293589, 30.2301, 2], [0.130206, 16.2822, 2], [0.195568, 43.9413, 3], [-0.014623, 12.1555, 3], - [0.011521, 8.3117, 3], [-0.00787, 5.9421, 3], [-0.026376, 18.9661, 4], [0.004103, 3.6177, 4], [-0.003698, 2.4376, 4], - [0.001466, 1.6995, 4]], - '2s': [[0.337565, 35.3339, 1], [0.124841, 30.6181, 2], [-0.93184203, 16.5502, 2], [0.030352, 44.3716, 3], [-0.18536399, 12.1605, 3], - [0.155084, 8.3182, 3], [-0.14165901, 5.9383, 3], [-0.13931, 18.7439, 4], [0.078292, 3.6222, 4], [-0.0282, 2.4359, 4], - [-0.006112, 1.7022, 4]], - '3s': [[-0.136214, 35.7152, 1], [-0.077873, 30.2307, 2], [0.48745999, 16.2817, 2], [-0.009457, 43.9398, 3], [0.080025, 12.1566, 3], - [-0.59521103, 8.32, 3], [-0.61796701, 5.9532, 3], [0.136694, 18.9692, 4], [0.010564, 3.617, 4], [0.002059, 2.4341, 4], - [-0.002996, 1.6936, 4]], - '4s': [[-0.04019, 35.7148, 1], [-0.023338, 30.2312, 2], [0.147507, 16.2817, 2], [-0.003234, 43.9396, 3], [0.029047, 12.1552, 3], - [-0.209626, 8.3107, 3], [-0.23587, 5.9515, 3], [0.044044, 18.9654, 4], [0.48224899, 3.6474, 4], [0.521658, 2.4302, 4], - [0.14524201, 1.7046, 4]], - '2p': [[0.090961, 37.3526, 2], [0.53498101, 13.8738, 2], [0.19929899, 29.1718, 3], [0.23327699, 11.02, 3], [-0.32401001, 7.172, 3], - [0.288753, 5.0943, 3], [0.135456, 25.0277, 4], [-0.16534901, 3.3186, 4], [0.116283, 2.0985, 4], [-0.036956, 1.3765, 4]], - '3p': [[-0.019855, 36.6754, 2], [-0.41750899, 14.0001, 2], [-0.023384, 30.1477, 3], [0.11915, 10.8979, 3], [0.68600899, 7.0982, 3], - [0.33678401, 5.0843, 3], [-0.020365, 24.3917, 4], [0.027158, 3.2746, 4], [-0.009518, 2.079, 4], [-0.020251, 1.3413, 4]], - '4p': [[-0.003774, 36.6717, 2], [-0.121997, 13.9929, 2], [-0.001805, 30.1518, 3], [0.044297, 10.897, 3], [0.196528, 7.0898, 3], - [0.102786, 5.0702, 3], [-0.000153, 24.39, 4], [-0.38091999, 3.2815, 4], [-0.53637201, 2.0815, 4], [-0.230395, 1.338, 4]], - '3d': [[0.01725, 18.498, 3], [0.24079999, 10.2724, 3], [0.45355999, 6.4369, 3], [0.35856, 4.1902, 3], [0.03957, 2.6395, 3]],}, - 'Sl core':{'fa':[4.71423 ,15.38528 ,7.89302 ,0.00000],'fb':[4.87517 ,1.98002 ,0.11882 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.024 + 'Br':{'Sl core':{'fa':[4.71423 ,15.38528 ,7.89302 ,0.00000],'fb':[4.87517 ,1.98002 ,0.11882 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.024 + 'Sl val':{'fa':[0.37576 ,0.84930 ,-2.35087 ,2.12633],'fb':[48.46141 ,20.41748 ,3.68323 ,3.38636],'fc':-0.00089,'Ne':7}, #Rw: 0.244 'Sl 4s':{'fa':[0.87747 ,-5.78561 ,5.58988 ,0.31849],'fb':[24.19127 ,6.46280 ,7.09871 ,2.66164],'fc':-0.00131,'Ne':2}, #Rw: 0.352 'Sl 4p':{'fa':[1.79208 ,0.35056 ,0.84810 ,-1.99033],'fb':[3.57417 ,54.44600 ,23.96286 ,3.91640],'fc':-0.00064,'Ne':5}, #Rw: 0.207 'Cu core':{'fa':[0.64144 ,6.18991 ,7.47211 ,13.32565],'fb':[8.27530 ,3.80474 ,0.12879 ,1.88915],'fc':0.37080,'Ne':1}, #Rw: 0.001 @@ -1097,724 +551,147 @@ ' 4p':{'fa':[0.33635 ,-1.84144 ,0.86393 ,1.64157],'fb':[55.50444 ,4.00078 ,24.42156 ,3.62134],'fc':-0.00057,'Ne':5}, #Rw: 0.083 ' 4p':{'fa':[2.44966 ,-0.41420 ,-2.04013 ,0.00000],'fb':[5.88307 ,44.13080 ,5.33806 ,0.00000],'fc':0.00573,'Ne':1}, #Rw: 1.164 ' 4s,4p':{'fa':[0.12982 ,0.43904 ,-3.25600 ,2.68035],'fb':[1152.52491 ,80.58437 ,8.43699 ,7.53224],'fc':-0.00621,'Ne':1}}, #Rw: 2.348 - 'Kr':{'ZSlater':4.735,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[37.6775,12.6493,16.1326,6.1058,6.0844,7.0102,2.7835,2.3673], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.11025095, 35.251, 1], [-0.72533202, 29.7431, 2], [0.75332999, 16.6815, 2], [0.208369, 51.2273, 3], [0.31870401, 12.6688, 3], - [-0.15484899, 8.7877, 3], [0.06087, 6.645, 3], [-0.65847301, 19.8471, 4], [-0.012872, 4.1495, 4], [0.011925, 2.9046, 4], - [-0.005655, 2.4027, 4]], - '2s': [[-0.3348, 36.7138, 1], [-0.13858999, 31.1194, 2], [0.97337002, 16.7616, 2], [-0.02571, 45.1229, 3], [0.13170999, 12.5174, 3], - [-0.02531, 8.5716, 3], [0.01549, 6.217, 3], [0.10812, 19.5943, 4], [0.01341, 3.8389, 4], [0.00164, 2.5911, 4], [-0.00123, 1.81, 4]], - '3s': [[0.13852, 36.721, 1], [0.07652, 31.1113, 2], [-0.49463999, 16.7541, 2], [0.01047, 45.1136, 3], [-0.0784, 12.5185, 3], - [0.59852999, 8.5821, 3], [0.61365998, 6.2324, 3], [-0.13605, 19.6037, 4], [-0.00774, 3.8392, 4], [-0.00185, 2.5912, 4], [0.00189, 1.81, 4]], - '4s': [[0.042837, 36.7206, 1], [0.024373, 31.1117, 2], [-0.157463, 16.754, 2], [0.0035, 45.1135, 3], [-0.026124, 12.517, 3], - [0.21197499, 8.5712, 3], [0.26099801, 6.2314, 3], [-0.047397, 19.6009, 4], [-0.500687, 3.8732, 4], [-0.521294, 2.5857, 4], - [-0.135649, 1.821, 4]], - '2p': [[0.06819, 38.0286, 2], [0.74269003, 14.6963, 2], [0.11613, 31.3127, 3], [0.02472, 11.7832, 3], [0.00999, 7.4972, 3], - [0.01511, 5.3888, 3], [0.08668, 25.3645, 4], [0.00474, 3.5216, 4], [0.00114, 2.2424, 4], [0.00022, 1.4475, 4]], - '3p': [[-0.015273, 38.0299, 2], [-0.459939, 14.7013, 2], [-0.009335, 31.3134, 3], [0.106715, 11.7864, 3], [0.67497402, 7.4958, 3], - [0.37768799, 5.4036, 3], [-0.005278, 25.3619, 4], [0.000313, 3.5219, 4], [-0.007226, 2.2426, 4], [-0.012502, 1.4475, 4]], - '4p': [[0.001045, 38.0283, 2], [-0.15307599, 14.6935, 2], [0.007595, 31.3136, 3], [0.045489, 11.7836, 3], [0.1752, 7.4979, 3], - [0.12909999, 5.389, 3], [0.015016, 25.3637, 4], [-0.39124101, 3.5241, 4], [-0.53603601, 2.2405, 4], [-0.230092, 1.4431, 4]], - '3d': [[0.01714, 19.1363, 3], [0.24354, 10.6902, 3], [0.46776, 6.6981, 3], [0.34347999, 4.4211, 3], [0.03147, 2.7615, 3]],}, - }, - 'Rb':{'ZSlater':2.175,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[38.8707,13.0618,16.6508,6.3329,6.3238,7.3638,2.9696,2.5957,0.0000,0.0000,1.0875,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.04325604, 37.6989, 1], [-0.31936699, 31.9402, 2], [0.109639, 16.8623, 2], [0.220338, 45.355, 3], [-0.013767, 12.0922, 3], - [-0.00797, 6.6453, 3], [-0.012832, 19.9153, 4], [0.00278, 3.9731, 4], [-0.001562, 2.6092, 4], [0.008905, 11.5427, 5], - [0.000129, 1.7329, 5], [0.000547, 1.0914, 5], [-0.000411, 0.73, 5]], - '2s': [[0.33998799, 37.535, 1], [0.128121, 32.0611, 2], [-1.00920999, 16.9422, 2], [0.028603, 45.4964, 3], [-0.091471, 12.0947, 3], - [-0.14872199, 6.638, 3], [-0.080457, 19.8636, 4], [0.042124, 3.9726, 4], [0.00728, 2.6075, 4], [0.116138, 11.5591, 5], - [-0.022561, 1.7306, 5], [-0.0035, 1.0854, 5], [0.000608, 0.7168, 5]], - '3s': [[-0.14101, 37.66, 1], [-0.07964, 31.94, 2], [0.54568398, 16.8625, 2], [-0.010882, 45.3544, 3], [-0.279834, 12.0894, 3], - [-0.75223202, 6.668, 3], [0.17225499, 19.9214, 4], [0.014719, 3.9723, 4], [-0.010468, 2.6063, 4], [-0.20576701, 11.5462, 5], [-0.003863, 1.7303, 5], [0.000257, 1.0853, 5], [5e-06, 0.7168, 5]], - '4s': [[0.046691, 37.6598, 1], [0.027137, 31.9402, 2], [-0.18756799, 16.8618, 2], [0.004372, 45.3535, 3], [0.103355, 12.0914, 3], - [0.333846, 6.6605, 3], [-0.062626, 19.9158, 4], [-0.58580297, 4.0134, 4], [-0.55632401, 2.6058, 4], [0.081769, 11.5413, 5], - [-0.012952, 1.7312, 5], [0.000267, 1.0855, 5], [-0.00688, 0.7168, 5]], - '5s': [[0.009714, 37.6597, 1], [0.006543, 31.9404, 2], [-0.040294, 16.8617, 2], [0.000352, 45.3535, 3], [0.023399, 12.0916, 3], - [0.075921, 6.6432, 3], [-0.014294, 19.9148, 4], [-0.156902, 3.9733, 4], [-0.09715, 2.6086, 4], [0.017219, 11.5421, 5], [0.18518899, 1.7322, 5], [0.54933399, 1.096, 5], [0.391298, 0.7247, 5]], - '2p': [[0.079956, 40.4901, 2], [0.53852999, 15.5864, 2], [0.15845101, 32.9741, 3], [0.144348, 12.7344, 3], [0.02706, 7.9211, 3], - [-0.071006, 5.7158, 3], [0.16668101, 26.874, 4], [0.067288, 3.7753, 4], [-0.006812, 2.43, 4], [-0.024994, 1.6133, 4]], - '3p': [[-0.01513, 40.4129, 2], [-0.43366399, 15.5582, 2], [-0.011124, 33.1335, 3], [0.045963, 12.7474, 3], [0.70163798, 7.9337, 3], - [0.37792301, 5.7238, 3], [-0.01378, 26.7288, 4], [0.02719, 3.7731, 4], [-0.039328, 2.4256, 4], [0.002448, 1.6099, 4]], - '4p': [[-0.000372, 40.4071, 2], [-0.162149, 15.5468, 2], [0.008156, 33.1346, 3], [0.03721, 12.745, 3], [0.20291901, 7.9156, 3], - [0.15350799, 5.7137, 3], [0.011401, 26.7293, 4], [-0.428734, 3.7827, 4], [-0.57823199, 2.4243, 4], [-0.14342199, 1.6088, 4]], - '3d': [[0.016172, 19.9727, 3], [0.231434, 11.2418, 3], [0.44955501, 7.1726, 3], [0.35728201, 4.8534, 3], [0.042824, 3.1998, 3]],}, - 'Sl core':{'fa':[10.03855 ,17.95727 ,7.93606 ,0.00000],'fb':[18.58664 ,1.75144 ,0.10811 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.170 + 'Rb':{'Sl core':{'fa':[10.03855 ,17.95727 ,7.93606 ,0.00000],'fb':[18.58664 ,1.75144 ,0.10811 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.170 + 'Sl val':{'fa':[-5.88625 ,0.28275 ,5.67112 ,0.93231],'fb':[30.22264 ,340.10348 ,29.42748 ,186.13580],'fc':0.00008,'Ne':1}, #Rw: 0.487 'Sl 5s':{'fa':[-5.88625 ,0.28275 ,5.67112 ,0.93231],'fb':[30.22264 ,340.10348 ,29.42748 ,186.13580],'fc':0.00008,'Ne':1}, #Rw: 0.487 }, - 'Sr':{'ZSlater':2.562,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[40.0738,13.4688,17.1696,6.5611,6.5643,7.7125,3.1546,2.8088,0.0000,0.0000,1.2812], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.04881096, 38.7186, 1], [-0.349558, 32.7624, 2], [0.124469, 17.6646, 2], [0.233799, 46.8251, 3], [-0.018203, 12.7986, 3], - [-0.006223, 6.9769, 3], [-0.011841, 20.6867, 4], [0.001117, 4.2141, 4], [-3.5e-05, 2.8515, 4], [0.011188, 12.1089, 5], - [-0.000232, 1.8502, 5], [-7e-05, 1.22, 5], [0.000327, 0.8313, 5]], - '2s': [[0.34144601, 38.6679, 1], [0.132625, 32.7711, 2], [-0.99549901, 17.6732, 2], [0.028334, 46.8363, 3], [-0.093221, 12.7984, 3], - [-0.010374, 6.9745, 3], [-0.111538, 20.6799, 4], [0.003032, 4.213, 4], [0.000645, 2.8498, 4], [0.004517, 12.1093, 5], - [0.002184, 1.8495, 5], [-0.004787, 1.2153, 5], [0.003045, 0.8192, 5]], - '3s': [[0.14328299, 38.6771, 1], [0.081027, 32.7617, 2], [-0.53907698, 17.6643, 2], [0.011907, 46.8232, 3], [0.245306, 12.7945, 3], - [0.77714002, 7.0016, 3], [-0.180237, 20.6897, 4], [-0.008028, 4.213, 4], [0.006305, 2.8493, 4], [0.20475499, 12.1177, 5], - [0.004096, 1.8492, 5], [0.000261, 1.2152, 5], [0.000196, 0.8188, 5]], - '4s': [[0.05089, 38.6767, 1], [0.029497, 32.7621, 2], [-0.199139, 17.6639, 2], [0.005324, 46.8232, 3], [0.092348, 12.7973, 3], - [0.38015699, 6.9936, 3], [-0.06817, 20.6864, 4], [-0.605483, 4.2476, 4], [-0.55728698, 2.8562, 4], [0.08665, 12.1093, 5], - [0.002322, 1.8496, 5], [0.030962, 1.2144, 5], [0.013812, 0.8187, 5]], - '5s': [[0.010601, 38.6766, 1], [0.006358, 32.7622, 2], [-0.041413, 17.6638, 2], [0.000815, 46.8232, 3], [0.020172, 12.7979, 3], - [0.087921, 6.9762, 3], [-0.015821, 20.6858, 4], [-0.1709, 4.2203, 4], [-0.095405, 2.8543, 4], [0.015242, 12.1086, 5], - [0.30113301, 1.8599, 5], [0.55019802, 1.2246, 5], [0.28394201, 0.8268, 5]], - '2p': [[0.07188, 44.6427, 2], [0.51129502, 16.3371, 2], [0.178619, 33.1864, 3], [0.27504501, 14.8333, 3], - [-0.208087, 8.8157, 3], [0.22102199, 6.4446, 3], [0.096776, 29.0278, 4], [-0.158998, 4.5618, 4], - [0.13345499, 3.0554, 4], [-0.056174, 2.0758, 4]], - '3p': [[-0.009296, 42.7313, 2], [-0.455603, 16.6544, 2], [0.004991, 35.0193, 3], [0.016276, 13.8107, 3], [0.70142102, 8.2947, 3], - [0.39078599, 6.0211, 3], [0.001022, 28.1641, 4], [0.019306, 4.0095, 4], [-0.012389, 2.6166, 4], [-0.017918, 1.686, 4]], - '4p': [[0.003273, 42.7301, 2], [-0.202572, 16.6393, 2], [0.018993, 35.0113, 3], [0.033857, 13.8074, 3], [0.22456101, 8.2949, 3], - [0.180106, 6.0019, 3], [0.025219, 28.1754, 4], [-0.45709801, 4.0179, 4], [-0.60468698, 2.6161, 4], [-0.090184, 1.6853, 4]], - '3d': [[0.015079, 20.7716, 3], [0.222012, 11.8003, 3], [0.43852201, 7.598, 3], [0.35858601, 5.283, 3], [0.058104, 3.5968, 3]],}, - 'Sl core':{'fa':[199.85352 ,5.07720 ,-189.71537 ,16.63877],'fb':[15.62013 ,0.35743 ,15.62005 ,1.66991],'fc':4.10277,'Ne':1}, #Rw: 0.105 + 'Sr':{'Sl core':{'fa':[199.85352 ,5.07720 ,-189.71537 ,16.63877],'fb':[15.62013 ,0.35743 ,15.62005 ,1.66991],'fc':4.10277,'Ne':1}, #Rw: 0.105 + 'Sl val':{'fa':[0.56900 ,0.75374 ,-67.35923 ,67.03614],'fb':[195.69513 ,100.33103 ,23.54796 ,23.48020],'fc':0.00008,'Ne':2}, #Rw: 0.487 'Sl 5s':{'fa':[0.56900 ,0.75374 ,-67.35923 ,67.03614],'fb':[195.69513 ,100.33103 ,23.54796 ,23.48020],'fc':0.00008,'Ne':2}, #Rw: 0.487 }, - 'Y' :{'ZSlater':3.165,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[41.2899,13.8783,17.6908,6.7911,6.8059,8.0646,3.3279,2.9940,1.9533,0.0000,1.3970,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,1,0,], [2,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.05388498, 39.7691, 1], [-0.38892299, 33.6024, 2], [0.15163399, 18.3803, 2], [0.248671, 48.7468, 3], [-0.014399, 13.3457, 3], - [-0.006016, 7.2859, 3], [-0.017611, 21.3832, 4], [0.002006, 4.4496, 4], [-0.001162, 3.0499, 4], [0.006913, 12.5614, 5], - [0.00135, 2.006, 5], [-0.001625, 1.319, 5], [0.00083, 0.8886, 5]], - '2s': [[0.345781, 39.5955, 1], [0.124826, 33.7106, 2], [-0.97110701, 18.4595, 2], [0.03191, 48.9202, 3], [-0.088263, 13.3468, 3], - [-0.007108, 7.2929, 3], [-0.14196999, 21.3156, 4], [0.010375, 4.4587, 4], [-0.01379, 3.0577, 4], [-0.00226, 12.5787, 5], - [0.011418, 2.0114, 5], [-0.010684, 1.3169, 5], [0.005121, 0.8876, 5]], - '3s': [[0.145312, 39.7228, 1], [0.081255, 33.6021, 2], [-0.53525198, 18.3796, 2], [0.012552, 48.745, 3], [0.23158801, 13.3421, 3], - [0.79937702, 7.3127, 3], [-0.190543, 21.3892, 4], [-0.013488, 4.4491, 4], [0.013364, 3.0476, 4], [0.196013, 12.5669, 5], - [0.0039, 2.0044, 5], [-0.000965, 1.3112, 5], [-0.000289, 0.8747, 5]], - '4s': [[0.053925, 39.7225, 1], [0.030695, 33.6027, 2], [-0.206424, 18.3794, 2], [0.006062, 48.744, 3], [0.085838, 13.344, 3], - [0.41641399, 7.3054, 3], [-0.073362, 21.3828, 4], [-0.621512, 4.4814, 4], [-0.55342901, 3.0607, 4], [0.086105, 12.5622, 5], - [0.006818, 2.0047, 5], [0.034593, 1.3101, 5], [0.016925, 0.8746, 5]], - '5s': [[0.011059, 39.7224, 1], [0.006634, 33.6027, 2], [-0.041916, 18.3793, 2], [0.000694, 48.744, 3], [0.019068, 13.3445, 3], - [0.097368, 7.2858, 3], [-0.01777, 21.3821, 4], [-0.178295, 4.4581, 4], [-0.09347, 3.0531, 4], [0.012632, 12.5609, 5], - [0.31198499, 2.0163, 5], [0.54854602, 1.324, 5], [0.28105801, 0.8873, 5]], - '2p': [[0.089632, 45.9529, 2], [0.26703, 17.8911, 2], [0.206001, 36.0505, 3], [0.46478501, 15.0467, 3], [-0.35560101, 8.9348, 3], - [0.32417899, 6.4908, 3], [0.198213, 30.7888, 4], [-0.20237701, 4.3578, 4], [0.16046099, 2.8917, 4], [-0.061475, 1.8916, 4]], - '3p': [[-0.003059, 45.1957, 2], [-0.47530001, 17.9107, 2], [0.021941, 37.1672, 3], [-0.021625, 14.7947, 3], [0.65667599, 8.7808, 3], - [0.45148599, 6.4139, 3], [0.018549, 29.901, 4], [0.011943, 4.2356, 4], [-0.003468, 2.7966, 4], [-0.018659, 1.7852, 4]], - '4p': [[0.001694, 45.1958, 2], [-0.181714, 17.8993, 2], [0.015048, 37.1597, 3], [0.004893, 14.7949, 3], [0.230543, 8.782, 3], - [0.21064401, 6.3995, 3], [0.015779, 29.9103, 4], [-0.47488701, 4.2461, 4], [-0.601605, 2.804, 4], [-0.079858, 1.7814, 4]], - '3d': [[0.01693, 20.9996, 3], [0.25606701, 11.9042, 3], [0.59863502, 7.2908, 3], [0.132136, 4.6, 3], [0.085476, 5.7894, 4], - [0.006149, 2.7134, 4], [-0.0027, 1.6373, 4], [-0.000938, 0.9993, 4]], - '4d': [[-0.003871, 20.9991, 3], [-0.054268, 11.9066, 3], [-0.23890699, 7.2893, 3], [0.50904602, 4.6093, 3], [-0.36274999, 5.784, 4], - [0.38647801, 2.7106, 4], [0.473479, 1.6284, 4], [0.233927, 0.9822, 4]],}, - 'Sl core':{'fa':[10.27636 ,16.61965 ,4.86820 ,0.00000],'fb':[13.54412 ,1.50839 ,0.34661 ,0.00000],'fc':4.19740,'Ne':1}, #Rw: 0.092 + 'Y' :{'Sl core':{'fa':[10.27636 ,16.61965 ,4.86820 ,0.00000],'fb':[13.54412 ,1.50839 ,0.34661 ,0.00000],'fc':4.19740,'Ne':1}, #Rw: 0.092 + 'Sl val':{'fa':[0.30697 ,0.87588 ,-0.02182 ,-0.16271],'fb':[194.02499 ,84.64480 ,276.80058 ,55.42502],'fc':0.00161,'Ne':3}, #Rw: 0.793 'Sl 4d':{'fa':[-0.13901 ,0.05322 ,0.41048 ,0.67531],'fb':[4.62976 ,1.46619 ,86.35381 ,30.85942],'fc':-0.00078,'Ne':1}, #Rw: 0.136 'Sl 5s':{'fa':[-35.77577 ,0.60251 ,0.73945 ,35.43338],'fb':[20.38169 ,165.38795 ,81.72335 ,20.26832],'fc':0.00009,'Ne':2}, #Rw: 0.482 'Sr core':{'fa':[2.56710 ,9.95642 ,5.68260 ,17.83816],'fb':[122.81298 ,12.76505 ,0.13234 ,1.42397],'fc':1.94349,'Ne':1}, #Rw: 0.031 ' 4d':{'fa':[0.36687 ,0.71200 ,-0.19835 ,0.11944],'fb':[84.65773 ,31.28453 ,3.54875 ,1.93591],'fc':-0.00050,'Ne':1}, #Rw: 0.220 ' 4d':{'fa':[-0.31812 ,0.36210 ,-0.05049 ,0.01848],'fb':[71.82890 ,8.13387 ,2.22585 ,0.06758],'fc':-0.01048,'Ne':1}, #Rw: 0.787 ' 4d':{'fa':[0.03150 ,-0.20956 ,0.19047 ,-0.01568],'fb':[134.66583 ,23.34489 ,3.62317 ,0.78031],'fc':0.00329,'Ne':1}}, #Rw: 0.150 - 'Zr':{'ZSlater':3.703,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[42.5194,14.2903,18.2138,7.0225,7.0486,8.4084,3.4949,3.1677,2.2153,0.0000,1.4872,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,2,0,], [2,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.05899894, 40.8444, 1], [-0.44193599, 34.4622, 2], [0.232409, 19.1705, 2], [0.25365099, 51.1786, 3], [-0.057816, 14.0071, 3], - [-0.027884, 7.6009, 3], [-0.040285, 22.139, 4], [0.008092, 4.6607, 4], [-0.005428, 3.2114, 4], [0.03982, 13.0608, 5], - [0.002932, 2.1229, 5], [-0.002362, 1.3842, 5], [0.001103, 0.9253, 5]], - '2s': [[0.34858701, 40.6609, 1], [0.118694, 34.5574, 2], [-0.94743001, 19.2479, 2], [0.035434, 51.3766, 3], [-0.089739, 14.0075, 3], - [-0.001659, 7.6081, 3], [-0.165766, 22.0611, 4], [0.007872, 4.6693, 4], [-0.011423, 3.2186, 4], [-0.008506, 13.0762, 5], - [0.008912, 2.1281, 5], [-0.007569, 1.3829, 5], [0.003109, 0.925, 5]], - '3s': [[-0.147356, 40.7886, 1], [-0.080441, 34.4606, 2], [0.527291, 19.1698, 2], [-0.013682, 51.1804, 3], [-0.21181101, 14.005, 3], - [-0.82458103, 7.6325, 3], [0.202731, 22.1509, 4], [0.019715, 4.6598, 4], [-0.024756, 3.2074, 4], [-0.18814699, 13.0598, 5], - [0.007639, 2.1201, 5], [0.00666, 1.3752, 5], [0.001529, 0.9098, 5]], - '4s': [[0.056265, 40.7893, 1], [0.031571, 34.4607, 2], [-0.20872401, 19.1696, 2], [0.006154, 51.176, 3], [0.077703, 14.0052, 3], - [0.45699701, 7.622, 3], [-0.081858, 22.139, 4], [-0.64961803, 4.7033, 4], [-0.53458297, 3.2162, 4], [0.078579, 13.0609, 5], - [0.003244, 2.1207, 5], [0.031768, 1.3747, 5], [0.010856, 0.9099, 5]], - '5s': [[0.012563, 40.7891, 1], [0.007353, 34.4609, 2], [-0.046206, 19.1696, 2], [0.000851, 51.176, 3], [0.0189, 14.0055, 3], - [0.114845, 7.5994, 3], [-0.021148, 22.1375, 4], [-0.202325, 4.6661, 4], [-0.094318, 3.2135, 4], [0.012549, 13.0608, 5], - [0.325679, 2.1338, 5], [0.54673398, 1.393, 5], [0.27353901, 0.9291, 5]], - '2p': [[0.069271, 47.5284, 2], [0.418749, 18.9998, 2], [0.136324, 38.6648, 3], [0.36907601, 15.5588, 3], [-0.19842, 9.2224, 3], - [0.17080601, 6.7509, 3], [0.16663601, 31.9215, 4], [-0.091163, 4.487, 4], [0.06832, 2.9895, 4], [-0.026212, 1.9255, 4]], - '3p': [[-0.007093, 47.2923, 2], [-0.413472, 18.9349, 2], [0.009965, 39.081, 3], [-0.079337, 15.5771, 3], [0.66087699, 9.2258, 3], - [0.45984501, 6.7489, 3], [-0.001485, 31.4713, 4], [0.039823, 4.4678, 4], [-0.047544, 2.9671, 4], [0.022665, 1.8958, 4]], - '4p': [[-0.000329, 47.2892, 2], [-0.161761, 18.9143, 2], [0.009604, 39.0634, 3], [-0.016553, 15.5796, 3], [0.23830099, 9.1963, 3], - [0.23346099, 6.7489, 3], [0.006355, 31.483, 4], [-0.49094999, 4.4693, 4], [-0.593086, 2.9847, 4], [-0.077747, 1.8887, 4]], - '3d': [[0.017622, 21.6087, 3], [0.395917, 11.8522, 3], [0.361756, 5.6298, 3], [0.31061, 9.1569, 4], [0.021375, 4.1293, 4], - [-0.005062, 2.6333, 4], [0.001389, 1.6878, 4], [0.001935, 1.1008, 4]], - '4d': [[0.003968, 21.6044, 3], [0.095607, 11.8738, 3], [0.086759, 5.6327, 3], [0.06692, 9.1444, 4], [-0.198998, 4.1339, 4], - [-0.41931999, 2.6339, 4], [-0.39465699, 1.6746, 4], [-0.17902701, 1.0646, 4]],}, - 'Sl core':{'fa':[10.34750 ,16.26830 ,4.91183 ,0.00000],'fb':[12.00824 ,1.39113 ,0.37982 ,0.00000],'fc':4.43826,'Ne':1}, #Rw: 0.083 + 'Zr':{'Sl core':{'fa':[10.34750 ,16.26830 ,4.91183 ,0.00000],'fb':[12.00824 ,1.39113 ,0.37982 ,0.00000],'fc':4.43826,'Ne':1}, #Rw: 0.083 + 'Sl val':{'fa':[1.23999 ,-2.25717 ,2.01285 ,0.31509],'fb':[103.74526 ,71.08276 ,58.99636 ,0.00212],'fc':-0.31170,'Ne':4}, #Rw: 1.613 'Sl 4d':{'fa':[0.38519 ,0.71891 ,0.06964 ,-0.17342],'fb':[67.82982 ,24.40629 ,1.34960 ,3.99235],'fc':-0.00102,'Ne':2}, #Rw: 0.120 'Sl 5s':{'fa':[17.55957 ,-17.93609 ,0.67024 ,0.70567],'fb':[18.33209 ,18.55956 ,143.79248 ,66.84719],'fc':0.00017,'Ne':2}, #Rw: 0.558 'Sr core':{'fa':[2.53489 ,10.08001 ,6.57468 ,18.04459],'fb':[111.36183 ,11.36447 ,0.08886 ,1.29004],'fc':0.75302,'Ne':1}, #Rw: 0.031 ' 4d':{'fa':[0.34915 ,0.75515 ,-0.19991 ,0.09605],'fb':[65.15008 ,24.25139 ,3.58341 ,1.51198],'fc':-0.00088,'Ne':2}, #Rw: 0.076 ' 4d':{'fa':[0.43405 ,-0.33089 ,-0.10980 ,0.02641],'fb':[6.05586 ,53.35500 ,2.59432 ,0.02915],'fc':-0.01828,'Ne':1}, #Rw: 0.812 ' 4d':{'fa':[0.03338 ,-0.22777 ,0.21405 ,-0.02366],'fb':[97.60890 ,17.05485 ,2.96539 ,0.77443],'fc':0.00399,'Ne':1}}, #Rw: 0.109 - 'Nb':{'ZSlater':4.346,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[43.7627,14.7046,18.7389,7.2552,7.2919,8.7498,3.6505,3.3213,2.3384,0.0000,1.5115,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,4,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.06601906, 41.7948, 1], [-0.44221899, 35.3344, 2], [0.202903, 19.4802, 2], [0.265468, 51.3735, 3], [-0.051511, 14.2974, 3], - [-0.016833, 7.8731, 3], [-0.034835, 22.7007, 4], [0.002297, 4.884, 4], [-0.000359, 3.3385, 4], [0.028408, 13.3486, 5], - [-0.000364, 2.2478, 5], [0.000309, 1.4396, 5], [-5.3e-05, 0.952, 5]], - '2s': [[0.35305101, 41.5343, 1], [0.112281, 35.4895, 2], [-0.961528, 19.6037, 2], [0.035906, 51.7121, 3], [-0.083205, 14.299, 3], - [-0.002322, 7.894, 3], [-0.15426099, 22.5924, 4], [0.008371, 4.9048, 4], [-0.011712, 3.3584, 4], [-0.007705, 13.3807, 5], - [0.008913, 2.2673, 5], [-0.00798, 1.4539, 5], [0.004026, 0.987, 5]], - '3s': [[0.14968801, 41.7414, 1], [0.078854, 35.3338, 2], [-0.54163802, 19.4805, 2], [0.014073, 51.3795, 3], [0.22158501, 14.2968, 3], - [0.82052302, 7.9092, 3], [-0.19558799, 22.7154, 4], [-0.02308, 4.8837, 4], [0.031629, 3.335, 4], [0.188538, 13.3417, 5], - [-0.01751, 2.2447, 5], [-0.004605, 1.4325, 5], [0.002024, 0.9392, 5]], - '4s': [[0.057625, 41.7436, 1], [0.031302, 35.3323, 2], [-0.21604501, 19.4788, 2], [0.006257, 51.3714, 3], [0.077996, 14.2958, 3], - [0.47772801, 7.9008, 3], [-0.079843, 22.7017, 4], [-0.66015899, 4.9414, 4], [-0.524445, 3.3338, 4], [0.073242, 13.3464, 5], - [-0.008162, 2.2462, 5], [0.004537, 1.4329, 5], [-0.007141, 0.9392, 5]], - '5s': [[0.014927, 41.7434, 1], [0.008425, 35.3327, 2], [-0.056973, 19.4788, 2], [0.001492, 51.3713, 3], [0.020648, 14.2958, 3], - [0.13879301, 7.8722, 3], [-0.022023, 22.699, 4], [-0.237547, 4.8843, 4], [-0.11043, 3.3408, 4], [0.017396, 13.3479, 5], - [0.30544499, 2.2526, 5], [0.55851197, 1.457, 5], [0.28284699, 0.9654, 5]], - '2p': [[0.069445, 50.2343, 2], [0.388868, 19.6386, 2], [0.14421999, 39.6227, 3], [0.42331299, 16.5499, 3], [-0.26842099, 9.7266, 3], - [0.253093, 7.2057, 3], [0.14891399, 33.8455, 4], [-0.157305, 4.8876, 4], [0.131613, 3.2656, 4], [-0.055201, 2.1718, 4]], - '3p': [[-0.002328, 49.4284, 2], [-0.456182, 19.6433, 2], [0.023924, 40.7976, 3], [-0.066468, 16.1916, 3], [0.62932998, 9.5291, 3], - [0.491023, 7.0739, 3], [0.017022, 32.813, 4], [0.010738, 4.6929, 4], [0.004429, 3.1133, 4], [-0.018977, 2.0121, 4]], - '4p': [[-0.000627, 49.4285, 2], [-0.161773, 19.6352, 2], [0.009094, 40.7925, 3], [-0.022409, 16.1933, 3], [0.23887099, 9.53, 3], - [0.25208101, 7.0611, 3], [0.005275, 32.8187, 4], [-0.507191, 4.6977, 4], [-0.575194, 3.1305, 4], [-0.089939, 2.0055, 4]], - '3d': [[0.018617, 22.2158, 3], [0.40931299, 12.1366, 3], [0.35798499, 5.7858, 3], [-0.002242, 3.4032, 3], [0.31029701, 9.422, 4], - [0.007709, 2.5967, 4], [0.004785, 1.6525, 4], [-0.006476, 1.0606, 4]], - '4d': [[0.004077, 22.1886, 3], [0.098351, 12.2183, 3], [0.15644599, 5.785, 3], [-0.33578101, 3.4014, 3], [0.064804, 9.3961, 4], - [-0.387842, 2.5982, 4], [-0.37524799, 1.6479, 4], [-0.15288199, 1.0491, 4]],}, - 'Sl core':{'fa':[18.08024 ,10.43160 ,7.44763 ,0.00000],'fb':[1.20008 ,10.92801 ,0.07458 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.096 + 'Nb':{'Sl core':{'fa':[18.08024 ,10.43160 ,7.44763 ,0.00000],'fb':[1.20008 ,10.92801 ,0.07458 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.096 + 'Sl val':{'fa':[0.48761 ,0.56641 ,-0.47245 ,0.41730],'fb':[86.23508 ,23.67912 ,2.09274 ,1.76826],'fc':-0.00071,'Ne':5}, #Rw: 0.314 'Sl 4d':{'fa':[0.70564 ,-0.18385 ,0.39310 ,0.08519],'fb':[21.73081 ,3.31406 ,63.43230 ,1.32312],'fc':-0.00093,'Ne':4}, #Rw: 0.145 'Sl 5s':{'fa':[1.16538 ,28.17017 ,-9.72435 ,-18.61602],'fb':[105.80207 ,8.88448 ,8.13977 ,9.39941],'fc':0.00104,'Ne':1}, #Rw: 0.736 'Rb core':{'fa':[10.18493 ,1.31452 ,4.27912 ,17.58867],'fb':[10.75088 ,112.98729 ,0.21196 ,1.22771],'fc':3.62223,'Ne':1}, #Rw: 0.031 ' 4d':{'fa':[0.34978 ,0.73893 ,-0.35951 ,0.27074],'fb':[66.54201 ,22.23465 ,2.46767 ,1.75400],'fc':-0.00060,'Ne':4}, #Rw: 0.112 ' 4d':{'fa':[0.99977 ,-0.30292 ,-0.70120 ,0.03691],'fb':[4.26286 ,54.18106 ,3.48839 ,0.00232],'fc':-0.03063,'Ne':1}, #Rw: 1.049 ' 4d':{'fa':[0.02358 ,-0.19976 ,0.22273 ,-0.04976],'fb':[118.02129 ,16.09825 ,2.33986 ,1.04530],'fc':0.00324,'Ne':1}}, #Rw: 0.216 - 'Nb+3':{'ZSlater':4.346,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[43.7627,14.7046,18.7389,7.2552,7.2919,8.7498,3.6505,3.3213,2.3384,0.0000,1.5115,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,4,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[-3.32181 ,18.21918 ,9.10807 ,13.85651],'fb':[10.77886 ,1.19754 ,0.05538 ,10.77534],'fc':-1.87713,'Ne':1}, #Rw: 0.048 + 'Nb+3':{'Kr core':{'fa':[-3.32181 ,18.21918 ,9.10807 ,13.85651],'fb':[10.77886 ,1.19754 ,0.05538 ,10.77534],'fc':-1.87713,'Ne':1}, #Rw: 0.048 ' 4d':{'fa':[0.36095 ,0.82830 ,-0.28354 ,0.09622],'fb':[33.20803 ,16.57369 ,3.79953 ,1.14511],'fc':-0.00201,'Ne':2}, #Rw: 0.013 ' 4d':{'fa':[0.52959 ,-0.43516 ,-0.10971 ,0.07461],'fb':[5.27135 ,28.35811 ,1.74426 ,0.02755],'fc':-0.05890,'Ne':1}, #Rw: 0.256 ' 4d':{'fa':[0.08172 ,-0.34466 ,0.29224 ,-0.03536],'fb':[38.17722 ,11.15266 ,2.53607 ,0.67694],'fc':0.00605,'Ne':1}}, #Rw: 0.042 - 'Mo':{'ZSlater':4.767,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[45.0201,15.1215,19.2657,7.4893,7.5360,9.0903,3.8108,3.4841,2.5449,0.0000,1.5757,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,5,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.073663, 42.7657, 1], [-0.46709099, 36.2114, 2], [0.203633, 19.9253, 2], [0.28130099, 52.4542, 3], [-0.056127, 14.6534, 3], - [-0.016129, 8.1531, 3], [-0.027945, 23.3238, 4], [0.00176, 5.1176, 4], [-1.9e-05, 3.4938, 4], [0.028164, 13.6859, 5], - [-0.000439, 2.3587, 5], [0.000454, 1.4927, 5], [-0.000359, 0.9693, 5]], - '2s': [[0.35804, 42.403, 1], [0.101639, 36.4481, 2], [-0.95906299, 20.1236, 2], [0.038688, 53.0589, 3], [-0.075213, 14.657, 3], - [0.000893, 8.2011, 3], [-0.161136, 23.1606, 4], [0.007567, 5.1634, 4], [-0.010907, 3.5332, 4], [-0.011348, 13.7523, 5], - [0.00805, 2.4011, 5], [-0.006754, 1.5389, 5], [0.003225, 1.0341, 5]], - '3s': [[-0.15193699, 42.734, 1], [-0.076614, 36.2159, 2], [0.54800498, 19.9306, 2], [-0.014786, 52.479, 3], [-0.224738, 14.6592, 3], - [-0.82262897, 8.1982, 3], [0.19467799, 23.3526, 4], [0.028905, 5.1184, 4], [-0.042912, 3.4935, 4], [-0.187135, 13.6594, 5], - [0.037076, 2.3579, 5], [-0.009914, 1.4898, 5], [-0.010604, 0.9661, 5]], - '4s': [[0.059787, 42.7427, 1], [0.031079, 36.2095, 2], [-0.223704, 19.9248, 2], [0.006839, 52.4537, 3], [0.076618, 14.6518, 3], - [0.49941599, 8.1837, 3], [-0.079728, 23.3252, 4], [-0.67325097, 5.1735, 4], [-0.52065802, 3.4947, 4], [0.073957, 13.6851, 5], - [-0.009513, 2.359, 5], [0.005262, 1.4902, 5], [-0.005644, 0.9661, 5]], - '5s': [[0.015414, 42.7425, 1], [0.008382, 36.2098, 2], [-0.058597, 19.9247, 2], [0.001539, 52.4536, 3], [0.019948, 14.6519, 3], - [0.14747199, 8.1528, 3], [-0.022342, 23.323, 4], [-0.244422, 5.1172, 4], [-0.107851, 3.4987, 4], [0.015723, 13.6858, 5], - [0.31109399, 2.365, 5], [0.55820101, 1.5163, 5], [0.28235599, 0.995, 5]], - '2p': [[0.074043, 51.7244, 2], [0.32836699, 20.5, 2], [0.14975099, 41.7023, 3], [0.465637, 16.9059, 3], [-0.312451, 9.96, 3], - [0.28314, 7.4073, 3], [0.18238001, 34.9832, 4], [-0.164426, 4.9854, 4], [0.133362, 3.3068, 4], [-0.054288, 2.1647, 4]], - '3p': [[-6.8e-05, 51.2734, 2], [-0.472166, 20.4334, 2], [0.031142, 42.4438, 3], [-0.070631, 16.8351, 3], [0.60758197, 9.879, 3], - [0.51614797, 7.3842, 3], [0.025408, 34.1826, 4], [0.002286, 4.9235, 4], [0.008146, 3.2699, 4], [-0.008216, 2.1185, 4]], - '4p': [[-0.001832, 51.2735, 2], [-0.15277401, 20.4295, 2], [0.006257, 42.4419, 3], [-0.033781, 16.836, 3], [0.24352799, 9.8814, 3], - [0.26927599, 7.3784, 3], [0.000305, 34.1849, 4], [-0.52016503, 4.9245, 4], [-0.56903702, 3.2909, 4], [-0.089617, 2.1116, 4]], - '3d': [[0.018023, 22.908, 3], [0.408209, 12.594, 3], [0.353917, 6.0349, 3], [-0.013679, 3.6133, 3], [0.31851301, 9.7915, 4], - [0.032538, 2.7888, 4], [-0.043181, 1.7736, 4], [0.025939, 1.1349, 4]], - '4d': [[0.004114, 22.9007, 3], [0.105945, 12.656, 3], [0.17082299, 6.0512, 3], [-0.381017, 3.5542, 3], [0.071353, 9.7503, 4], - [-0.40889299, 2.7024, 4], [-0.34154001, 1.7283, 4], [-0.121136, 1.1315, 4]],}, - 'Sl core':{'fa':[10.47487 ,7.35603 ,18.12997 ,0.00000],'fb':[9.89680 ,0.06875 ,1.10651 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.092 + 'Mo':{'Sl core':{'fa':[10.47487 ,7.35603 ,18.12997 ,0.00000],'fb':[9.89680 ,0.06875 ,1.10651 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.092 + 'Sl val':{'fa':[0.42559 ,0.64329 ,-0.52883 ,0.45881],'fb':[75.40989 ,19.94525 ,1.96158 ,1.63076],'fc':-0.00078,'Ne':6}, #Rw: 0.311 'Sl 4d':{'fa':[0.10259 ,-0.21603 ,0.73943 ,0.37434],'fb':[1.22875 ,2.95206 ,18.21006 ,51.84881],'fc':-0.00108,'Ne':5}, #Rw: 0.124 'Sl 5s':{'fa':[0.78811 ,0.67783 ,-2.41043 ,1.94338],'fb':[118.77086 ,47.55285 ,17.44778 ,15.38228],'fc':0.00038,'Ne':1}, #Rw: 0.750 'Rb core':{'fa':[1.30177 ,10.32219 ,18.22240 ,5.39309],'fb':[106.40783 ,9.69655 ,1.09796 ,0.09213],'fc':1.74967,'Ne':1}, #Rw: 0.043 ' 4d':{'fa':[0.33221 ,0.76708 ,-0.52927 ,0.43009],'fb':[55.35317 ,18.77570 ,2.13176 ,1.66268],'fc':-0.00067,'Ne':6}, #Rw: 0.094 ' 4d':{'fa':[1.11529 ,-0.31225 ,-0.80927 ,0.02862],'fb':[3.57147 ,43.60743 ,2.94435 ,0.01093],'fc':-0.02047,'Ne':1}, #Rw: 1.038 ' 4d':{'fa':[0.02482 ,-0.21078 ,0.29679 ,-0.11407],'fb':[93.27345 ,12.91551 ,1.89330 ,1.16370],'fc':0.00327,'Ne':1}}, #Rw: 0.202 - 'Mo+3':{'ZSlater':4.767,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[45.0201,15.1215,19.2657,7.4893,7.5360,9.0903,3.8108,3.4841,2.5449,0.0000,1.5757,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,5,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[9.89516 ,10.35370 ,0.00000 ,10.78401],'fb':[0.65408 ,9.93974 ,0.00000 ,1.40891],'fc':4.95552,'Ne':1}, #Rw: 0.042 + 'Mo+3':{'Kr core':{'fa':[9.89516 ,10.35370 ,0.00000 ,10.78401],'fb':[0.65408 ,9.93974 ,0.00000 ,1.40891],'fc':4.95552,'Ne':1}, #Rw: 0.042 ' 4d':{'fa':[0.32111 ,0.86534 ,-0.30173 ,0.11710],'fb':[30.59024 ,14.82659 ,3.19894 ,1.10904],'fc':-0.00201,'Ne':3}, #Rw: 0.083 ' 4d':{'fa':[0.57032 ,-0.42927 ,-0.15492 ,0.20560],'fb':[4.38753 ,25.11658 ,1.83927 ,0.00622],'fc':-0.19126,'Ne':1}, #Rw: 0.290 ' 4d':{'fa':[0.07591 ,-0.34178 ,0.30301 ,-0.04340],'fb':[34.81499 ,9.57759 ,2.20131 ,0.66738],'fc':0.00627,'Ne':1}}, #Rw: 0.189 - 'Mo+5':{'ZSlater':4.767,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[45.0201,15.1215,19.2657,7.4893,7.5360,9.0903,3.8108,3.4841,2.5449,0.0000,1.5757,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,5,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[0.27242 ,10.77885 ,9.81111 ,18.25542],'fb':[21.73286 ,8.80373 ,0.03448 ,1.04877],'fc':-3.11772,'Ne':1}, #Rw: 0.025 + 'Mo+5':{'Kr core':{'fa':[0.27242 ,10.77885 ,9.81111 ,18.25542],'fb':[21.73286 ,8.80373 ,0.03448 ,1.04877],'fc':-3.11772,'Ne':1}, #Rw: 0.025 ' 4d':{'fa':[0.94374 ,0.33901 ,-0.40803 ,0.12820],'fb':[12.18465 ,21.77254 ,3.44702 ,1.02709],'fc':-0.00294,'Ne':1}, #Rw: 0.006 ' 4d':{'fa':[0.67729 ,-0.52343 ,-0.17396 ,0.06870],'fb':[4.25929 ,18.28697 ,1.65078 ,0.03425],'fc':-0.04832,'Ne':1}, #Rw: 0.186 ' 4d':{'fa':[0.11981 ,-0.46149 ,0.37992 ,-0.04687],'fb':[22.86284 ,7.71560 ,2.21793 ,0.57735],'fc':0.00861,'Ne':1}}, #Rw: 0.043 - 'Tc':{'ZSlater':5.004,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[46.2924,15.5408,19.7944,7.7247,7.7809,9.4293,3.9769,3.6574,2.8234,0.0000,1.6993,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,5,0,], [2,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.07881403, 43.8137, 1], [-0.499374, 37.081, 2], [0.183112, 20.5485, 2], [0.308799, 53.9251, 3], [-0.011808, 15.1961, 3], - [-0.000854, 8.4544, 3], [-0.015487, 24.0325, 4], [0.000213, 5.3525, 4], [0.000261, 3.6823, 4], [0.000997, 14.1189, 5], - [0.000169, 2.415, 5], [-0.00044, 1.5326, 5], [0.00021, 0.9998, 5]], - '2s': [[0.36051601, 43.4665, 1], [0.09789, 37.2727, 2], [-0.95362699, 20.7128, 2], [0.039838, 54.4749, 3], [-0.076926, 15.1993, 3], - [0.001642, 8.4887, 3], [-0.16439401, 23.8849, 4], [0.008273, 5.3909, 4], [-0.01156, 3.717, 4], [-0.012449, 14.1836, 5], - [0.009443, 2.4443, 5], [-0.00916, 1.5608, 5], [0.004604, 1.0639, 5]], - '3s': [[-0.15408801, 43.7573, 1], [-0.075153, 37.0844, 2], [0.548105, 20.5505, 2], [-0.015833, 53.9326, 3], [-0.21705399, 15.1959, 3], - [-0.828219, 8.4893, 3], [0.198102, 24.0488, 4], [0.026846, 5.3507, 4], [-0.038067, 3.6768, 4], [-0.188684, 14.1118, 5], - [0.025565, 2.4104, 5], [-0.000261, 1.528, 5], [-0.005555, 0.9884, 5]], - '4s': [[0.06249, 43.7614, 1], [0.031301, 37.0817, 2], [-0.230361, 20.5474, 2], [0.007611, 53.9182, 3], [0.070794, 15.1932, 3], - [0.52510399, 8.4826, 3], [-0.081909, 24.032, 4], [-0.688353, 5.3997, 4], [-0.52041698, 3.6876, 4], [0.077452, 14.1217, 5], - [-0.002705, 2.4119, 5], [0.01988, 1.5281, 5], [0.004851, 0.9884, 5]], - '5s': [[0.014778, 43.7613, 1], [0.007787, 37.0818, 2], [-0.053891, 20.5473, 2], [0.001135, 53.9182, 3], [0.016894, 15.1937, 3], - [0.145578, 8.4507, 3], [-0.022722, 24.0307, 4], [-0.23171601, 5.3542, 4], [-0.085511, 3.682, 4], [0.009361, 14.1205, 5], - [0.33111599, 2.4233, 5], [0.54742801, 1.5462, 5], [0.27425101, 1.008, 5]], - '2p': [[0.075889, 53.7798, 2], [0.30288699, 21.0695, 2], [0.15712801, 42.6569, 3], [0.498891, 17.7074, 3], [-0.36700699, 10.3188, 3], - [0.35444501, 7.7288, 3], [0.17380901, 36.5065, 4], [-0.21496101, 5.3518, 4], [0.169972, 3.5954, 4], [-0.068459, 2.3741, 4]], - '3p': [[-0.007368, 52.9829, 2], [-0.40352601, 21.0621, 2], [0.011291, 43.8664, 3], [-0.106489, 17.3606, 3], [0.66617, 10.1255, 3], - [0.45981601, 7.6337, 3], [-0.00426, 35.321, 4], [0.025726, 5.1801, 4], [-0.004819, 3.4527, 4], [-0.017694, 2.2242, 4]], - '4p': [[-0.002817, 52.982, 2], [-0.149894, 21.0517, 2], [0.00422, 43.8595, 3], [-0.039963, 17.3639, 3], [0.26267099, 10.1173, 3], - [0.274818, 7.6196, 3], [-0.003368, 35.3252, 4], [-0.52370697, 5.1802, 4], [-0.58349103, 3.4816, 4], [-0.077408, 2.214, 4]], - '3d': [[0.020984, 52.953, 3], [-0.34958601, 21.0033, 3], [-0.018108, 43.8577, 3], [0.601013, 17.1576, 3], [0.55007201, 10.7916, 4], - [0.18555599, 7.5843, 4], [0.074217, 35.2256, 4], [0.070122, 5.4907, 4]], - '4d': [[-0.203529, 52.7955, 3], [1.69602394, 20.7401, 3], [0.234202, 43.7338, 3], [-1.52175701, 16.3873, 3], [0.32691199, 11.422, 4], - [-0.54707301, 3.8632, 4], [-0.624062, 34.5506, 4], [-0.56651199, 2.0758, 4]],}, - 'Sl core':{'fa':[7.17132 ,10.61540 ,18.17988 ,0.00000],'fb':[0.06076 ,8.84816 ,1.01255 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.079 + 'Tc':{'Sl core':{'fa':[7.17132 ,10.61540 ,18.17988 ,0.00000],'fb':[0.06076 ,8.84816 ,1.01255 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.079 + 'Sl val':{'fa':[0.44531 ,0.62249 ,-0.64892 ,0.57930],'fb':[77.35004 ,16.70981 ,1.70698 ,1.46677],'fc':-0.00102,'Ne':7}, #Rw: 0.476 'Sl 4d':{'fa':[0.68696 ,-0.28633 ,0.49168 ,0.10855],'fb':[12.80714 ,3.04600 ,30.38572 ,1.09066],'fc':-0.00105,'Ne':5}, #Rw: 0.034 'Sl 5s':{'fa':[-28.11958 ,27.86262 ,0.90077 ,0.35444],'fb':[25.68303 ,26.13320 ,106.51474 ,12.21760],'fc':0.00040,'Ne':2}, #Rw: 0.634 'Sr core':{'fa':[18.13442 ,10.31905 ,4.05655 ,2.44916],'fb':[1.00990 ,8.52790 ,0.12237 ,91.07299],'fc':3.02725,'Ne':1}, #Rw: 0.033 ' 4d':{'fa':[0.30746 ,0.82646 ,-0.33504 ,0.20191],'fb':[39.50347 ,14.98713 ,2.31116 ,1.28381],'fc':-0.00110,'Ne':5}, #Rw: 0.053 ' 4d':{'fa':[1.91699 ,-0.34886 ,-1.99249 ,0.41698],'fb':[2.86231 ,30.48853 ,2.39647 ,1.97408],'fc':0.00877,'Ne':1}, #Rw: 0.788 ' 4d':{'fa':[0.03629 ,0.27976 ,-0.06126 ,-0.25959],'fb':[54.13258 ,1.85956 ,0.79118 ,9.44381],'fc':0.00478,'Ne':1}}, #Rw: 0.076 - 'Ru':{'ZSlater':5.533,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[47.5804,15.9629,20.3257,7.9611,8.0262,9.7662,4.1262,3.8008,2.9201,0.0000,1.6894,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,7,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.08556902, 44.8205, 1], [-0.52902198, 37.9454, 2], [0.18396799, 21.104, 2], [0.327894, 55.1801, 3], [-0.012893, 15.7134, 3], - [-0.00013, 8.7404, 3], [-0.005855, 24.7, 4], [3.8e-05, 5.5772, 4], [0.000329, 3.8002, 4], [0.000209, 14.5098, 5], - [0.000211, 2.5197, 5], [-0.000585, 1.5588, 5], [0.000451, 0.9981, 5]], - '2s': [[0.35989499, 44.7691, 1], [0.101635, 37.9459, 2], [-0.96795499, 21.107, 2], [0.04003, 55.1748, 3], [-0.095719, 15.7108, 3], - [-0.005639, 8.7354, 3], [-0.133687, 24.6967, 4], [0.001874, 5.5734, 4], [0.0012, 3.7942, 4], [-0.000417, 14.5128, 5], - [0.000565, 2.5157, 5], [-0.00112, 1.5535, 5], [0.000743, 0.9867, 5]], - '3s': [[-0.156293, 44.7682, 1], [-0.07361, 37.9466, 2], [0.55215299, 21.1037, 2], [-0.016911, 55.1704, 3], [-0.21730199, 15.7066, 3], - [-0.82040697, 8.7742, 3], [0.199664, 24.7035, 4], [0.015125, 5.5744, 4], [-0.020463, 3.7946, 4], [-0.197027, 14.5249, 5], - [0.001163, 2.5156, 5], [0.003169, 1.5531, 5], [0.000777, 0.9861, 5]], - '4s': [[0.063687, 44.7677, 1], [0.030666, 37.9472, 2], [-0.232464, 21.1033, 2], [0.008054, 55.1696, 3], [0.06596, 15.7095, 3], - [0.53934801, 8.774, 3], [-0.082369, 24.6987, 4], [-0.69630301, 5.625, 4], [-0.51199597, 3.8066, 4], [0.076936, 14.5147, 5], - [-0.013564, 2.5176, 5], [0.004834, 1.5535, 5], [-0.001017, 0.9861, 5]], - '5s': [[-0.015453, 44.7676, 1], [-0.007635, 37.9473, 2], [0.057262, 21.1032, 2], [-0.001944, 55.1696, 3], [-0.013737, 15.71, 3], - [-0.155293, 8.7378, 3], [0.020875, 24.6979, 4], [0.244321, 5.5753, 4], [0.09022, 3.8, 4], [-0.012785, 14.5126, 5], - [-0.30540699, 2.5219, 5], [-0.55106002, 1.582, 5], [-0.29976499, 1.0179, 5]], - '2p': [[0.034152, 55.3644, 2], [0.69731498, 21.7595, 2], [0.032949, 45.6857, 3], [0.22178, 17.9326, 3], [0.064007, 10.4318, 3], - [-0.07445, 7.9121, 3], [0.060397, 36.7022, 4], [0.036095, 5.4138, 4], [0.005832, 3.5978, 4], [-0.025042, 2.3298, 4]], - '3p': [[-0.004195, 55.3565, 2], [-0.43468201, 21.7334, 2], [0.020735, 45.6923, 3], [-0.096814, 17.9511, 3], [0.64269501, 10.4332, 3], - [0.484916, 7.933, 3], [0.009292, 36.6908, 4], [0.007131, 5.4133, 4], [0.013826, 3.5965, 4], [-0.018519, 2.3286, 4]], - '4p': [[0.000747, 55.3564, 2], [-0.18685, 21.7271, 2], [0.014324, 45.6887, 3], [-0.027902, 17.9532, 3], [0.24475101, 10.4306, 3], - [0.305893, 7.9237, 3], [0.012153, 36.694, 4], [-0.54317802, 5.4293, 4], [-0.57296598, 3.6003, 4], [-0.082432, 2.3277, 4]], - '3d': [[0.017465, 24.1715, 3], [0.41633499, 13.4341, 3], [0.28682399, 6.5041, 3], [0.018947, 4.0439, 3], [0.35866699, 10.3975, 4], - [-0.004267, 2.9873, 4], [-0.005661, 1.8815, 4], [-0.001855, 1.2184, 4]], - '4d': [[0.004699, 24.1689, 3], [0.119397, 13.4496, 3], [0.206295, 6.5039, 3], [-0.44087699, 4.0443, 3], [0.082948, 10.3861, 4], - [-0.44124201, 2.9901, 4], [-0.31222901, 1.8736, 4], [-0.093557, 1.2187, 4]],}, - 'Sl core':{'fa':[10.55445 ,18.19519 ,7.21558 ,0.00000],'fb':[8.25296 ,0.95128 ,0.06007 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.084 + 'Ru':{'Sl core':{'fa':[10.55445 ,18.19519 ,7.21558 ,0.00000],'fb':[8.25296 ,0.95128 ,0.06007 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.084 + 'Sl val':{'fa':[0.34953 ,0.73677 ,-0.71819 ,0.63071],'fb':[63.58789 ,15.19478 ,1.62032 ,1.37027],'fc':-0.00118,'Ne':8}, #Rw: 0.356 'Sl 4d':{'fa':[0.77707 ,-0.32494 ,0.34747 ,0.20047],'fb':[13.87235 ,2.11281 ,40.24630 ,1.20538],'fc':-0.00081,'Ne':7}, #Rw: 0.122 'Sl 5s':{'fa':[-118.94571 ,0.65016 ,0.64942 ,118.64516],'fb':[13.34599 ,53.77890 ,121.20030 ,13.32372],'fc':0.00041,'Ne':1}, #Rw: 0.681 'Rb core':{'fa':[1.23577 ,10.23950 ,8.99745 ,11.48126],'fb':[100.47678 ,8.31580 ,1.23826 ,0.64283],'fc':5.03618,'Ne':1}, #Rw: 0.030 ' 4d':{'fa':[0.31334 ,0.80371 ,-0.77729 ,0.66060],'fb':[40.94447 ,14.13002 ,1.72582 ,1.42249],'fc':-0.00087,'Ne':7}, #Rw: 0.082 ' 4d':{'fa':[-0.22925 ,-0.51954 ,0.02707 ,0.71796],'fb':[9.14909 ,1.13862 ,63.44101 ,1.34044],'fc':0.00375,'Ne':1}, #Rw: 0.147 ' 4d':{'fa':[0.02707 ,-0.22924 ,0.72309 ,-0.52467],'fb':[63.44022 ,9.14919 ,1.33965 ,1.13953],'fc':0.00375,'Ne':1}}, #Rw: 0.147 - 'Ru+3':{'ZSlater':5.533,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[47.5804,15.9629,20.3257,7.9611,8.0262,9.7662,4.1262,3.8008,2.9201,0.0000,1.6894,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,7,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[25.41601 ,10.97441 ,0.00000 ,-5.81630],'fb':[0.85661 ,8.02549 ,0.00000 ,0.85591],'fc':5.40482,'Ne':1}, #Rw: 0.083 + 'Ru+3':{'Kr core':{'fa':[25.41601 ,10.97441 ,0.00000 ,-5.81630],'fb':[0.85661 ,8.02549 ,0.00000 ,0.85591],'fc':5.40482,'Ne':1}, #Rw: 0.083 ' 4d':{'fa':[0.37746 ,0.82643 ,-0.34038 ,0.13869],'fb':[23.53439 ,11.22261 ,2.59079 ,0.95948],'fc':-0.00232,'Ne':5}, #Rw: 0.052 ' 4d':{'fa':[1.26297 ,-0.41942 ,-0.85367 ,0.20426],'fb':[2.85701 ,20.28916 ,2.26308 ,-0.00105],'fc':-0.19363,'Ne':1}, #Rw: 0.332 ' 4d':{'fa':[0.07023 ,-0.34099 ,0.32144 ,-0.05763],'fb':[28.47762 ,7.41038 ,1.72516 ,0.60194],'fc':0.00694,'Ne':1}}, #Rw: 0.065 - 'Ru+4':{'ZSlater':5.533,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[47.5804,15.9629,20.3257,7.9611,8.0262,9.7662,4.1262,3.8008,2.9201,0.0000,1.6894,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,7,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[10.67854 ,0.39090 ,23.00931 ,18.66884],'fb':[7.57423 ,19.03339 ,0.00898 ,0.89361],'fc':-16.74699,'Ne':1}, #Rw: 0.010 + 'Ru+4':{'Kr core':{'fa':[10.67854 ,0.39090 ,23.00931 ,18.66884],'fb':[7.57423 ,19.03339 ,0.00898 ,0.89361],'fc':-16.74699,'Ne':1}, #Rw: 0.010 ' 4d':{'fa':[0.88671 ,0.35347 ,-0.37677 ,0.13948],'fb':[10.56524 ,20.66751 ,2.68658 ,0.91186],'fc':-0.00293,'Ne':4}, #Rw: 0.015 ' 4d':{'fa':[0.77219 ,-0.46079 ,-0.32545 ,0.12235],'fb':[3.13905 ,17.49738 ,1.81472 ,0.00379],'fc':-0.10795,'Ne':1}, #Rw: 0.247 ' 4d':{'fa':[0.08422 ,-0.39061 ,0.35456 ,-0.05595],'fb':[24.13731 ,6.66592 ,1.76237 ,0.56117],'fc':0.00783,'Ne':1}}, #Rw: 0.439 - 'Rh':{'ZSlater':5.891,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[48.8844,16.3876,20.8589,8.1988,8.2722,10.1029,4.2821,3.9559,3.0958,0.0000,1.7410,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,8,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.086972, 45.8373, 1], [-0.56184399, 38.8207, 2], [0.233024, 21.7913, 2], [0.33418599, 56.5108, 3], [0.002638, 16.2963, 3], - [-0.001371, 9.0506, 3], [-0.046473, 25.4426, 4], [0.000107, 5.791, 4], [0.002048, 3.9335, 4], [-0.005726, 14.9862, 5], - [-0.000828, 2.5739, 5], [0.000859, 1.5768, 5], [-0.000401, 1.0033, 5]], - '2s': [[0.37323099, 45.7722, 1], [0.08581, 38.8184, 2], [-0.94052702, 21.7985, 2], [0.047946, 56.5301, 3], [-0.108325, 16.2954, 3], - [-0.026243, 9.0432, 3], [-0.13521101, 25.4322, 4], [0.006392, 5.7863, 4], [0.00372, 3.9284, 4], [-0.00391, 14.9898, 5], - [0.000757, 2.5695, 5], [-0.001189, 1.5706, 5], [0.000575, 0.9907, 5]], - '3s': [[-0.16824199, 45.7799, 1], [-0.077251, 38.8129, 2], [0.55453497, 21.7918, 2], [-0.011294, 56.5092, 3], [-0.20443501, 16.2946, 3], - [-0.96841502, 9.1229, 3], [0.23932099, 25.4406, 4], [0.06599, 5.7915, 4], [-0.088309, 3.93, 4], [-0.079947, 15.0022, 5], - [0.008038, 2.5699, 5], [0.015338, 1.5698, 5], [0.004356, 0.9897, 5]], - '4s': [[0.073809, 45.7789, 1], [0.031843, 38.8162, 2], [-0.25410599, 21.7902, 2], [0.010148, 56.5088, 3], [0.06002, 16.2939, 3], - [0.578897, 9.0837, 3], [-0.088582, 25.4428, 4], [-0.71081299, 5.8404, 4], [-0.50498402, 3.9398, 4], [0.083489, 14.9924, 5], - [-0.011143, 2.5713, 5], [0.002789, 1.5702, 5], [-0.002744, 0.9897, 5]], - '5s': [[-0.015347, 45.7788, 1], [-0.007243, 38.8162, 2], [0.055731, 21.7902, 2], [-0.002126, 56.5086, 3], [-0.009764, 16.2941, 3], - [-0.157676, 9.0459, 3], [0.019914, 25.4412, 4], [0.24416199, 5.7874, 4], [0.078485, 3.934, 4], [-0.011085, 14.9903, 5], - [-0.30671701, 2.5762, 5], [-0.54773998, 1.5999, 5], [-0.30263799, 1.0231, 5]], - '2p': [[0.051863, 57.2153, 2], [0.50362098, 22.4058, 2], [0.084682, 47.0625, 3], [0.32624099, 18.4272, 3], [-0.049155, 10.6733, 3], - [0.038754, 8.1601, 3], [0.132787, 37.9648, 4], [0.026805, 5.6749, 4], [-0.059015, 3.7677, 4], [0.038015, 2.4503, 4]], - '3p': [[-0.009427, 57.1605, 2], [-0.400195, 22.349, 2], [0.007906, 47.1465, 3], [-0.123781, 18.4736, 3], [0.682069, 10.6869, 3], - [0.432693, 8.1812, 3], [-0.01079, 37.8407, 4], [0.035208, 5.6668, 4], [0.020842, 3.7637, 4], [-0.022238, 2.4406, 4]], - '4p': [[0.000284, 57.1591, 2], [-0.204262, 22.339, 2], [0.014676, 47.1401, 3], [-0.033626, 18.4769, 3], [0.27077401, 10.6713, 3], - [0.32640299, 8.1685, 3], [0.011957, 37.844, 4], [-0.54996198, 5.689, 4], [-0.57672203, 3.7579, 4], [-0.081154, 2.4424, 4]], - '3d': [[0.017023, 24.9364, 3], [0.41427699, 13.887, 3], [0.28362101, 6.7897, 3], [0.020669, 4.3808, 3], [0.36049899, 10.7877, 4], - [-0.004348, 3.2221, 4], [-0.005506, 2.0252, 4], [-0.001126, 1.2974, 4]], - '4d': [[0.0047, 24.9329, 3], [0.122576, 13.9084, 3], [0.23481201, 6.7904, 3], [-0.44874999, 4.3841, 3], [0.08325, 10.7717, 4], - [-0.44834799, 3.2193, 4], [-0.31512699, 2.0227, 4], [-0.097886, 1.2944, 4]],}, - 'Sl core':{'fa':[7.20208 ,10.57454 ,18.18988 ,0.00000],'fb':[0.05766 ,7.60388 ,0.88865 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.082 + 'Rh':{'Sl core':{'fa':[7.20208 ,10.57454 ,18.18988 ,0.00000],'fb':[0.05766 ,7.60388 ,0.88865 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.082 + 'Sl val':{'fa':[0.69768 ,0.31892 ,0.77362 ,-0.79125],'fb':[1.25359 ,59.05065 ,13.58317 ,1.47696],'fc':-0.00155,'Ne':9}, #Rw: 0.377 'Sl 4d':{'fa':[0.79236 ,-0.54929 ,0.33629 ,0.42039],'fb':[12.35956 ,1.72777 ,35.80832 ,1.27173],'fc':-0.00048,'Ne':8}, #Rw: 0.117 'Sl 5s':{'fa':[2.72419 ,0.88219 ,-2.83932 ,0.23096],'fb':[26.82050 ,106.12134 ,23.09394 ,10.05406],'fc':0.00047,'Ne':1}, #Rw: 0.689 'Rb core':{'fa':[0.75746 ,0.96310 ,10.50692 ,19.46251],'fb':[213.55136 ,32.91086 ,6.96452 ,0.79316],'fc':5.33961,'Ne':1}, #Rw: 0.222 ' 4d':{'fa':[0.36089 ,0.77524 ,-0.50891 ,0.37366],'fb':[33.04851 ,11.89210 ,1.76347 ,1.24109],'fc':-0.00071,'Ne':8}, #Rw: 0.505 ' 4d':{'fa':[1.16311 ,-0.33019 ,-0.84707 ,0.02409],'fb':[2.41173 ,27.11027 ,1.93108 ,0.07804],'fc':-0.00818,'Ne':1}, #Rw: 1.086 ' 4d':{'fa':[0.02765 ,-0.23740 ,0.86339 ,-0.65764],'fb':[54.57220 ,7.90166 ,1.20490 ,1.04974],'fc':0.00398,'Ne':1}}, #Rw: 0.140 - 'Rh+3':{'ZSlater':5.891,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[48.8844,16.3876,20.8589,8.1988,8.2722,10.1029,4.2821,3.9559,3.0958,0.0000,1.7410,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,8,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[36.29869 ,10.96361 ,0.00000 ,-16.66752],'fb':[0.80435 ,7.44227 ,0.00000 ,0.80424],'fc':5.38661,'Ne':1}, #Rw: 0.073 + 'Rh+3':{'Kr core':{'fa':[36.29869 ,10.96361 ,0.00000 ,-16.66752],'fb':[0.80435 ,7.44227 ,0.00000 ,0.80424],'fc':5.38661,'Ne':1}, #Rw: 0.073 ' 4d':{'fa':[0.34328 ,0.85921 ,-0.35529 ,0.15513],'fb':[22.06273 ,10.31661 ,2.29209 ,0.90899],'fc':-0.00240,'Ne':6}, #Rw: 0.015 ' 4d':{'fa':[1.47559 ,-0.41776 ,-1.06874 ,0.20346],'fb':[2.52074 ,18.38109 ,2.07330 ,-0.00091],'fc':-0.19205,'Ne':1}, #Rw: 0.348 ' 4d':{'fa':[0.06851 ,-0.33740 ,0.35415 ,-0.09140],'fb':[26.01002 ,6.66928 ,1.47798 ,0.68887],'fc':0.00612,'Ne':1}}, #Rw: 0.055 - 'Rh+4':{'ZSlater':5.891,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[48.8844,16.3876,20.8589,8.1988,8.2722,10.1029,4.2821,3.9559,3.0958,0.0000,1.7410,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,8,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[0.57530 ,10.53968 ,13.15019 ,18.79590],'fb':[15.24439 ,6.93579 ,0.01272 ,0.82796],'fc':-7.06085,'Ne':1}, #Rw: 0.012 + 'Rh+4':{'Kr core':{'fa':[0.57530 ,10.53968 ,13.15019 ,18.79590],'fb':[15.24439 ,6.93579 ,0.01272 ,0.82796],'fc':-7.06085,'Ne':1}, #Rw: 0.012 ' 4d':{'fa':[0.90191 ,0.33481 ,-0.38897 ,0.15509],'fb':[9.67359 ,19.24608 ,2.37829 ,0.87144],'fc':-0.00288,'Ne':5}, #Rw: 0.010 ' 4d':{'fa':[1.44894 ,-0.45322 ,-1.00772 ,0.09245],'fb':[2.52058 ,16.08568 ,2.02373 ,-0.00244],'fc':-0.08010,'Ne':1}, #Rw: 0.336 ' 4d':{'fa':[0.08617 ,-0.38766 ,0.36003 ,-0.06640],'fb':[21.38099 ,6.11743 ,1.55706 ,0.55295],'fc':0.00784,'Ne':1}}, #Rw: 0.049 - 'Pd':{'ZSlater':6.372,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[50.2124,16.8157,21.3985,8.4378,8.5189,10.4380,4.4319,4.0995,3.1860,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.09475398, 46.8559, 1], [-0.58874601, 39.7628, 2], [0.230938, 22.2673, 2], [0.35241401, 57.8294, 3], [-0.009956, 17.1785, 3], - [-0.002137, 10.4859, 3], [-0.003345, 7.6036, 3], [-0.028092, 26.2314, 4], [0.001665, 7.1177, 4], [0.001155, 4.7078, 4], - [0.000157, 3.1998, 4]], - '2s': [[-0.373357, 46.7981, 1], [-0.078303, 39.7591, 2], [0.93771201, 22.2715, 2], [-0.048755, 57.8348, 3], [0.106767, 17.1697, 3], - [0.029982, 10.4875, 3], [0.008293, 7.6058, 3], [0.12798101, 26.227, 4], [-0.0058, 7.098, 4], [-0.005951, 4.6933, 4], - [-0.000999, 3.1887, 4]], - '3s': [[-0.17004099, 46.7986, 1], [-0.060206, 39.7601, 2], [0.53670001, 22.2669, 2], [-0.021485, 57.8244, 3], [0.056872, 17.1726, 3], - [-1.02670598, 10.5269, 3], [-0.16076, 7.6062, 3], [0.156132, 26.2329, 4], [0.009552, 7.0987, 4], [-0.038937, 4.6945, 4], - [0.006847, 3.1885, 4]], - '4s': [[-0.07503, 46.7974, 1], [-0.029307, 39.761, 2], [0.248384, 22.266, 2], [-0.009144, 57.8249, 3], [0.010564, 17.1703, 3], - [-0.379269, 10.4977, 3], [-0.58455002, 7.6224, 3], [0.084753, 26.2282, 4], [0.68178999, 7.1223, 4], [0.68912798, 4.6982, 4], - [0.119573, 3.1975, 4]], - '2p': [[0.047097, 58.841, 2], [0.551489, 23.0708, 2], [0.067127, 48.56, 3], [0.29598999, 18.9973, 3], [0.0175, 10.9174, 3], - [-0.027762, 8.4017, 3], [0.114904, 39.0999, 4], [0.055355, 5.9209, 4], [-0.068152, 3.9141, 4], [0.041141, 2.5254, 4]], - '3p': [[-0.01087, 58.8145, 2], [-0.38686901, 23.0265, 2], [0.00527, 48.5956, 3], [-0.134152, 19.0341, 3], [0.71141797, 10.9373, 3], - [0.40368101, 8.423, 3], [-0.016577, 39.0442, 4], [0.038964, 5.9146, 4], [0.017891, 3.9134, 4], [-0.018099, 2.5179, 4]], - '4p': [[-0.000623, 58.8127, 2], [-0.198442, 23.0164, 2], [0.013061, 48.5892, 3], [-0.039201, 19.0377, 3], [0.28330699, 10.9165, 3], - [0.32742101, 8.4118, 3], [0.008314, 39.0459, 4], [-0.553716, 5.9355, 4], [-0.572676, 3.9116, 4], [-0.091878, 2.5185, 4]], - '3d': [[0.016749, 25.6339, 3], [0.412328, 14.3079, 3], [0.274887, 7.0413, 3], [0.026598, 4.7075, 3], [0.36125401, 11.1458, 4], - [0.006199, 3.3994, 4], [0.007742, 2.0533, 4], [0.004723, 1.2286, 4]], - '4d': [[0.004895, 25.6292, 3], [0.129908, 14.3352, 3], [0.25981399, 7.0427, 3], [-0.44610599, 4.7102, 3], [0.089259, 11.125, 4], - [-0.45014799, 3.3973, 4], [-0.333812, 2.0505, 4], [-0.12289, 1.227, 4]],}, - 'Sl core':{'fa':[10.58771 ,17.97025 ,7.40655 ,0.00000],'fb':[7.10811 ,0.84706 ,0.06129 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.088 + 'Pd':{'Sl core':{'fa':[10.58771 ,17.97025 ,7.40655 ,0.00000],'fb':[7.10811 ,0.84706 ,0.06129 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.088 + 'Sl val':{'fa':[0.33634 ,0.77579 ,1.04913 ,-1.16159],'fb':[38.20070 ,11.74182 ,1.25744 ,1.43856],'fc':-0.00085,'Ne':10}, #Rw: 0.175 'Sl 4d':{'fa':[0.77575 ,-0.89734 ,0.33654 ,0.78475],'fb':[11.73767 ,1.46829 ,38.18904 ,1.22965],'fc':-0.00089,'Ne':10}, #Rw: 0.175 }, - 'Pd+2':{'ZSlater':6.372,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[50.2124,16.8157,21.3985,8.4378,8.5189,10.4380,4.4319,4.0995,3.1860,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[22.44144 ,10.68651 ,0.00000 ,-2.61477],'fb':[0.78035 ,7.18645 ,0.00000 ,0.78282],'fc':5.46909,'Ne':1}, #Rw: 0.144 + 'Pd+2':{'Kr core':{'fa':[22.44144 ,10.68651 ,0.00000 ,-2.61477],'fb':[0.78035 ,7.18645 ,0.00000 ,0.78282],'fc':5.46909,'Ne':1}, #Rw: 0.144 ' 4d':{'fa':[0.32604 ,0.84116 ,-0.43488 ,0.26867],'fb':[24.09978 ,10.14332 ,1.77984 ,1.03742],'fc':-0.00115,'Ne':8}, #Rw: 0.027 ' 4d':{'fa':[1.71930 ,-0.38079 ,-1.35063 ,0.58054],'fb':[2.21656 ,19.39859 ,1.89998 ,0.00061],'fc':-0.56753,'Ne':1}, #Rw: 0.548 ' 4d':{'fa':[0.04941 ,-0.29236 ,0.63763 ,-0.39926],'fb':[30.65761 ,6.48015 ,1.15612 ,0.91261],'fc':0.00456,'Ne':1}}, #Rw: 0.071 - 'Pd+4':{'ZSlater':2.464,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[50.2124,16.8157,21.3985,8.4378,8.5189,10.4380,4.4319,4.0995,3.1860,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Kr core':{'fa':[0.25566 ,10.89598 ,9.74814 ,18.28056],'fb':[-0.23683 ,6.95315 ,0.04680 ,0.81155],'fc':-3.17458,'Ne':1}, #Rw: 0.325 + 'Pd+4':{'Kr core':{'fa':[0.25566 ,10.89598 ,9.74814 ,18.28056],'fb':[-0.23683 ,6.95315 ,0.04680 ,0.81155],'fc':-3.17458,'Ne':1}, #Rw: 0.325 ' 4d':{'fa':[0.90254 ,0.33368 ,-0.40241 ,0.16919],'fb':[8.81735 ,17.75100 ,2.13414 ,0.82404],'fc':-0.00305,'Ne':6}, #Rw: 0.009 ' 4d':{'fa':[1.69830 ,-0.45080 ,-1.25969 ,0.06269],'fb':[2.25433 ,14.73726 ,1.87946 ,-0.00490],'fc':-0.05010,'Ne':1}, #Rw: 0.262 ' 4d':{'fa':[0.08296 ,-0.38148 ,0.39690 ,-0.10499],'fb':[19.89036 ,5.56268 ,1.34768 ,0.64368],'fc':0.00660,'Ne':1}}, #Rw: 0.051 - 'Ag':{'ZSlater':6.575,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[51.5514,17.2461,21.9362,8.6779,8.7662,10.7715,4.5916,4.2619,3.4325,0.0000,1.8371,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,10,0,], [1,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[1.10477304, 47.9081, 1],[-0.59780103, 40.9823, 2], [0.221229, 22.0055, 2], [0.36449701, 59.2369, 3], [-0.004092, 14.2677, 3], - [0.003709, 9.8301, 3], [-0.045243, 26.8893, 4], [-0.000768, 6.1488, 4], [0.001088, 4.1684, 4], [-0.001297, 12.0314, 5], - [-0.000627, 2.6683, 5], [0.000356, 1.6078, 5], [-5.6e-05, 1.0057, 5]], - '2s': [[0.36971399, 47.855, 1], [0.067463, 40.9765, 2], [-0.99614501, 22.008, 2], [0.048225, 59.2358, 3], [-0.19692799, 14.2647, 3], - [0.204928, 9.8318, 3], [-0.093604, 26.8878, 4], [0.017478, 6.1462, 4], [-0.014223, 4.1637, 4], [-0.113489, 12.0267, 5], - [0.007876, 2.666, 5], [-0.006457, 1.6007, 5], [0.00341, 0.9972, 5]], - '3s': [[-0.16307101, 47.8528, 1], [-0.049218, 40.9778, 2], [0.55120802, 22.0077, 2], [-0.024379, 59.2331, 3], [0.150061, 14.2629, 3], - [-1.56614995, 9.8437, 3], [0.112975, 26.8943, 4], [-0.027256, 6.147, 4], [0.018347, 4.1629, 4], [0.32903799, 12.0375, 5], - [0.007684, 2.6653, 5], [0.000695, 1.6001, 5], [-6e-05, 0.9964, 5]], - '4s': [[0.068771, 47.8515, 1], [0.023171, 40.9787, 2], [-0.242691, 22.0036, 2], [0.009885, 59.2335, 3], [-0.072651, 14.2668, 3], - [0.84947997, 9.8565, 3], [-0.058186, 26.8873, 4], [-0.75384098, 6.1919, 4], [-0.46478099, 4.1829, 4], [-0.106218, 12.0154, 5], - [-0.018869, 2.6669, 5], [0.0125, 1.6002, 5], [0.004846, 0.9964, 5]], - '5s': [[0.014853, 47.8514, 1], [0.005987, 40.9787, 2], [-0.054165, 22.0035, 2], [0.00158, 59.2334, 3], [-0.003931, 14.2658, 3], - [0.176588, 9.833, 3], [-0.015142, 26.8874, 4], [-0.241739, 6.1456, 4], [-0.052329, 4.1679, 4], [-0.006456, 12.0236, 5], - [0.30422401, 2.674, 5], [0.54313499, 1.6328, 5], [0.31097499, 1.0311, 5]], - '2p': [[0.06474, 59.7962, 2], [0.40909901, 23.5221, 2], [0.112962, 49.1029, 3], [0.37819999, 19.3543, 3], [-0.101684, 11.1184, 3], - [0.04632, 8.6044, 3], [0.17453, 39.9669, 4], [0.037126, 6.2086, 4], [-0.059243, 4.1228, 4], [0.031256, 2.711, 4]], - '3p': [[-0.006477, 59.6758, 2], [-0.42947301, 23.4514, 2], [0.018693, 49.2883, 3], [-0.101684, 19.4051, 3], [0.72902, 11.1218, 3], - [0.40105101, 8.6052, 3], [0.001651, 39.6955, 4], [0.001525, 6.1924, 4], [0.022684, 4.0985, 4], [-0.019994, 2.6664, 4]], - '4p': [[0.003486, 59.6755, 2], [-0.229123, 23.4442, 2], [0.02396, 49.2846, 3], [-0.017776, 19.4075, 3], [0.272585, 11.1173, 3], - [0.32789999, 8.5981, 3], [0.02533, 39.6983, 4], [-0.56178403, 6.2082, 4], [-0.58025903, 4.0987, 4], [-0.088731, 2.6682, 4]], - '3d': [[0.015853, 26.5344, 3], [0.407251, 14.8419, 3], [0.28458199, 7.4085, 3], [0.019955, 5.0316, 3], [0.36312601, 11.5967, 4], - [-0.003554, 3.6742, 4], [-0.009224, 2.3057, 4], [-0.003282, 1.4594, 4]], - '4d': [[0.004408, 26.5321, 3], [0.122673, 14.8576, 3], [0.28469601, 7.4083, 3], [-0.46182001, 5.0363, 3], [0.079357, 11.5836, 4], - [-0.46047699, 3.6698, 4], [-0.32396799, 2.3053, 4], [-0.100179, 1.456, 4]]}, - 'Sl core':{'fa':[18.09456 ,10.57119 ,7.30326 ,0.00000],'fb':[0.78538 ,6.54623 ,0.05712 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.077 + 'Ag':{'Sl core':{'fa':[18.09456 ,10.57119 ,7.30326 ,0.00000],'fb':[0.78538 ,6.54623 ,0.05712 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.077 + 'Sl val':{'fa':[0.27075 ,0.83172 ,-0.77102 ,0.66842],'fb':[51.53678 ,11.14399 ,1.24201 ,1.02882],'fc':-0.00288,'Ne':11}, #Rw: 0.415 'Sl 4d':{'fa':[0.32921 ,-1.48715 ,0.81290 ,1.34473],'fb':[28.47291 ,1.34443 ,9.92580 ,1.20389],'fc':-0.00039,'Ne':10}, #Rw: 0.106 'Sl 5s':{'fa':[0.66214 ,-1.82562 ,0.53894 ,1.62372],'fb':[57.16789 ,10.98107 ,125.05872 ,10.10210],'fc':0.00040,'Ne':1}, #Rw: 0.568 }, - 'Cd':{'ZSlater':3.952,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[52.9087,17.6795,22.4764,8.9194,9.0141,11.1071,4.7533,4.4263,3.6677,0.0000,1.9762,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.112064, 48.9174, 1], [0.62779897, 41.8734, 2], [-0.23047701, 22.481, 2], [-0.381349, 60.4921, 3], [0.005948, 14.3028, 3], - [-0.003017, 10.1191, 3], [0.046241, 27.5584, 4], [0.000752, 6.4039, 4], [5.2e-05, 4.3838, 4], [-0.00015, 12.3363, 5], - [-8.2e-05, 2.8314, 5], [-3.6e-05, 1.7518, 5], [0.000162, 1.1119, 5]], - '2s': [[0.373238, 48.8627, 1], [0.059469, 41.8676, 2], [-0.99684697, 22.4835, 2], [0.050546, 60.4914, 3], [-0.226952, 14.2988, 3], - [0.25495201, 10.1201, 3], [-0.091285, 27.5563, 4], [0.01852, 6.4017, 4], [-0.01493, 4.3779, 4], [-0.134461, 12.3331, 5], - [0.008473, 2.8253, 5], [-0.007006, 1.7475, 5], [0.003546, 1.1006, 5]], - '3s': [[-0.16534699, 48.8606, 1], [-0.04536, 41.869, 2], [0.55574697, 22.4834, 2], [-0.026532, 60.4876, 3], [0.199045, 14.2963, 3], - [-1.63979006, 10.1361, 3], [0.1074, 27.5624, 4], [-0.033156, 6.4021, 4], [0.019377, 4.3775, 4], [0.362762, 12.3434, 5], - [0.013939, 2.8248, 5], [0.001973, 1.7468, 5], [2.1e-05, 1.0996, 5]], - '4s': [[0.071277, 48.8594, 1], [0.021823, 41.8698, 2], [-0.249798, 22.4791, 2], [0.011008, 60.4881, 3], [-0.097124, 14.3015, 3], - [0.90703797, 10.1464, 3], [-0.057837, 27.5563, 4], [-0.75404602, 6.4457, 4], [-0.47971001, 4.4017, 4], [-0.120229, 12.3203, 5], - [-0.008426, 2.8259, 5], [0.024172, 1.7469, 5], [0.009819, 1.0996, 5]], - '5s': [[0.016041, 48.8593, 1], [0.004874, 41.8698, 2], [-0.056875, 22.479, 2], [0.002731, 60.488, 3], [-0.018769, 14.3001, 3], - [0.205256, 10.1209, 3], [-0.012994, 27.5563, 4], [-0.247584, 6.4007, 4], [-0.057434, 4.3848, 4], [-0.016855, 12.3302, 5], - [0.34128401, 2.8444, 5], [0.545093, 1.7711, 5], [0.272156, 1.1264, 5]], - '2p': [[0.058284, 61.3259, 2], [0.46388501, 24.4549, 2], [0.08701, 50.9535, 3], [0.350106, 20.089, 3], [-0.004641, 11.445, 3], - [-0.054458, 8.8979, 3], [0.151089, 41.3998, 4], [0.082713, 6.4532, 4], [-0.080116, 4.2894, 4], [0.036822, 2.8056, 4]], - '3p': [[-0.010461, 61.27, 2], [-0.38343799, 24.3954, 2], [0.009753, 51.0261, 3], [-0.13584299, 20.1383, 3], [0.74792498, 11.4576, 3], - [0.38622901, 8.9096, 3], [-0.014783, 41.2709, 4], [0.009104, 6.4433, 4], [0.027342, 4.2746, 4], [-0.034769, 2.7727, 4]], - '4p': [[-0.003152, 61.2693, 2], [-0.164607, 24.3885, 2], [0.006554, 51.0224, 3], [-0.053459, 20.1411, 3], [0.29971299, 11.4477, 3], - [0.323612, 8.9038, 3], [-0.002815, 41.2719, 4], [-0.555381, 6.4644, 4], [-0.59987801, 4.2813, 4], [-0.07526, 2.7706, 4]], - '3d': [[0.016333, 27.1236, 3], [0.41378501, 15.2042, 3], [0.26204699, 7.5657, 3], [0.022138, 5.7573, 3], [0.37500101, 11.908, 4], - [-0.001753, 4.0763, 4], [-0.011016, 2.6005, 4], [0.000101, 1.6782, 4]], - '4d': [[0.004828, 27.119, 3], [0.129951, 15.2322, 3], [0.39886901, 7.5644, 3], [-0.516146, 5.7657, 3], [0.079276, 11.8849, 4], - [-0.49153799, 4.0726, 4], [-0.33702499, 2.5976, 4], [-0.091185, 1.6764, 4]],}, - 'Sl core':{'fa':[3.95922 ,19.24855 ,17.72152 ,0.00000],'fb':[21.53369 ,0.59571 ,7.00154 ,0.00000],'fc':5.06671,'Ne':1}, #Rw: 0.046 + 'Cd':{'Sl core':{'fa':[3.95922 ,19.24855 ,17.72152 ,0.00000],'fb':[21.53369 ,0.59571 ,7.00154 ,0.00000],'fc':5.06671,'Ne':1}, #Rw: 0.046 + 'Sl val':{'fa':[0.56483 ,1.35474 ,0.70009 ,-1.62061],'fb':[97.40582 ,8.93383 ,43.15813 ,10.02115],'fc':0.00045,'Ne':2}, #Rw: 0.553 'Sl 5s':{'fa':[-6.66093 ,14.17218 ,1.11822 ,-7.63719],'fb':[5.00794 ,4.50883 ,69.80720 ,4.19433],'fc':0.00160,'Ne':2}, #Rw: 0.993 }, - 'In':{'ZSlater':3.937,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[54.2850,18.1161,23.0185,9.1622,9.2629,11.4400,4.9165,4.5929,3.9007,0.0000,2.1492,1.6072,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,1,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.12141502, 49.9107, 1], [0.66213, 42.7337, 2], [-0.19007, 23.3267, 2], [-0.416215, 61.6048, 3], [0.016795, 15.4136, 3], - [0.00775, 10.5019, 3], [-0.013832, 28.3624, 4], [0.004624, 6.6373, 4], [-0.003388, 4.5993, 4], [-0.010272, 12.8878, 5], - [0.002046, 2.999, 5], [-0.001853, 1.9247, 5], [0.000978, 1.254, 5]], - '2s': [[0.37701899, 49.8453, 1], [0.046812, 42.7429, 2], [-0.98669302, 23.2945, 2], [0.066006, 61.6099, 3], [-0.55011398, 15.5186, 3], - [0.63485402, 10.6114, 3], [-0.001273, 28.3343, 4], [-0.033156, 6.6988, 4], [0.028778, 4.6537, 4], [-0.273278, 12.696, 5], - [-0.019519, 3.0509, 5], [0.017645, 1.961, 5], [-0.008694, 1.2809, 5]], - '3s': [[-0.165159, 49.8575, 1], [-0.044721, 42.7388, 2], [0.54152, 23.3285, 2], [-0.026272, 61.5832, 3], [0.19631501, 15.4085, 3], - [-1.60525703, 10.5204, 3], [0.115264, 28.3652, 4], [-0.021912, 6.6353, 4], [0.014689, 4.5947, 4], [0.328742, 12.8874, 5], - [0.008311, 2.9947, 5], [0.00172, 1.9172, 5], [0.000115, 1.2425, 5]], - '4s': [[0.070914, 49.8563, 1], [0.021293, 42.7396, 2], [-0.242851, 23.3252, 2], [0.011153, 61.5835, 3], [-0.098397, 15.412, 3], - [0.89134198, 10.5353, 3], [-0.060347, 28.3596, 4], [-0.74379998, 6.6904, 4], [-0.48632601, 4.6179, 4], [-0.10021, 12.8697, 5], - [-0.019855, 2.9968, 5], [0.013448, 1.9179, 5], [0.006003, 1.2426, 5]], - '5s': [[0.019656, 49.8562, 1], [0.006442, 42.7396, 2], [-0.067905, 23.3252, 2], [0.002563, 61.5834, 3], [-0.015083, 15.4102, 3], - [0.240678, 10.5057, 3], [-0.020337, 28.3599, 4], [-0.29840401, 6.6353, 4], [-0.08729, 4.6046, 4], [-0.011504, 12.8798, 5], - [0.380721, 3.0181, 5], [0.54822701, 1.9368, 5], [0.22148401, 1.2652, 5]], - '2p': [[0.117739, 50.4573, 2], [0.57481098, 19.8921, 2], [0.16790999, 41.9716, 3], [-0.122956, 12.3583, 3], [-0.154974, 8.9684, 3], - [0.21821301, 34.9165, 4], [0.144435, 6.0734, 4], [-0.108987, 4.0378, 4], [0.225016, 18.8517, 5], [0.056946, 2.5353, 5], - [-0.021755, 1.5508, 5], [-0.003246, 0.9804, 5]], - '3p': [[0.025235, 50.3618, 2], [0.55579603, 19.8975, 2], [0.001345, 42.1272, 3], [-0.96616203, 12.3769, 3], [-0.42699999, 8.9754, 3], - [0.020452, 34.7385, 4], [0.026976, 6.0605, 4], [-0.034716, 4.0233, 4], [0.17369799, 18.784, 5], [-0.001126, 2.52, 5], - [0.002036, 1.5365, 5], [0.000525, 0.9534, 5]], - '4p': [[0.010387, 50.3598, 2], [-0.434351, 19.8873, 2], [0.056191, 42.1292, 3], [0.73319203, 12.3767, 3], [0.128548, 8.9635, 3], - [0.063506, 34.7349, 4], [-0.695467, 6.0858, 4], [-0.464091, 4.0231, 4], [-0.191451, 18.7784, 5], [-0.016452, 2.5205, 5], - [0.005676, 1.5365, 5], [0.000844, 0.9534, 5]], - '5p': [[0.000647, 50.3601, 2], [0.075726, 19.884, 2], [-0.008993, 42.1273, 3], [-0.150769, 12.3729, 3], [-0.023759, 8.959, 3], - [-0.006657, 34.7383, 4], [0.196537, 6.0583, 4], [0.043764, 4.0268, 4], [0.036916, 18.7749, 5], [-0.29474199, 2.5353, 5], - [-0.54080802, 1.552, 5], [-0.32055801, 0.9683, 5]], - '3d': [[0.015782, 27.9361, 3], [0.39986199, 15.7299, 3], [0.16772, 8.4271, 3], [0.143838, 7.3876, 3], [0.353403, 12.396, 4], - [0.010429, 4.7209, 4], [-0.013507, 3.005, 4], [0.001594, 1.9244, 4]], - '4d': [[-0.005313, 27.9292, 3], [-0.102207, 15.7689, 3], [-0.99546301, 8.4246, 3], [0.856134, 7.4037, 3], [0.03477, 12.3634, 4], - [0.52308702, 4.71, 4], [0.39554101, 3.0078, 4], [0.094898, 1.9201, 4]],}, - 'Sl core':{'fa':[12.70850 ,12.99557 ,20.12990 ,0.00000],'fb':[0.92268 ,0.16674 ,8.06785 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.346 + 'In':{'Sl core':{'fa':[12.70850 ,12.99557 ,20.12990 ,0.00000],'fb':[0.92268 ,0.16674 ,8.06785 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.346 + 'Sl val':{'fa':[0.48967 ,1.82002 ,-2.09831 ,0.78714],'fb':[105.86905 ,8.61823 ,9.48742 ,40.27660],'fc':0.00059,'Ne':3}, #Rw: 0.720 'Sl 5s':{'fa':[-10.95007 ,9.53329 ,0.87450 ,1.54029],'fb':[10.86018 ,10.29700 ,62.82455 ,18.12012],'fc':0.00076,'Ne':2}, #Rw: 0.802 'Sl 5p':{'fa':[-2.68172 ,2.51397 ,0.44380 ,0.72326],'fb':[11.07752 ,10.56202 ,151.75945 ,70.30332],'fc':0.00042,'Ne':1}, #Rw: 0.460 }, - 'Sn':{'ZSlater':4.109,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[55.6809,18.5559,23.5635,9.4065,9.5124,11.7747,5.0819,4.7616,4.1239,0.0000,2.3061,1.8026,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,2,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.12740505, 50.9448, 1], [0.69851702, 43.6516, 2], [-0.24496301, 23.7619, 2], [-0.42171299, 63.198, 3], [0.020258, 15.3025, 3], - [0.003796, 10.783, 3], [0.027897, 29.009, 4], [0.002912, 6.929, 4], [-0.000971, 4.8522, 4], [-0.008316, 13.1628, 5], - [-0.000327, 3.1756, 5], [0.000163, 2.085, 5], [0.000449, 1.3899, 5]], - '2s': [[0.38266101, 50.8842, 1], [0.033295, 43.6497, 2], [-0.98045802, 23.7514, 2], [0.066973, 63.1962, 3], [-0.37601399, 15.334, 3], - [0.39673099, 10.8129, 3], [-0.057378, 28.9967, 4], [-0.075687, 6.9531, 4], [0.057895, 4.8686, 4], [-0.119895, 13.1181, 5], - [-0.037064, 3.192, 5], [0.034166, 2.0955, 5], [-0.017806, 1.3938, 5]], - '3s': [[-0.169927, 50.8862, 1], [-0.038794, 43.6511, 2], [0.54900903, 23.764, 2], [-0.029354, 63.184, 3], [0.25135601, 15.2964, 3], - [-1.692173, 10.8029, 3], [0.110922, 29.012, 4], [-0.033019, 6.9283, 4], [0.01541, 4.8465, 4], [0.37258601, 13.1675, 5], - [0.014797, 3.171, 5], [0.003317, 2.0784, 5], [0.000307, 1.3745, 5]], - '4s': [[0.075071, 50.8849, 1], [0.019728, 43.6518, 2], [-0.25338799, 23.7601, 2], [0.012364, 63.1845, 3], [-0.12187, 15.3019, 3], - [0.95347202, 10.8133, 3], [-0.06186, 29.0061, 4], [-0.72743303, 6.9713, 4], [-0.513116, 4.8818, 4], [-0.11544, 13.1468, 5], - [-0.015975, 3.173, 5], [0.01325, 2.0789, 5], [0.01062, 1.3745, 5]], - '5s': [[0.02196, 50.8848, 1], [0.005996, 43.6518, 2], [-0.074655, 23.76, 2], [0.003473, 63.1844, 3], [-0.029275, 15.3001, 3], - [0.28089601, 10.7864, 3], [-0.019835, 29.0062, 4], [-0.308456, 6.9289, 4], [-0.110108, 4.8605, 4], [-0.02072, 13.1563, 5], - [0.41095901, 3.2014, 5], [0.54241902, 2.0945, 5], [0.197211, 1.3955, 5]], - '2p': [[0.113559, 52.1525, 2], [0.59535301, 20.4382, 2], [0.157821, 43.2795, 3], [-0.136476, 12.5882, 3], [-0.16756301, 9.1906, 3], - [0.211382, 36.0944, 4], [0.16659001, 6.3356, 4], [-0.123648, 4.2795, 4], [0.242336, 19.4334, 5], [0.070467, 2.718, 5], - [-0.050939, 1.7179, 5], [0.011698, 1.1321, 5]], - '3p': [[0.027168, 52.0195, 2], [0.53881001, 20.4552, 2], [0.00554, 43.486, 3], [-0.97444999, 12.6019, 3], [-0.404836, 9.1925, 3], - [0.029711, 35.8493, 4], [0.03226, 6.313, 4], [-0.043784, 4.2567, 4], [0.16695599, 19.3369, 5], [0.011167, 2.7012, 5], - [0.007429, 1.7056, 5], [0.001824, 1.0938, 5]], - '4p': [[0.007324, 52.0163, 2], [-0.41365299, 20.4474, 2], [0.048624, 43.4883, 3], [0.72584403, 12.6123, 3], [0.128892, 9.1769, 3], - [0.053691, 35.8437, 4], [-0.68900198, 6.3353, 4], [-0.47397199, 4.2589, 4], [-0.17583799, 19.3212, 5], [-0.017463, 2.7016, 5], - [0.005575, 1.7056, 5], [0.001693, 1.0938, 5]], - '5p': [[0.000328, 52.0165, 2], [0.088894, 20.4438, 2], [-0.010224, 43.4869, 3], [-0.177212, 12.6064, 3], [-0.029128, 9.1728, 3], - [-0.007996, 35.8462, 4], [0.224913, 6.3105, 4], [0.063157, 4.2611, 4], [0.042683, 19.3192, 5], [-0.33402801, 2.719, 5], - [-0.54802197, 1.7151, 5], [-0.264743, 1.1016, 5]], - '3d': [[0.015637, 28.8907, 3], [0.390724, 16.2129, 3], [0.176588, 8.8894, 3], [0.16694, 7.6592, 3], [0.33311, 12.9118, 4], - [-0.002371, 4.9311, 4], [-0.010687, 3.1906, 4], [0.018975, 2.0486, 4]], - '4d': [[-0.005276, 28.8717, 3], [-0.096336, 16.3108, 3], [-0.96503699, 8.8884, 3], [0.79205602, 7.6792, 3], [0.053875, 12.8371, 4], - [0.54149902, 4.9199, 4], [0.38785499, 3.1915, 4], [0.073512, 2.0462, 4]],}, - 'Sl core':{'fa':[20.46385 ,11.81496 ,13.58833 ,0.00000],'fb':[7.18342 ,0.87589 ,0.16940 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.284 + 'Sn':{'Sl core':{'fa':[20.46385 ,11.81496 ,13.58833 ,0.00000],'fb':[7.18342 ,0.87589 ,0.16940 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.284 + 'Sl val':{'fa':[0.58148 ,-3.06919 ,2.71759 ,0.76855],'fb':[82.88873 ,9.07340 ,8.40260 ,32.52053],'fc':0.00074,'Ne':4}, #Rw: 0.726 'Sl 5s':{'fa':[0.88700 ,5.31893 ,-10.39451 ,5.18669],'fb':[51.04666 ,13.48608 ,10.96821 ,9.34054],'fc':0.00091,'Ne':2}, #Rw: 0.828 'Sl 5p':{'fa':[-10.85998 ,0.72188 ,0.53189 ,10.60522],'fb':[10.07102 ,48.15491 ,103.61417 ,9.90769],'fc':0.00058,'Ne':2}, #Rw: 0.009 }, - 'Sb':{'ZSlater':4.331,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[57.0973,18.9988,24.1111,9.6523,9.7629,12.1094,5.2490,4.9315,4.3400,0.0000,2.4548,1.9728,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,3,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.13581705, 51.9461, 1], [0.73680699, 44.5139, 2], [-0.22265001, 24.4611, 2], [-0.45421699, 64.416, 3], [0.031554, 16.0129, 3], - [0.011881, 11.1336, 3], [-0.018737, 29.7491, 4], [0.008297, 7.1912, 4], [-0.005491, 5.0968, 4], [-0.017797, 13.6252, 5], - [0.002708, 3.352, 5], [-0.002129, 2.2387, 5], [0.000906, 1.5215, 5]], - '2s': [[0.38620001, 51.8741, 1], [0.027256, 44.5214, 2], [-0.973616, 24.4513, 2], [0.07246, 64.446, 3], [-0.445315, 16.0727, 3], - [0.47493401, 11.2034, 3], [-0.037819, 29.7326, 4], [-0.058084, 7.2378, 4], [0.043938, 5.1337, 4], [-0.17119201, 13.5178, 5], - [-0.02641, 3.3904, 5], [0.02253, 2.267, 5], [-0.010006, 1.5306, 5]], - '3s': [[-0.172271, 51.8891, 1], [-0.035967, 44.5205, 2], [0.54332101, 24.4637, 2], [-0.030144, 64.3873, 3], [0.255844, 16.007, 3], - [-1.68218195, 11.153, 3], [0.115652, 29.7517, 4], [-0.034169, 7.1911, 4], [0.01633, 5.0924, 4], [0.360488, 13.6294, 5], - [0.015835, 3.3489, 5], [0.003939, 2.2326, 5], [0.000422, 1.5001, 5]], - '4s': [[0.076954, 51.8878, 1], [0.018835, 44.5213, 2], [-0.25413999, 24.46, 2], [0.012907, 64.3878, 3], [-0.124048, 16.0123, 3], - [0.95633799, 11.1659, 3], [-0.064684, 29.7456, 4], [-0.71258098, 7.2361, 4], [-0.529589, 5.134, 4], [-0.103135, 13.6096, 5], - [-0.021705, 3.351, 5], [0.009963, 2.2339, 5], [0.013539, 1.5004, 5]], - '5s': [[-0.024366, 51.8877, 1], [-0.006333, 44.5213, 2], [0.080936, 24.46, 2], [-0.003728, 64.3876, 3], [0.031533, 16.0102, 3], - [-0.30550599, 11.1376, 3], [0.023205, 29.7459, 4], [0.326188, 7.1921, 4], [0.137594, 5.1101, 4], [0.018811, 13.6187, 5], - [-0.43550101, 3.3846, 5], [-0.53582001, 2.2475, 5], [-0.18190099, 1.521, 5]], - '2p': [[0.111221, 53.8163, 2], [0.60235798, 20.9844, 2], [0.151536, 44.6826, 3], [-0.151948, 12.8178, 3], [-0.17194, 9.3787, 3], - [0.213651, 37.1543, 4], [0.175944, 6.5698, 4], [-0.124985, 4.5078, 4], [0.257296, 19.9519, 5], [0.058571, 2.8965, 5], - [-0.045746, 1.8738, 5], [0.006145, 1.2659, 5]], - '3p': [[0.028467, 53.7068, 2], [0.52509302, 20.9976, 2], [0.008655, 44.8524, 3], [-0.97828102, 12.8396, 3], [-0.382799, 9.3807, 3], - [0.035501, 36.945, 4], [0.030466, 6.5549, 4], [-0.036996, 4.4875, 4], [0.154442, 19.8675, 5], [-0.001903, 2.8845, 5], - [0.003258, 1.864, 5], [0.0012, 1.2205, 5]], - '4p': [[0.005036, 53.7046, 2], [-0.39854699, 20.9877, 2], [0.043074, 44.8548, 3], [0.73049599, 12.84, 3], [0.122985, 9.3702, 3], - [0.046293, 36.94, 4], [-0.68479103, 6.5783, 4], [-0.48154399, 4.4909, 4], [-0.165573, 19.8612, 5], [-0.017771, 2.8849, 5], - [0.005655, 1.8641, 5], [0.002295, 1.2205, 5]], - '5p': [[-0.0014, 53.7047, 2], [0.10649, 20.9841, 2], [-0.012484, 44.8533, 3], [-0.202428, 12.8335, 3], [-0.034435, 9.3661, 3], - [-0.012157, 36.9426, 4], [0.239485, 6.5536, 4], [0.091922, 4.4912, 4], [0.04945, 19.8587, 5], [-0.35657501, 2.8974, 5], - [-0.551355, 1.8668, 5], [-0.23661, 1.222, 5]], - '3d': [[0.132346, 18.7521, 3], [0.70876002, 11.742, 3], [0.205292, 8.1279, 3], [0.020913, 25.4657, 3], [-0.02722, 14.97, 4], - [0.035989, 5.6957, 4], [-0.021158, 3.7089, 4], [-0.023041, 2.3638, 4]], - '4d': [[0.029465, 18.7549, 3], [-0.750539, 11.7267, 3], [0.150867, 8.1291, 3], [-0.007137, 25.4647, 3], [0.277657, 14.9735, 4], - [0.50299102, 5.6961, 4], [0.48079899, 3.7025, 4], [0.096138, 2.3638, 4]],}, - 'Sl core':{'fa':[14.49262 ,10.66136 ,20.73932 ,0.00000],'fb':[0.17550 ,0.85459 ,6.46246 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.236 + 'Sb':{'Sl core':{'fa':[14.49262 ,10.66136 ,20.73932 ,0.00000],'fb':[0.17550 ,0.85459 ,6.46246 ,0.00000],'fc':0.00000,'Ne':1}, #Rw: 0.236 + 'Sl val':{'fa':[0.71610 ,0.84715 ,-2.90818 ,2.34304],'fb':[64.96007 ,23.56862 ,9.30731 ,8.25981],'fc':0.00090,'Ne':5}, #Rw: 0.794 'Sl 5s':{'fa':[0.86373 ,-26.29037 ,14.17033 ,12.25461],'fb':[43.57209 ,10.09478 ,11.42017 ,8.96450],'fc':0.00103,'Ne':2}, #Rw: 0.935 'Sl 5p':{'fa':[0.94843 ,40.54416 ,5.92158 ,-46.41658],'fb':[67.01327 ,14.29394 ,11.42193 ,13.83767],'fc':0.00083,'Ne':3}, #Rw: 0.724 }, - 'Te':{'ZSlater':4.571,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[58.5349,19.4452,24.6615,9.8995,10.0142,12.4458,5.4176,5.1024,4.5506,0.0000,2.5980,2.1289,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,4,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.14411604, 52.9571, 1], [0.77104402, 45.4338, 2], [-0.226015, 24.8619, 2], [-0.476861, 65.6873, 3], [0.03879, 16.0425, 3], - [0.011864, 11.4233, 3], [-0.026433, 30.4517, 4], [0.010448, 7.4615, 4], [-0.006825, 5.3385, 4], [-0.020818, 13.9308, 5], - [0.003374, 3.4936, 5], [-0.002748, 2.3555, 5], [0.001174, 1.6079, 5]], - '2s': [[0.389846, 52.8944, 1], [0.017784, 45.4442, 2], [-0.97673899, 24.8634, 2], [0.073035, 65.6828, 3], [-0.346553, 16.0521, 3], - [0.35069999, 11.4374, 3], [-0.064276, 30.4439, 4], [-0.064238, 7.4719, 4], [0.045657, 5.3398, 4], [-0.102383, 13.9043, 5], - [-0.025042, 3.5003, 5], [0.020867, 2.3515, 5], [-0.009139, 1.5985, 5]], - '3s': [[-0.174647, 52.9009, 1], [-0.030888, 45.4456, 2], [0.54883802, 24.8655, 2], [-0.03209, 65.6486, 3], [0.30361599, 16.0319, 3], - [-1.76055205, 11.4401, 3], [0.110268, 30.4572, 4], [-0.038308, 7.4631, 4], [0.022289, 5.3321, 4], [0.39497399, 13.9451, 5], - [0.004711, 3.4922, 5], [-0.004894, 2.3444, 5], [-0.003213, 1.5918, 5]], - '4s': [[0.07886, 52.8992, 1], [0.016681, 45.4465, 2], [-0.25988901, 24.8601, 2], [0.014133, 65.649, 3], [-0.15728299, 16.0425, 3], - [1.02083695, 11.456, 3], [-0.061448, 30.4484, 4], [-0.69878203, 7.5038, 4], [-0.54810703, 5.3804, 4], [-0.123896, 13.9127, 5], - [-0.022457, 3.4941, 5], [0.004353, 2.3456, 5], [0.012786, 1.592, 5]], - '5s': [[0.026605, 52.8991, 1], [0.006098, 45.4465, 2], [-0.088512, 24.86, 2], [0.00443, 65.6488, 3], [-0.04512, 16.0402, 3], - [0.348369, 11.4278, 3], [-0.023104, 30.4485, 4], [-0.345539, 7.4646, 4], [-0.159169, 5.3529, 4], [-0.025437, 13.9229, 5], - [0.47524801, 3.5317, 5], [0.52668798, 2.3598, 5], [0.153743, 1.6083, 5]], - '2p': [[0.109527, 55.5198, 2], [0.60705602, 21.5241, 2], [0.146062, 46.0646, 3], [-0.169999, 13.0417, 3], [-0.173576, 9.562, 3], - [0.21680801, 38.2505, 4], [0.18740401, 6.7737, 4], [-0.13046999, 4.7088, 4], [0.27257401, 20.511, 5], [0.051866, 3.055, 5], - [-0.017524, 1.9738, 5], [-0.003521, 1.3119, 5]], - '3p': [[0.029245, 55.41, 2], [0.51366103, 21.5377, 2], [0.011958, 46.229, 3], [-0.97627902, 13.0806, 3], [-0.35529, 9.5554, 3], - [0.038274, 38.0431, 4], [0.016076, 6.7579, 4], [-0.018808, 4.694, 4], [0.13393, 20.4066, 5], [-0.008672, 3.0444, 5], - [-0.001598, 1.9579, 5], [-0.000188, 1.2735, 5]], - '4p': [[0.003624, 55.409, 2], [-0.39227399, 21.5246, 2], [0.040148, 46.2318, 3], [0.75415301, 13.064, 3], [0.10363, 9.5513, 3], - [0.041678, 38.0394, 4], [-0.68951601, 6.7863, 4], [-0.47724399, 4.6958, 4], [-0.162233, 20.4141, 5], [-0.016935, 3.045, 5], - [0.005241, 1.958, 5], [0.001676, 1.2735, 5]], - '5p': [[-0.000673, 55.4092, 2], [0.111521, 21.5219, 2], [-0.012637, 46.2297, 3], [-0.22717801, 13.0601, 3], [-0.028541, 9.5472, 3], - [-0.01095, 38.0427, 4], [0.26344901, 6.7579, 4], [0.096914, 4.6987, 4], [0.052725, 20.4073, 5], [-0.405267, 3.0608, 5], - [-0.53804398, 1.9607, 5], [-0.21018399, 1.2764, 5]], - '3d': [[0.147617, 19.1472, 3], [0.68714303, 11.9238, 3], [0.194249, 8.288, 3], [0.019272, 26.1141, 3], [-0.004295, 15.2951, 4], - [0.026508, 5.8289, 4], [-0.012892, 3.8608, 4], [-0.017333, 2.4677, 4]], - '4d': [[0.024778, 19.1504, 3], [-0.77398902, 11.9173, 3], [0.186424, 8.291, 3], [-0.007942, 26.1133, 3], [0.26977199, 15.2922, 4], - [0.522268, 5.8231, 4], [0.46198201, 3.8594, 4], [0.07802, 2.4666, 4]],}, - 'Sl core':{'fa':[-56.79571 ,78.56645 ,19.18239 ,0.00000],'fb':[5.66415 ,5.66265 ,0.46404 ,0.00000],'fc':4.93180,'Ne':1}, #Rw: 0.228 + 'Te':{'Sl core':{'fa':[-56.79571 ,78.56645 ,19.18239 ,0.00000],'fb':[5.66415 ,5.66265 ,0.46404 ,0.00000],'fc':4.93180,'Ne':1}, #Rw: 0.228 + 'Sl val':{'fa':[0.83741 ,8.60274 ,-19.84204 ,11.39927],'fb':[53.72099 ,12.34330 ,10.41602 ,9.39656],'fc':0.00106,'Ne':6}, #Rw: 0.865 'Sl 5s':{'fa':[24.14419 ,-11.88651 ,1.25831 ,-12.52395],'fb':[3.33753 ,3.05566 ,31.31223 ,3.71768],'fc':0.00452,'Ne':2}, #Rw: 0.552 'Sl 5p':{'fa':[0.87870 ,-17.45773 ,0.78133 ,16.79586],'fb':[22.28209 ,9.25385 ,62.45329 ,9.06703],'fc':0.00087,'Ne':4}, #Rw: 0.789 }, - 'I':{'ZSlater':4.815,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[59.9945,19.8950,25.2137,10.1482,10.2664,12.7810,5.5873,5.2739,4.7570,0.0000,2.7372,2.2758,], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [2,5,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.152282, 53.9829, 1], [0.81013203, 46.3647, 2], [-0.24406201, 25.3218, 2], [-0.496622, 67.178, 3], [0.05067, 16.5331, 3], - [0.016722, 11.7407, 3], [-0.029926, 31.2017, 4], [0.013232, 7.7625, 4], [-0.008523, 5.6005, 4], [-0.027287, 14.3187, 5], - [0.004032, 3.6923, 5], [-0.003172, 2.5179, 5], [0.001301, 1.7247, 5]], - '2s': [[0.34815899, 53.9234, 1], [0.18228, 46.3794, 2], [-1.07372403, 25.3273, 2], [-0.009223, 67.1358, 3], [-0.27229199, 16.5265, 3], - [0.286477, 11.7406, 3], [-0.036414, 31.2004, 4], [0.000932, 7.7566, 4], [1.3e-05, 5.5962, 4], [-0.135333, 14.3216, 5], - [-0.000191, 3.6878, 5], [0.000736, 2.5059, 5], [-5.4e-05, 1.7112, 5]], - '3s': [[0.177146, 53.9247, 1], [0.024769, 46.3771, 2], [-0.550102, 25.3244, 2], [0.03429, 67.1358, 3], [-0.32351699, 16.5256, 3], - [1.78261197, 11.766, 3], [-0.105344, 31.2035, 4], [0.036171, 7.7573, 4], [-0.012554, 5.5965, 4], [-0.40244901, 14.3237, 5], - [-0.01838, 3.6877, 5], [-0.004146, 2.5058, 5], [-3e-06, 1.7111, 5]], - '4s': [[0.080769, 53.9233, 1], [0.014181, 46.3778, 2], [-0.26304501, 25.3202, 2], [0.015041, 67.1363, 3], [-0.163721, 16.5326, 3], - [1.03835702, 11.7761, 3], [-0.061174, 31.1981, 4], [-0.67423201, 7.7972, 4], [-0.57651597, 5.6518, 4], [-0.121234, 14.3027, 5], [-0.023987, 3.6901, 5], [-0.001476, 2.5071, 5], [0.011268, 1.7114, 5]], - '5s': [[0.035862, 53.9232, 1], [-0.008417, 46.3777, 2], [-0.124501, 25.3199, 2], [0.009002, 67.1357, 3], [0.022972, 16.5299, 3], - [0.40169799, 11.7623, 3], [-0.003932, 31.197, 4], [-0.53947699, 7.7935, 4], [0.105356, 5.6526, 4], [-0.16670001, 14.295, 5], - [0.59452099, 3.8956, 5], [0.41565701, 2.6421, 5], [0.085854, 1.791, 5]], - '2p': [[0.105832, 57.2223, 2], [0.62902302, 22.0879, 2], [0.136179, 47.4727, 3], [-0.186358, 13.2551, 3], [-0.165003, 9.723, 3], - [0.207976, 39.4238, 4], [0.203311, 7.0059, 4], [-0.15099899, 4.9344, 4], [0.27515301, 21.0914, 5], [0.05925, 3.2642, 5], - [-0.00781, 2.1112, 5], [-0.016241, 1.4083, 5]], - '3p': [[0.031337, 57.1094, 2], [0.49548799, 22.108, 2], [0.014479, 47.6364, 3], [-0.99640101, 13.2865, 3], [-0.33095601, 9.7169, 3], - [0.049095, 39.2052, 4], [0.030052, 6.9879, 4], [-0.032231, 4.9218, 4], [0.13294899, 20.9886, 5], [-0.009828, 3.2524, 5], - [0.001323, 2.1021, 5], [0.001033, 1.3652, 5]], - '4p': [[0.001328, 57.1079, 2], [-0.373023, 22.0976, 2], [0.033793, 47.6379, 3], [0.74313402, 13.2865, 3], [0.096364, 9.7089, 3], - [0.03441, 39.2022, 4], [-0.68688703, 7.003, 4], [-0.48062, 4.9317, 4], [-0.140678, 20.9817, 5], [-0.012256, 3.2525, 5], [-0.006301, 2.1022, 5], [-0.003129, 1.3652, 5]], - '5p': [[-0.000744, 57.1079, 2], [0.119073, 22.0928, 2], [-0.012916, 47.6376, 3], [-0.25075099, 13.2725, 3], [-0.023291, 9.7066, 3], - [-0.011479, 39.2032, 4], [0.27588499, 6.9885, 4], [0.117769, 4.9263, 4], [0.054999, 20.9847, 5], [-0.42119801, 3.2678, 5], [-0.534401, 2.1042, 5], [-0.206604, 1.3674, 5]], - '3d': [[0.16850901, 19.5418, 3], [0.60016, 12.1023, 3], [0.18269201, 8.4832, 3], [0.020709, 26.9298, 3], [0.07097, 15.6239, 4], - [0.05498, 5.9429, 4], [-0.062849, 3.9828, 4], [0.013499, 2.5322, 4]], - '4d': [[0.014424, 19.5621, 3], [-0.77022099, 12.0801, 3], [0.222075, 8.5013, 3], [-0.008932, 26.9248, 3], [0.24042, 15.6005, 4], - [0.55466998, 5.9171, 4], [0.427773, 3.9924, 4], [0.064253, 2.5268, 4]],}, - 'Sl core':{'fa':[1.72427 ,20.91860 ,19.12700 ,0.00000],'fb':[13.21005 ,4.71710 ,0.39526 ,0.00000],'fc':4.23296,'Ne':1}, #Rw: 0.041 + 'I':{'Sl core':{'fa':[1.72427 ,20.91860 ,19.12700 ,0.00000],'fb':[13.21005 ,4.71710 ,0.39526 ,0.00000],'fc':4.23296,'Ne':1}, #Rw: 0.041 + 'Sl val':{'fa':[0.62234 ,0.80697 ,-2.09872 ,1.66749],'fb':[51.95873 ,19.35193 ,6.65149 ,5.78095],'fc':0.00101,'Ne':7}, #Rw: 0.679 'Sl 5s':{'fa':[19.37668 ,-35.52522 ,0.80817 ,16.34009],'fb':[6.91992 ,6.17421 ,25.07916 ,5.50906],'fc':-0.00024,'Ne':2}, #Rw: 0.895 'Sl 5p':{'fa':[11.38539 ,2.86512 ,0.85508 ,-14.10787],'fb':[9.07635 ,13.92675 ,52.02768 ,9.69140],'fc':0.00103,'Ne':5}, #Rw: 0.860 }, - 'Xe':{'ZSlater':4.815,'NSlater':[4,4,6,8,10,12,14,16],'SZE':[61.4771,20.3484,25.7687,10.3985,10.5194,13.1165,5.7581,5.4458,4.9600,0.0000,2.8732,2.4161], - 'popCore':[[2,], [2,6,], [2,6,10,], [2,6,10,0,], [2,6,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'popVal':[[0,], [0,0,], [0,0,0,], [0,0,0,0,], [0,0,0,0,0,], [0,0,0,0,0,0], [0,0,0,0,0,0,0]], - 'Slater':{ - '1s': [[-1.16171706, 54.9751, 1], [0.84649903, 47.2282, 2], [-0.228966, 26.0963, 2], [-0.52580601, 68.2402, 3], [0.066167, 16.8327, 3], - [0.019168, 12.0744, 3], [-0.059603, 31.907, 4], [0.016083, 8.0158, 4], [-0.010288, 5.8478, 4], [-0.033225, 14.7183, 5], - [0.004619, 3.8624, 5], [-0.003598, 2.644, 5], [0.001454, 1.8275, 5]], - '2s': [[0.39772701, 54.8543, 1], [-0.001134, 47.2403, 2], [-0.94584, 26.102, 2], [0.074539, 68.3953, 3], [-0.50350201, 16.8558, 3], - [0.65107203, 12.077, 3], [-0.076451, 31.8579, 4], [0.029704, 8.0156, 4], [-0.023503, 5.8439, 4], [-0.32999399, 14.6986, 5], - [0.010316, 3.8565, 5], [-0.006195, 2.6367, 5], [0.001303, 1.8248, 5]], - '3s': [[-0.179509, 54.9195, 1], [-0.021999, 47.2494, 2], [0.54476702, 26.0987, 2], [-0.036063, 68.1769, 3], [0.37497601, 16.8232, 3], - [-1.84897602, 12.0996, 3], [0.108746, 31.9093, 4], [-0.037983, 8.0155, 4], [0.014214, 5.8402, 4], [0.42547101, 14.7258, 5], - [0.016844, 3.8556, 5], [0.002511, 2.6343, 5], [-0.000803, 1.8124, 5]], - '4s': [[0.082662, 54.918, 1], [0.013133, 47.2499, 2], [-0.263293, 26.0944, 2], [0.015994, 68.1773, 3], [-0.19258501, 16.8325, 3], - [1.08512294, 12.1106, 3], [-0.063353, 31.9029, 4], [-0.66102999, 8.0544, 4], [-0.59176397, 5.9034, 4], [-0.131836, 14.7006, 5], - [-0.025638, 3.8579, 5], [-0.006498, 2.6361, 5], [0.013328, 1.8128, 5]], - '5s': [[-0.030638, 54.9179, 1], [-0.005246, 47.25, 2], [0.09834, 26.0942, 2], [-0.005602, 68.1771, 3], [0.064604, 16.83, 3], - [-0.41185701, 12.081, 3], [0.026191, 31.903, 4], [0.35839099, 8.0181, 4], [0.21926799, 5.871, 4], [0.032632, 14.7114, 5], - [-0.50935799, 3.9059, 5], [-0.51836801, 2.648, 5], [-0.141008, 1.8302, 5]], - '2p': [[0.10613, 58.8637, 2], [0.62103999, 22.6117, 2], [0.134729, 48.8362, 3], [-0.203557, 13.4863, 3], [-0.16957501, 9.8495, 3], - [0.21768899, 40.4394, 4], [0.20466501, 7.1953, 4], [-0.13551, 5.1464, 4], [0.29366699, 21.6209, 5], [0.028426, 3.4586, 5], - [0.019913, 2.2474, 5], [-0.029348, 1.4911, 5]], - '3p': [[0.032168, 58.7748, 2], [0.48929, 22.619, 2], [0.015899, 48.9676, 3], [-1.03285301, 13.5021, 3], [-0.29799801, 9.8476, 3], - [0.053877, 40.2664, 4], [0.043409, 7.1859, 4], [-0.050588, 5.1293, 4], [0.13635001, 21.548, 5], [0.007088, 3.4472, 5], - [0.012383, 2.2384, 5], [0.004081, 1.4588, 5]], - '4p': [[0.000167, 58.7712, 2], [-0.36997101, 22.6101, 2], [0.032302, 48.9722, 3], [0.77873802, 13.5081, 3], [0.072874, 9.8364, 3], - [0.029888, 40.256, 4], [-0.69348198, 7.2141, 4], [-0.47064799, 5.1326, 4], [-0.145079, 21.5389, 5], [-0.021965, 3.4475, 5], - [-0.000841, 2.2386, 5], [-0.000408, 1.4588, 5]], - '5p': [[-0.00087, 58.7712, 2], [0.128782, 22.6072, 2], [-0.013356, 48.9703, 3], [-0.27712601, 13.5011, 3], [-0.019517, 9.833, 3], - [-0.012003, 40.2589, 4], [0.295268, 7.1852, 4], [0.13601001, 5.1345, 4], [0.057587, 21.5325, 5], [-0.43676299, 3.4621, 5], - [-0.53351903, 2.2393, 5], [-0.198092, 1.4607, 5]], - '3d': [[0.185, 19.9627, 3], [0.56606501, 12.2424, 3], [0.170004, 8.6925, 3], [0.019223, 27.7437, 3], [0.104114, 15.9521, 4], - [0.050135, 6.06, 4], [-0.04702, 4.0993, 4], [-0.006577, 2.5865, 4]], - '4d': [[0.002695, 19.9787, 3], [-0.801759, 12.2144, 3], [0.26429799, 8.7, 3], [-0.006653, 27.7398, 3], [0.23758, 15.9408, 4], - [0.58332402, 6.0578, 4], [0.40301499, 4.0935, 4], [0.046825, 2.586, 4]],}, - }, } + #electron form factor coefficients to stl=6.0; those as M+0 are 4 term values from SHELX: match the 5 term ones ElecFF = { diff --git a/GSASII/config_example.py b/GSASII/config_example.py index 668500897..587bc767a 100644 --- a/GSASII/config_example.py +++ b/GSASII/config_example.py @@ -1,29 +1,31 @@ # -*- coding: utf-8 -*- #config.py - Variables used to set optional configuration options ''' -This file contains optional configuration options for GSAS-II. The variables -in this file can be copied to file config.py, which is imported if present. -Access these variables using :func:`GSASIIpath.GetConfigValue`, which returns -None if the variable is not set. Note that a config.py file need not -be present, but if in use it will typically be found with the GSAS-II source -directory (GSASIIpath.Path2GSAS2) or a directory for local GSAS-II -modifications (~/.G2local/ or /Documents and Settings//.G2local/). -Note that the contents of config.py is usually changed -using :func:`GSASIIctrlGUI.SelectConfigSetting`. - -When defining new config variables for GSAS-II, define them here with a -default value: use None or a string for strings, or use integers or real -values as defaults to ensure that only values of that type are allowed. -Include a doc string after each variable is defined to explain -what it does. +This file contains optional configuration options for GSAS-II. The +values for the variables named here will be set in file ~/.GSASII/config.ini +which is read on startup by :func:`GSASIIpath.LoadConfig`. To check +if a configuration variable has been set use +:func:`GSASIIpath.GetConfigValue`, which returns +None if the variable is not set. +Values are typically changed using :func:`GSASIIctrlGUI.SelectConfigSetting` +which uses :func:`GSASIIctrlGUI.SaveConfigVars` to write the +~/.GSASII/config.ini file. + +To define new config variables for GSAS-II, define them here with a +default value: use None or a string for strings. If an integer or real +values is used as a default, the routines will ensure that this type +is preserved for any user setting. +Always include a doc string after defining each variable. This definition +will be shown in the GUI to explain what the variable does. If a name ends with a particular keyword, then specialized edit -routines are used. +routines are used. -* Names ending in _location or _directory are for items +* Names ending in _location or _directory are for path items * Names ending in _exec for executable files (.exe on windows). * Names ending in _color for colors, to be specified as RGBA values (note that Contour_color is restricted to color maps). +* Names ending in _pos or _Size are integer tuples for wx sizes or positions. For example:: @@ -55,12 +57,6 @@ Transpose = False 'Set to True to cause images to be Transposed when read (for code development)' -Enable_logging = False -'Set to True to enable use of command logging (under development.)' - -logging_debug = False -'Set to True to enable debug for logging (under development.)' - Help_mode = "browser" '''Set to "internal" to use a Python-based web viewer to display help documentation and tutorials. If set to the default ("browser") @@ -103,11 +99,6 @@ home directory can be specified with a '~'. ''' -wxInspector = False -'''If set to True, the wxInspector widget is displayed when -GSAS-II is started. -''' - Spot_mask_diameter = 1.0 '''Specifies the default diameter for creation of spot masks. Default is 1.0 mm ''' @@ -137,19 +128,22 @@ ''' Main_Size = (700,450) '''Main window size (width, height) - initially uses wx.DefaultSize but will updated - and saved as the user changes the window +and saved as the user changes the window. +This is used internally by GSAS-II and would not normally be changed by a user. ''' Main_Pos = (100,100) '''Main window location - will be updated & saved when user moves -it. If position is outside screen then it will be repositioned to default -''' +it. If position is outside screen then it will be repositioned to default. +This is used internally by GSAS-II and would not normally be changed by a user. ''' Plot_Size = (700,600) -'''Plot window size (width, height) - initially uses wx.DefaultSize but will updated - and saved as the user changes the window +'''Plot window size (width, height) - initially uses wx.DefaultSize but will +updated and saved as the user changes the window. +This is used internally by GSAS-II and would not normally be changed by a user. ''' Plot_Pos = (200,200) '''Plot window location - will be updated & saved when user moves it -these widows. If position is outside screen then it will be repositioned to default +these widows. If position is outside screen then it will be repositioned to default. +This is used internally by GSAS-II and would not normally be changed by a user. ''' Tick_length = 8.0 @@ -234,7 +228,8 @@ ''' previous_GPX_files = [] -'''A list of previously used .gpx files +'''A list of previously used .gpx files. This is used internally by GSAS-II +and would not normally be changed by a user. ''' Image_calibrant = '' @@ -297,12 +292,6 @@ change to take effect. Default is False. ''' -svn_exec = None -'''Defines the full path to a subversion executable. -If None (the default), GSAS-II will search for a svn or svn.exe file -in the current path or in the location where the current Python is located. -''' - G2RefinementWindow = False '''When True a custom progress window is displayed to track the progress of refinements. When False a generic wxpython supplied progress diff --git a/GSASII/exports/G2export_Bracket.py b/GSASII/exports/G2export_Bracket.py index b676cd538..340a58c5c 100644 --- a/GSASII/exports/G2export_Bracket.py +++ b/GSASII/exports/G2export_Bracket.py @@ -1,12 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +'''Classes in :mod:`~GSASII.exports.G2export_Bracket` follow. +''' # This module initially written by Conrad Gillard. For any enquiries please contact conrad.gillard@gmail.com # Export3col exporter adapted from Exportbracket by BHT from __future__ import division, print_function -import GSASIIfiles as G2fil +from .. import GSASIIfiles as G2fil from collections import OrderedDict -from GSASIImath import ValEsd +from ..GSASIImath import ValEsd class Exportbracket(G2fil.ExportBaseclass): '''Enables export of parameters that are commonly needed for publications, in bracket notation @@ -18,7 +20,7 @@ def __init__(self, G2frame): self.exporttype = ['project'] def Exporter(self, event=None): - + # Define function to extract parameter and sigma from covariances, for later use def GetParamSig(phase_num, hist_num, keyword, display_name): param_index = None @@ -191,7 +193,7 @@ def GetParamSig(phase_num, hist_num, keyword, display_name): # for parameter_value in model_parameters.values(): # parameter_values = parameter_values + str(parameter_value) + ", " # self.Write(parameter_values[0:-2]) - + for name in model_parameters: self.Write('%s, %s,'%(name,model_parameters[name])) @@ -215,17 +217,17 @@ def __init__(self, G2frame): self.exporttype = ['project'] def ValEsd2col(self, param, param_sig): - '''Return two values with the formated value as the first number and the + '''Return two values with the formated value as the first number and the standard uncertainty (if provided) as the second value. ''' col1 = ValEsd(param, -abs(param_sig)) col2 = '' if param_sig > 0: col2 = ValEsd(param_sig, -param_sig/100) - return col1,col2 - + return col1,col2 + def Exporter(self, event=None): - + # Define function to extract parameter and sigma from covariances, for later use def GetParamSig(phase_num, hist_num, keyword, display_name): param_index = None @@ -271,7 +273,7 @@ def GetParamSig(phase_num, hist_num, keyword, display_name): for i in range(0, len(cellList)): # for cell in cellList: if cellSig[i] > 0: - # Formulate lattice parameter + # Formulate lattice parameter model_parameters[phasenam + " " + lp_letter + " (Å)"] = self.ValEsd2col(cellList[i], cellSig[i]) # Increment lattice parameter letter lp_letter = chr(ord(lp_letter[0]) + 1) @@ -398,4 +400,3 @@ def GetParamSig(phase_num, hist_num, keyword, display_name): except: pass self.CloseFile() - diff --git a/GSASII/exports/G2export_CIF.py b/GSASII/exports/G2export_CIF.py index 9e653a986..b93a3ffac 100644 --- a/GSASII/exports/G2export_CIF.py +++ b/GSASII/exports/G2export_CIF.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_CIF` follow: +'''Classes in :mod:`~GSASII.exports.G2export_CIF` follow: ''' # note documentation in docs/source/exports.rst # @@ -12,13 +12,15 @@ import copy import re interactive = False +G2G = None # used in Project export try: + # TODO: code that calls wx should be hidden in GUI modules that are imported only when needed import wx import wx.lib.scrolledpanel as wxscroll import wx.lib.resizewidget as rw interactive = True - import GSASIIctrlGUI as G2G except ImportError: + wx = None # Avoid wx dependency for Scriptable class Placeholder(object): def __init__(self): @@ -28,16 +30,16 @@ def __init__(self): self.ScrolledPanel = object wx = Placeholder() wxscroll = Placeholder() -import GSASIIpath -import GSASIIobj as G2obj -import GSASIImath as G2mth -import GSASIIspc as G2spc -import GSASIIlattice as G2lat -import GSASIIstrMain as G2stMn -import GSASIIstrIO as G2stIO -import GSASIImapvars as G2mv -import GSASIIElem as G2el -import GSASIIfiles as G2fil +from .. import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIImath as G2mth +from .. import GSASIIspc as G2spc +from .. import GSASIIlattice as G2lat +from .. import GSASIIstrMain as G2stMn +from .. import GSASIIstrIO as G2stIO +from .. import GSASIImapvars as G2mv +from .. import GSASIIElem as G2el +from .. import GSASIIfiles as G2fil DEBUG = False #True to skip printing of reflection/powder profile lists @@ -55,7 +57,7 @@ def getCellwStrain(phasedict,seqData,pId,histname): #newCellDict = {} #if name in seqData and 'newCellDict' in seqData[histname]: # newCellDict.update(seqData[histname]['newCellDict']) - + pfx = str(pId)+'::' # prefix for A values from phase Albls = [pfx+'A'+str(i) for i in range(6)] Avals = G2lat.cell2A(phasedict['General']['Cell'][1:7]) @@ -68,7 +70,7 @@ def getCellwStrain(phasedict,seqData,pId,histname): #AiLookup[seqData[histname]['newCellDict'][pfx+v][0]] = pfx+v DijLookup[pfx+v] = seqData[histname]['newCellDict'][pfx+v][0] covData = { # relabeled with p:h:Dij as p::Ai - 'varyList': [DijLookup.get(G2fil.striphist(v),v) for v in seqData[histname]['varyList']], + 'varyList': [DijLookup.get(G2fil.striphist(v),v) for v in seqData[histname]['varyList']], 'covMatrix': seqData[histname]['covMatrix']} # apply symmetry cellDict = dict(zip(Albls,Avals)) @@ -82,14 +84,14 @@ def getCellwStrain(phasedict,seqData,pId,histname): return cell,cE def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls): - '''Setup sequential results table (based on code from - GSASIIseqGUI.UpdateSeqResults) - - TODO: This should be merged with the table build code in - GSASIIseqGUI.UpdateSeqResults and moved to somewhere non-GUI - like GSASIIstrIO to create a single routine that can be used - in both places, but this means returning some - of the code that has been removed from there + '''Setup sequential results table (based on code from + :func:`GSASII.GSASIIseqGUI.UpdateSeqResults`) + + TODO: This should be merged with the table build code in + :func:`GSASII.GSASIIseqGUI.UpdateSeqResults` and moved to somewhere non-GUI + like :mod:`~GSASII.GSASIIstrIO` to create a single routine that can be used + in both places, but this means returning some + of the code that has been removed from there. ''' newAtomDict = seqData[seqHistList[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute @@ -137,7 +139,7 @@ def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls): break else: # should not happen uniqCellIndx[pId] = list(range(6)) - + # scan for locations where the variables change VaryListChanges = [] # histograms where there is a change combinedVaryList = [] @@ -210,7 +212,7 @@ def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls): tblLabels.append(key) tblTypes += ['float'] - # add unique cell parameters + # add unique cell parameters if len(newCellDict): for pId in sorted(RecpCellTerms): pfx = str(pId)+'::' # prefix for A values from phase @@ -242,7 +244,7 @@ def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls): # override with fit result if is Dij varied if var in cellAlist: try: - A[i] = seqData[name]['newCellDict'][esdLookUp[var]][1] # get refined value + A[i] = seqData[name]['newCellDict'][esdLookUp[var]][1] # get refined value except KeyError: pass # apply symmetry @@ -304,7 +306,7 @@ def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls): tblSigs += zip(*esds) tblLabels += varlbls tblTypes += ['float' for i in varlbls] - + # tabulate constrained variables, removing histogram numbers if needed # from parameter label depValDict = {} @@ -343,7 +345,7 @@ def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls): for phase in Phases: pId = Phases[phase]['pId'] var = str(pId)+':*:Scale' - if var not in combinedVaryList+list(depValDict): continue + if var not in combinedVaryList+list(depValDict): continue wtFrList = [] sigwtFrList = [] for i,name in enumerate(histNames): @@ -382,24 +384,24 @@ def WriteCIFitem(fp, name, value=''): >>> WriteCIFitem(fp, value=v1) >>> WriteCIFitem(fp, value=v2) - or if items will be aligned in a table (no spaces or new + or if items will be aligned in a table (no spaces or new lines in the items) >>> WriteCIFitem(fp, 'loop_ _cif_name1 _cif_name2') >>> for v1,v2 in values: >>> s = PutInCol("{:.4f}".format(v1),10) - >>> s += PutInCol(str(v2),8) + >>> s += PutInCol(str(v2),8) >>> WriteCIFitem(fp, value=s) - - It is occasionally used where a CIF value is passed as the name - parameter. This works if no quoting is needed, but is not a good - practice. - + + It is occasionally used where a CIF value is passed as the name + parameter. This works if no quoting is needed, but is not a good + practice. + :param fp: file access object :param str name: a CIF data name - :param str value: the value associated with the CIF data name. - Written in different ways depending on what the contents contain, - with respect to quoting. + :param str value: the value associated with the CIF data name. + Written in different ways depending on what the contents contain, + with respect to quoting. ''' # Ignores unicode issues if value: @@ -511,7 +513,7 @@ def WriteAtomsNuclear(fp, phasedict, phasenam, parmDict, sigDict, labellist, s += PutInCol(G2mth.ValEsd(val,sig),dig) s += PutInCol(at[cs+1],3) WriteCIFitem(fp, s) - if naniso != 0: + if naniso != 0: # now loop over aniso atoms WriteCIFitem(fp, '\nloop_' + '\n _atom_site_aniso_label' + '\n _atom_site_aniso_U_11' + '\n _atom_site_aniso_U_22' + @@ -562,7 +564,7 @@ def WriteAtomsNuclear(fp, phasedict, phasenam, parmDict, sigDict, labellist, nTors): s += i WriteCIFitem(fp,'',s.rstrip()) - + pId = phasedict['pId'] for i in RBObj['Ids']: lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0] @@ -592,7 +594,7 @@ def WriteAtomsNuclear(fp, phasedict, phasenam, parmDict, sigDict, labellist, RBObj['ThermalMotion'][0],parmDict,sigDict): s += i WriteCIFitem(fp,'',s.rstrip()) - + pId = phasedict['pId'] for i in RBObj['Ids']: lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0] @@ -604,7 +606,7 @@ def WriteAtomsNuclear(fp, phasedict, phasenam, parmDict, sigDict, labellist, '\n _restr_rigid_body.class_id\n _restr_rigid_body.details') for i,l in enumerate(rbAtoms): WriteCIFitem(fp,' {:5d} {}'.format(i+1,l)) - + def WriteAtomsMagnetic(fp, phasedict, phasenam, parmDict, sigDict, labellist): 'Write atom positions to CIF' # phasedict = self.Phases[phasenam] # pointer to current phase info @@ -691,7 +693,7 @@ def WriteAtomsMagnetic(fp, phasedict, phasenam, parmDict, sigDict, labellist): s += PutInCol(G2mth.ValEsd(val,sig),dig) s += PutInCol(at[cs+1],3) WriteCIFitem(fp, s) - if naniso: + if naniso: # now loop over aniso atoms WriteCIFitem(fp, '\nloop_' + '\n _atom_site_aniso_label' + '\n _atom_site_aniso_U_11' + '\n _atom_site_aniso_U_22' + @@ -785,7 +787,7 @@ def WriteAtomsMM(fp, phasedict, phasenam, parmDict, sigDict, num = 0 # uniquely index the side chains entity_id = {} - for i,at in enumerate(Atoms): + for i,at in enumerate(Atoms): if at[ct-2] not in entity_id: num += 1 entity_id[at[ct-2]] = num @@ -812,7 +814,7 @@ def WriteAtomsMM(fp, phasedict, phasenam, parmDict, sigDict, s += PutInCol('?',2) # pdbx_PDB_ins_code s += PutInCol('?',2) # pdbx_formal_charge s += PutInCol('1',2) # pdbx_PDB_model_num - + # fval = parmDict.get(fpfx+str(i),at[cfrac]) # if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact) # if at[cia] == 'I': @@ -936,7 +938,7 @@ def WriteSeqAtomsNuclear(fp, cell, phasedict, phasenam, hist, seqData, RBparms): s += PutInCol(G2mth.ValEsd(val,sig),dig) s += PutInCol(at[cs+1],3) WriteCIFitem(fp, s) - if naniso != 0: + if naniso != 0: # now loop over aniso atoms WriteCIFitem(fp, '\nloop_' + '\n _atom_site_aniso_label' + '\n _atom_site_aniso_U_11' + '\n _atom_site_aniso_U_22' + @@ -987,7 +989,7 @@ def WriteSeqAtomsNuclear(fp, cell, phasedict, phasenam, hist, seqData, RBparms): nTors): s += i WriteCIFitem(fp,'',s.rstrip()) - + pId = phasedict['pId'] for i in RBObj['Ids']: lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0] @@ -1017,7 +1019,7 @@ def WriteSeqAtomsNuclear(fp, cell, phasedict, phasenam, hist, seqData, RBparms): RBObj['ThermalMotion'][0],parmDict,sigDict): s += i WriteCIFitem(fp,'',s.rstrip()) - + pId = phasedict['pId'] for i in RBObj['Ids']: lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0] @@ -1029,7 +1031,7 @@ def WriteSeqAtomsNuclear(fp, cell, phasedict, phasenam, hist, seqData, RBparms): '\n _restr_rigid_body.class_id\n _restr_rigid_body.details') for i,l in enumerate(rbAtoms): WriteCIFitem(fp,' {:5d} {}'.format(i+1,l)) - + # Refactored over here to allow access by GSASIIscriptable.py def MakeUniqueLabel(lbl, labellist): lbl = lbl.strip() @@ -1100,7 +1102,7 @@ def WriteComposition(fp, phasedict, phasenam, parmDict, quickmode=True, keV=None If quickmode is False, then scattering factors are added to the element loop. - If keV is specified, then resonant scattering factors are also computed and included. + If keV is specified, then resonant scattering factors are also computed and included. ''' General = phasedict['General'] Z = General.get('cellZ',0.0) @@ -1168,7 +1170,7 @@ def WriteComposition(fp, phasedict, phasenam, parmDict, quickmode=True, keV=None s = ' _atom_type_scat_dispersion_real _atom_type_scat_dispersion_imag _atom_type_scat_dispersion_source' WriteCIFitem(fp, s) - + formula = '' for elem in HillSortElements(list(compDict)): s = ' ' @@ -1211,11 +1213,11 @@ def WriteComposition(fp, phasedict, phasenam, parmDict, quickmode=True, keV=None s += ' ' s += PutInCol(val,9) WriteCIFitem(fp,s.rstrip()) - WriteCIFitem(fp,' https://github.com/AdvancedPhotonSource/GSAS-II/blob/master/GSASII/atmdata.py') + WriteCIFitem(fp,' https://raw.githubusercontent.com/AdvancedPhotonSource/GSAS-II/refs/heads/main/GSASII/atmdata.py') if keV: Orbs = G2el.GetXsectionCoeff(elem.split('+')[0].split('-')[0]) FP,FPP,Mu = G2el.FPcalc(Orbs, keV) - WriteCIFitem(fp,' {:8.3f}{:8.3f} https://github.com/AdvancedPhotonSource/GSAS-II/blob/master/GSASII/atmdata.py'.format(FP,FPP)) + WriteCIFitem(fp,' {:8.3f}{:8.3f} https://raw.githubusercontent.com/AdvancedPhotonSource/GSAS-II/refs/heads/main/GSASII/atmdata.py'.format(FP,FPP)) else: WriteCIFitem(fp,s.rstrip()) if formula: formula += " " @@ -1234,7 +1236,7 @@ def WriteCompositionMM(fp, phasedict, phasenam, parmDict, quickmode=True, keV=No If quickmode is False, then scattering factors are added to the element loop. - If keV is specified, then resonant scattering factors are also computed and included. + If keV is specified, then resonant scattering factors are also computed and included. ''' General = phasedict['General'] Z = General.get('cellZ',0.0) @@ -1302,7 +1304,7 @@ def WriteCompositionMM(fp, phasedict, phasenam, parmDict, quickmode=True, keV=No s = ' _atom_type.scat_dispersion_real _atom_type.scat_dispersion_imag _atom_type_scat_dispersion_source' WriteCIFitem(fp, s) - + formula = '' for elem in HillSortElements(list(compDict)): s = ' ' @@ -1345,11 +1347,11 @@ def WriteCompositionMM(fp, phasedict, phasenam, parmDict, quickmode=True, keV=No s += ' ' s += PutInCol(val,9) WriteCIFitem(fp,s.rstrip()) - WriteCIFitem(fp,' https://github.com/AdvancedPhotonSource/GSAS-II/blob/master/GSASII/atmdata.py') + WriteCIFitem(fp,' https://raw.githubusercontent.com/AdvancedPhotonSource/GSAS-II/refs/heads/main/GSASII/atmdata.py') if keV: Orbs = G2el.GetXsectionCoeff(elem.split('+')[0].split('-')[0]) FP,FPP,Mu = G2el.FPcalc(Orbs, keV) - WriteCIFitem(fp,' {:8.3f}{:8.3f} https://github.com/AdvancedPhotonSource/GSAS-II/blob/master/GSASII/atmdata.py'.format(FP,FPP)) + WriteCIFitem(fp,' {:8.3f}{:8.3f} https://raw.githubusercontent.com/AdvancedPhotonSource/GSAS-II/refs/heads/main/GSASII/atmdata.py'.format(FP,FPP)) else: WriteCIFitem(fp,s.rstrip()) if formula: formula += " " @@ -1363,7 +1365,7 @@ def WriteCompositionMM(fp, phasedict, phasenam, parmDict, quickmode=True, keV=No G2mth.ValEsd(cellmass/Z,-0.09,True)) class ExportCIF(G2fil.ExportBaseclass): - '''Base class for CIF exports. Not used directly. Exporters are defined + '''Base class for CIF exports. Not used directly. Exporters are defined in subclasses that call :meth:`MasterExporter`. ''' def __init__(self,G2frame,formatName,extension,longFormatName=None,): @@ -1380,6 +1382,7 @@ def ValidateAscii(self,checklist): if msg: msg += '\n' msg += lbl + " contains unicode characters: " + val if msg: + from .. import GSASIIctrlGUI as G2G G2G.G2MessageBox(self.G2frame, 'Error: CIFs can contain only ASCII characters. Please change item(s) below:\n\n'+msg, 'Unicode not valid for CIF') @@ -1389,7 +1392,7 @@ def _CellSelectNeeded(self,phasenam): '''Determines if selection is needed for a T value in a multiblock CIF :returns: True if the choice of T is ambiguous and a human should - be asked. + be asked. ''' phasedict = self.Phases[phasenam] # pointer to current phase info Tlist = {} # histname & T values used for cell w/o Hstrain @@ -1415,12 +1418,12 @@ def _CellSelectNeeded(self,phasenam): elif len(DijTlist) > 1: # each histogram has different cell lengths, user needs to pick one return True - + def _CellSelectT(self,phasenam): '''Select T value for a phase in a multiblock CIF :returns: T,h_ranId where T is a temperature (float) or '?' and - h_ranId is the random Id (ranId) for a histogram in the + h_ranId is the random Id (ranId) for a histogram in the current phase. This is stored in OverallParms['Controls']['CellHistSelection'] ''' phasedict = self.Phases[phasenam] # pointer to current phase info @@ -1431,7 +1434,7 @@ def _CellSelectT(self,phasenam): for h in phasedict['Histograms']: if not phasedict['Histograms'][h]['Use']: continue if phasedict['Histograms'][h].get('Type','').startswith('HKL'): - return ( # TODO Is temperature in HKLF datasets? + return ( # TODO Is temperature in HKLF datasets? self.Histograms[h]['Instrument Parameters'].get('Temperature',295), self.Histograms[h]['ranId'] ) @@ -1448,7 +1451,7 @@ def _CellSelectT(self,phasenam): Ti = [T] for h in Tlist: choices += ["{} (hist {})".format(Tlist[h],h)] - Ti += [Tlist[h]] + Ti += [Tlist[h]] msg = 'The cell parameters for phase {} are from\nhistograms with different temperatures.\n\nSelect a T value below'.format(phasenam) dlg = wx.SingleChoiceDialog(self.G2frame,msg,'Select T',choices) if dlg.ShowModal() == wx.ID_OK: @@ -1471,7 +1474,7 @@ def _CellSelectT(self,phasenam): msg = 'There are {} sets of cell parameters for phase {}\n due to refined Hstrain values.\n\nSelect the histogram to use with the phase form list below'.format(len(DijTlist),phasenam) dlg = wx.SingleChoiceDialog(self.G2frame,msg,'Select cell',choices) if dlg.ShowModal() == wx.ID_OK: - h = hi[dlg.GetSelection()] + h = hi[dlg.GetSelection()] h_ranId = self.Histograms[h]['ranId'] T = DijTlist[h] else: @@ -1484,8 +1487,8 @@ def _CellSelectT(self,phasenam): return ('?',None) def ShowHstrainCells(self,phasenam,datablockidDict): - '''Displays the unit cell parameters for phases where Dij values create - mutiple sets of lattice parameters. At present there is no way defined for this in + '''Displays the unit cell parameters for phases where Dij values create + mutiple sets of lattice parameters. At present there is no way defined for this in CIF, so local data names are used. ''' phasedict = self.Phases[phasenam] # pointer to current phase info @@ -1508,7 +1511,7 @@ def ShowHstrainCells(self,phasenam,datablockidDict): else: print('ShowHstrainCells error: Laue class not found',SGData['SGLaue']) terms = list(range(7)) - + WriteCIFitem(self.fp, '\n# cell parameters generated by hydrostatic strain') WriteCIFitem(self.fp, 'loop_') WriteCIFitem(self.fp, '\t _gsas_measurement_temperature') @@ -1538,7 +1541,7 @@ def ShowHstrainCells(self,phasenam,datablockidDict): for i in terms: line += PutInCol(G2mth.ValEsd(cellList[i],cellSig[i]),12) line += ' ' + datablockidDict[h] - WriteCIFitem(self.fp, line) + WriteCIFitem(self.fp, line) def MasterExporter(self,event=None,phaseOnly=None,histOnly=None): '''Basic code to export a CIF. Export can be full or simple, as set by @@ -1567,7 +1570,7 @@ def WriteOverall(mode=None): if self.ifPWDR: WriteCIFitem(self.fp, '_pd_proc_info_datetime', self.CIFdate) WriteCIFitem(self.fp, '_pd_calc_method', 'Rietveld Refinement') - + WriteCIFitem(self.fp, '_computing_structure_refinement','GSAS-II (Toby & Von Dreele, J. Appl. Cryst. 46, 544-549, 2013)') if self.ifHKLF: controls = self.OverallParms['Controls'] @@ -1624,7 +1627,7 @@ def WriteOverallMM(mode=None): if self.ifPWDR: WriteCIFitem(self.fp, '_pd_proc_info_datetime', self.CIFdate) WriteCIFitem(self.fp, '_pd_calc_method', 'Rietveld Refinement') - + WriteCIFitem(self.fp, '_computing_structure_refinement','GSAS-II (Toby & Von Dreele, J. Appl. Cryst. 46, 544-549, 2013)') if self.ifHKLF: controls = self.OverallParms['Controls'] @@ -1669,7 +1672,7 @@ def WriteOverallMM(mode=None): WriteCIFitem(self.fp, '_refine.ls_wR_factor_obs',R) except: pass - + def writeCIFtemplate(G2dict,tmplate,defaultname='', cifKey="CIF_template"): '''Write out the selected or edited CIF template @@ -1678,6 +1681,7 @@ def writeCIFtemplate(G2dict,tmplate,defaultname='', In all cases the initial data_ header is stripped (there should only be one!) ''' CIFobj = G2dict.get(cifKey) + if CIFobj is None: return if defaultname: defaultname = G2obj.StripUnicode(defaultname) defaultname = re.sub(r'[^a-zA-Z0-9_-]','',defaultname) @@ -1872,7 +1876,7 @@ def FormatPhaseProfile(phasenam,hist=''): else: parmDict = self.parmDict sigDict = self.sigDict - + SGData = phasedict['General'] ['SGData'] for histogram in sorted(phasedict['Histograms']): if hist is not None and hist != histogram: continue @@ -2036,6 +2040,7 @@ def WriteDistances(phasenam): if 'DisAglCtls' not in generalData: # should not happen, since DisAglDialog should be called # for all phases before getting here + from .. import GSASIIctrlGUI as G2G dlg = G2G.DisAglDialog( self.G2frame, {}, @@ -2165,8 +2170,8 @@ def WriteSeqDistances(phasenam,histname,phasedict,cellList,seqData): http://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Igeom_angle_site_symmetry_.html TODO: this is based on WriteDistances and could likely be merged with that - without too much work. Note also that G2stMn.RetDistAngle is pretty slow for - sequential fits, since it is called so many times. + without too much work. Note also that G2stMn.RetDistAngle is pretty slow for + sequential fits, since it is called so many times. ''' Atoms = phasedict['Atoms'] generalData = phasedict['General'] @@ -2305,7 +2310,7 @@ def WriteSeqOverallPhaseInfo(phasenam,histblk): WriteCIFitem(self.fp, '_symmetry_cell_setting', phasedict['General']['SGData']['SGSys']) - + lam = None if 'X' in histblk['Instrument Parameters'][0]['Type'][0]: for k in ('Lam','Lam1'): @@ -2314,9 +2319,9 @@ def WriteSeqOverallPhaseInfo(phasenam,histblk): break keV = None if lam: keV = 12.397639/lam - # report cell contents + # report cell contents WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict, False, keV) - + def WriteSeqPhaseVals(phasenam,phasedict,pId,histname,phaseWithHist): 'Write out the phase information for the selected phase' WriteCIFitem(self.fp, '_pd_phase_name', phasenam) @@ -2330,7 +2335,7 @@ def WriteSeqPhaseVals(phasenam,phasedict,pId,histname,phaseWithHist): phasedict['General']['SGData']['SGSys']) # generate symmetry operations including centering and center of symmetry - # note that this would be better in WriteSeqOverallPhaseInfo() so there could + # note that this would be better in WriteSeqOverallPhaseInfo() so there could # be only one copy per phase if phasedict['General']['Type'] in ['nuclear','macromolecular']: spacegroup = phasedict['General']['SGData']['SpGrp'].strip() @@ -2391,7 +2396,7 @@ def WriteSeqPhaseVals(phasenam,phasedict,pId,histname,phaseWithHist): # report atom params if phasedict['General']['Type'] in ['nuclear','macromolecular']: #this needs macromolecular variant, etc! - WriteSeqAtomsNuclear(self.fp, cellList, phasedict, phasenam, histname, + WriteSeqAtomsNuclear(self.fp, cellList, phasedict, phasenam, histname, self.seqData, self.OverallParms['Rigid bodies']) else: print("Warning: no export for sequential "+str(phasedict['General']['Type'])+" coordinates implemented") @@ -2406,7 +2411,7 @@ def WriteSeqPhaseVals(phasenam,phasedict,pId,histname,phaseWithHist): # MinMax = phasedict['General']['Map']['minmax'] # WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009)) # WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009)) - + def WritePhaseInfo(phasenam,quick=True,oneblock=True): 'Write out the phase information for the selected phase' WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows') @@ -2449,10 +2454,10 @@ def WritePhaseInfo(phasenam,quick=True,oneblock=True): else: txt = G2mth.ValEsd(val,min(defsig,prevsig),True) WriteCIFitem(self.fp, '_cell_'+lbl,txt) - + density = G2mth.getDensity(phasedict['General'])[0] WriteCIFitem(self.fp, '_exptl_crystal_density_diffrn', - G2mth.ValEsd(density,-0.001)) + G2mth.ValEsd(density,-0.001)) WriteCIFitem(self.fp, '_symmetry_cell_setting', phasedict['General']['SGData']['SGSys']) @@ -2468,7 +2473,7 @@ def WritePhaseInfo(phasenam,quick=True,oneblock=True): WriteCIFitem(self.fp, '_space_group_name_Hall','. # not defined -- non standard setting') else: WriteCIFitem(self.fp, '_space_group_name_Hall',HallSym) - + # generate symmetry operations including centering and center of symmetry SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps( phasedict['General']['SGData']) @@ -2527,7 +2532,7 @@ def WritePhaseInfo(phasenam,quick=True,oneblock=True): WriteAtomsNuclear(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.sigDict, self.labellist, self.OverallParms['Rigid bodies'], - self.RBsuDict) + getattr(self, 'RBsuDict', {})) else: try: self.labellist @@ -2536,7 +2541,7 @@ def WritePhaseInfo(phasenam,quick=True,oneblock=True): WriteAtomsMagnetic(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.sigDict, self.labellist) # raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented") - keV = None + keV = None if oneblock: # get xray wavelength lamlist = [] for hist in self.Histograms: @@ -2549,7 +2554,7 @@ def WritePhaseInfo(phasenam,quick=True,oneblock=True): if len(lamlist) == 1: keV = 12.397639/lamlist[0] - # report cell contents + # report cell contents WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.quickmode, keV) if not self.quickmode and phasedict['General']['Type'] == 'nuclear': # report distances and angles WriteDistances(phasenam) @@ -2594,10 +2599,10 @@ def WritePhaseInfoMM(phasenam,quick=True,oneblock=True): else: txt = G2mth.ValEsd(val,min(defsig,prevsig),True) WriteCIFitem(self.fp, '_cell.'+lbl,txt) - + density = G2mth.getDensity(phasedict['General'])[0] WriteCIFitem(self.fp, '_exptl_crystal.density_diffrn', - G2mth.ValEsd(density,-0.001)) + G2mth.ValEsd(density,-0.001)) WriteCIFitem(self.fp, '_symmetry.cell_setting', phasedict['General']['SGData']['SGSys']) @@ -2647,10 +2652,10 @@ def WritePhaseInfoMM(phasenam,quick=True,oneblock=True): except AttributeError: self.labellist = [] WriteAtomsMM(self.fp, self.Phases[phasenam], phasenam, - self.parmDict, self.sigDict, + self.parmDict, self.sigDict, self.OverallParms['Rigid bodies']) - keV = None + keV = None if oneblock: # get xray wavelength lamlist = [] for hist in self.Histograms: @@ -2663,7 +2668,7 @@ def WritePhaseInfoMM(phasenam,quick=True,oneblock=True): if len(lamlist) == 1: keV = 12.397639/lamlist[0] - # report cell contents + # report cell contents WriteCompositionMM(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.quickmode, keV) #if not self.quickmode and phasedict['General']['Type'] == 'nuclear': # report distances and angles # WriteDistances(phasenam) @@ -2672,7 +2677,7 @@ def WritePhaseInfoMM(phasenam,quick=True,oneblock=True): MinMax = phasedict['General']['Map']['minmax'] WriteCIFitem(self.fp, '_refine.diff_density_max',G2mth.ValEsd(MinMax[0],-0.009)) WriteCIFitem(self.fp, '_refine.diff_density_min',G2mth.ValEsd(MinMax[1],-0.009)) - + def Yfmt(ndec,val): 'Format intensity values' try: @@ -2682,7 +2687,7 @@ def Yfmt(ndec,val): except TypeError: print(val) return '.' - + def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1): 'Write reflection statistics' WriteCIFitem(self.fp, '_reflns_number_total', str(refcount)) @@ -2760,7 +2765,7 @@ def WritePowderData(histlbl,seq=False): if var in depDict: wtFr,sig = depDict[var] wgtstr = G2mth.ValEsd(wtFr,sig) - else: + else: wgtstr = '?' WriteCIFitem(self.fp, ' '+ @@ -2789,7 +2794,7 @@ def WritePowderData(histlbl,seq=False): pfx = str(pId)+':'+str(hId)+':' WriteCIFitem(self.fp, '_refine_ls_R_F_factor ','%.5f'%(resdblk[pfx+'Rf']/100.)) WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor ','%.5f'%(resdblk[pfx+'Rf^2']/100.)) - + try: WriteCIFitem(self.fp, '_pd_proc_ls_prof_R_factor ','%.5f'%(resdblk['R']/100.)) WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_factor ','%.5f'%(resdblk['wR']/100.)) @@ -2826,7 +2831,7 @@ def WritePowderData(histlbl,seq=False): #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10)) #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20)) - # code removed because it is causing duplication in histogram block 1/26/19 BHT + # code removed because it is causing duplication in histogram block 1/26/19 BHT #if not oneblock: # instrumental profile terms go here # WriteCIFitem(self.fp, '_pd_proc_ls_profile_function', # FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])) @@ -3008,11 +3013,11 @@ def WritePowderData(histlbl,seq=False): if 'T' in inst['Type'][0]: excluded += f" from {histblk['Data'][0].data[iS]:.4f} to {histblk['Data'][0].data[iE]:.4f} msec" else: - excluded += f" from {histblk['Data'][0].data[iS]:.3f} to {histblk['Data'][0].data[iE]:.3f} 2theta" + excluded += f" from {histblk['Data'][0].data[iS]:.3f} to {histblk['Data'][0].data[iE]:.3f} 2theta" if excluded: excluded += '\n explain here why region(s) were excluded' WriteCIFitem(self.fp, '_pd_proc_info_excluded_regions',excluded) - + def WritePowderDataMM(histlbl,seq=False): 'Write out the selected powder diffraction histogram info' histblk = self.Histograms[histlbl] @@ -3022,7 +3027,7 @@ def WritePowderDataMM(histlbl,seq=False): WriteCIFitem(self.fp, '_diffrn.id',str(hId)) WriteCIFitem(self.fp, '_diffrn.crystal_id',str(hId)) - + if 'Lam1' in inst: ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1]) sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009) @@ -3045,7 +3050,7 @@ def WritePowderDataMM(histlbl,seq=False): elif 'Lam' in inst: WriteCIFitem(self.fp, '_diffrn_radiation.diffrn_id',str(hId)) WriteCIFitem(self.fp, '_diffrn_radiation.wavelength_id','1') - WriteCIFitem(self.fp, '_diffrn_radiation_wavelength.id','1') + WriteCIFitem(self.fp, '_diffrn_radiation_wavelength.id','1') lam1 = self.parmDict.get('Lam',inst['Lam'][1]) slam1 = self.sigDict.get('Lam',-0.00009) WriteCIFitem(self.fp, '_diffrn_radiation_wavelength.wavelength',G2mth.ValEsd(lam1,slam1)) @@ -3074,7 +3079,7 @@ def WritePowderDataMM(histlbl,seq=False): if var in depDict: wtFr,sig = depDict[var] wgtstr = G2mth.ValEsd(wtFr,sig) - else: + else: wgtstr = '?' WriteCIFitem(self.fp, ' '+ @@ -3103,7 +3108,7 @@ def WritePowderDataMM(histlbl,seq=False): pfx = str(pId)+':'+str(hId)+':' WriteCIFitem(self.fp, '_refine.ls_R_factor_all ','%.5f'%(histblk[pfx+'Rf']/100.)) WriteCIFitem(self.fp, '_refine_ls.R_Fsqd_factor ','%.5f'%(histblk[pfx+'Rf^2']/100.)) - + try: WriteCIFitem(self.fp, '_pd_proc_ls_prof_R_factor ','%.5f'%(histblk['R']/100.)) WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_factor ','%.5f'%(histblk['wR']/100.)) @@ -3138,7 +3143,7 @@ def WritePowderDataMM(histlbl,seq=False): #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10)) #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20)) - # code removed because it is causing duplication in histogram block 1/26/19 BHT + # code removed because it is causing duplication in histogram block 1/26/19 BHT #if not oneblock: # instrumental profile terms go here # WriteCIFitem(self.fp, '_pd_proc_ls_profile_function', # FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])) @@ -3189,10 +3194,10 @@ def WritePowderDataMM(histlbl,seq=False): '\n _refln.d_spacing' + '\n _refln.status' + '\n _refln.crystal_id' + - '\n _refln.wavelength_id' + - '\n _refln.scale_group_code' + + '\n _refln.wavelength_id' + + '\n _refln.scale_group_code' + '\n _refln.F_squared_sigma') - + # if Imax > 0: # WriteCIFitem(self.fp, ' _gsas_i100_meas') @@ -3251,7 +3256,7 @@ def WritePowderDataMM(histlbl,seq=False): if hklmin is not None: WriteCIFitem(self.fp, '_reflns.d_resolution_low ', G2mth.ValEsd(dmax,-0.009)) WriteCIFitem(self.fp, '_reflns.d_resolution_high ', G2mth.ValEsd(dmin,-0.009)) - + WriteCIFitem(self.fp, '\n# POWDER DATA TABLE') # is data fixed step? If the step varies by <0.01% treat as fixed step @@ -3318,7 +3323,7 @@ def WritePowderDataMM(histlbl,seq=False): if 'T' in inst['Type'][0]: excluded += f" from {histblk['Data'][0].data[iS]:.4f} to {histblk['Data'][0].data[iE]:.4f} msec" else: - excluded += f" from {histblk['Data'][0].data[iS]:.3f} to {histblk['Data'][0].data[iE]:.3f} 2theta" + excluded += f" from {histblk['Data'][0].data[iS]:.3f} to {histblk['Data'][0].data[iE]:.3f} 2theta" if excluded: excluded += '\n explain here why region(s) were excluded' WriteCIFitem(self.fp, '_pd_proc_info_excluded_regions',excluded) @@ -3415,6 +3420,7 @@ def WriteSingleXtalData(histlbl): def EditAuthor(event=None): 'dialog to edit the CIF author info' 'Edit the CIF author name' + from .. import GSASIIctrlGUI as G2G dlg = G2G.SingleStringDialog(self.G2frame, 'Get CIF Author', 'Provide CIF Author name (Last, First)', @@ -3433,6 +3439,7 @@ def EditAuthor(event=None): def EditInstNames(event=None): 'Provide a dialog for editing instrument names; for sequential fit, only need one name' + from .. import GSASIIctrlGUI as G2G dictlist = [] keylist = [] lbllist = [] @@ -3450,7 +3457,7 @@ def EditInstNames(event=None): instrname = d.get('InstrName') if instrname is None: d['InstrName'] = '' - if hist.startswith("PWDR") and seqmode: break + if hist.startswith("PWDR") and seqmode: break return G2G.CallScrolledMultiEditor( self.G2frame,dictlist,keylist, prelbl=range(1,len(dictlist)+1), @@ -3466,6 +3473,7 @@ def EditRanges(event): ''' but = event.GetEventObject() phasedict = but.phasedict + from .. import GSASIIctrlGUI as G2G dlg = G2G.DisAglDialog( self.G2frame, phasedict['General']['DisAglCtls'], # edited @@ -3474,7 +3482,7 @@ def EditRanges(event): if dlg.ShowModal() == wx.ID_OK: phasedict['General']['DisAglCtls'] = dlg.GetData() dlg.Destroy() - + def SetCellT(event): '''Set the temperature value by selection of a histogram ''' @@ -3482,11 +3490,12 @@ def SetCellT(event): phasenam = but.phase rId = self.Phases[phasenam]['ranId'] self.CellHistSelection[rId] = self._CellSelectT(phasenam) - + def EditCIFDefaults(): '''Fills the CIF Defaults window with controls for editing various CIF export parameters (mostly related to templates). ''' + from .. import GSASIIctrlGUI as G2G if len(self.cifdefs.GetChildren()) > 0: saveSize = self.cifdefs.GetSize() self.cifdefs.DestroyChildren() @@ -3657,6 +3666,7 @@ def _ResetSelT(event): def SelectDisAglFlags(event): 'Select Distance/Angle use flags for the selected phase' + from .. import GSASIIctrlGUI as G2G phasenam = event.GetEventObject().phase phasedict = self.Phases[phasenam] SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData']) @@ -3909,7 +3919,7 @@ def SelectDisAglFlags(event): seqHistList = [h for h in self.seqData['histNames'] if h in self.seqData] if 'Use' in self.seqData and len(seqHistList) == len(self.seqData.get('Use',[])): seqHistList = [h for i,h in enumerate(seqHistList) if self.seqData['Use'][i]] - + #=============================================================================== # test for errors & warnings; get max & mean final refinement LSQ shifts #=============================================================================== @@ -3997,7 +4007,7 @@ def SelectDisAglFlags(event): self.OverallParms['Controls']['CellHistSelection'] = self.OverallParms[ 'Controls'].get('CellHistSelection',{}) self.CellHistSelection = self.OverallParms['Controls']['CellHistSelection'] - + # create a dict with refined values and their uncertainties self.loadParmDict(True) # is there anything to export? @@ -4011,7 +4021,7 @@ def SelectDisAglFlags(event): print('No name supplied') return self.OpenFile(delayOpen=True) - + self.quickmode = False # full CIF phasenam = None # include all phases # Will this require a multiblock CIF? @@ -4057,6 +4067,7 @@ def SelectDisAglFlags(event): #i = self.Phases[phasenam]['pId'] phasedict = self.Phases[phasenam] # pointer to current phase info if 'DisAglCtls' not in phasedict['General']: + from .. import GSASIIctrlGUI as G2G dlg = G2G.DisAglDialog( self.G2frame, {}, @@ -4067,7 +4078,7 @@ def SelectDisAglFlags(event): dlg.Destroy() return dlg.Destroy() - + # check if temperature values & pressure are defaulted default = 0 for hist in self.Histograms: @@ -4228,7 +4239,7 @@ def SelectDisAglFlags(event): str(self.shortauthorname) + "|Overall") writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template # ``template_publ.cif`` -- could be customized - + # 2) overall info block WriteCIFitem(self.fp, '') WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall') @@ -4246,7 +4257,7 @@ def SelectDisAglFlags(event): if phaseWithHist: WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phase in histogram block)') else: - WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phases pointer in histogram block)') + WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phases pointer in histogram block)') datablockidDict = {} # save block names here # loop over data blocks WriteCIFitem(self.fp, 'loop_ _pd_block_diffractogram_id') @@ -4286,7 +4297,7 @@ def SelectDisAglFlags(event): s = ' ' s += " _gsas_seq_results_val" + str(i) WriteCIFitem(self.fp,s) - + for r in range(len(tblValues[0])): s = '' for c in range(len(tblLabels)): @@ -4296,14 +4307,14 @@ def SelectDisAglFlags(event): sig = None if tblSigs[c] is not None: sig = tblSigs[c][r] - + if tblValues[c][r] is None: if tblTypes[c] == 'int': wid = 5 elif tblTypes[c] == 'str': wid = 10 else: - wid = 12 + wid = 12 s += PutInCol('.',wid) elif sig is None and ',' in tblTypes[c]: s += PutInCol( @@ -4323,7 +4334,7 @@ def SelectDisAglFlags(event): s += PutInCol(str(tblValues[c][r]),15) WriteCIFitem(self.fp,s+'\n') - # 3) overall phase info (w/sample template): a block for each + # 3) overall phase info (w/sample template): a block for each # phase in project histblk = self.Histograms[seqHistList[0]] if phaseWithHist: # include sample info in overall block @@ -4344,7 +4355,7 @@ def SelectDisAglFlags(event): # 4) create at least one block for each seq. ref (per histogram), looping over # histograms in the sequential refinement results. - # Include the phase in each block for one-phase refinements or in separate + # Include the phase in each block for one-phase refinements or in separate # blocks for each phase & histogram if more than one phase (controlled by # variable phaseWithHist) for i,hist in enumerate(seqHistList): @@ -4374,14 +4385,14 @@ def SelectDisAglFlags(event): if var in self.seqData[hist].get('depParmDict',{}): wtFr,sig = self.seqData[hist]['depParmDict'][var] wgtstr = G2mth.ValEsd(wtFr,sig) - else: + else: wgtstr = '?' WriteCIFitem(self.fp, " "+ s + " " + phaseBlockName[pId] + " " + wgtstr) datablockidDict[phasenam] = phaseBlockName[pId] PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']) PP += '\n' WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP) - + WritePowderData(hist,seq=True) # write background, data & reflections, some instrument & sample terms writeCIFtemplate(self.OverallParms['Controls'],'powder', self.Histograms[hist]["Sample Parameters"]['InstrName'], @@ -4407,7 +4418,7 @@ def SelectDisAglFlags(event): errmsg,warnmsg,groups,parmlist = G2mv.GenerateConstraints(varyList,constrDict,fixedList,self.parmDict) WriteCIFitem(self.fp, '_refine_ls_number_constraints', str(G2mv.CountUserConstraints())) - + WriteCIFitem(self.fp, '\n# PHASE INFO FOR HISTOGRAM '+hist) # loop over phases, add a block header if there is more than one phase for j,phasenam in enumerate(sorted(self.Phases)): @@ -4449,13 +4460,13 @@ def SelectDisAglFlags(event): #====================================================================== oneblock = False # select the temperature to use if more than one is histograms - # associated with each phase. This gets stored in the Data Tree - # in self.OverallParms['Controls']['CellHistSelection'] & + # associated with each phase. This gets stored in the Data Tree + # in self.OverallParms['Controls']['CellHistSelection'] & # in self.CellHistSelection (really don't need both) # TODO: temperature selection process is a bit messy. Might be better - # to review if choices are needed and if so, post an error; - # only when a selection is done save that. - # A better way to handle this would be to report all cells and + # to review if choices are needed and if so, post an error; + # only when a selection is done save that. + # A better way to handle this would be to report all cells and # temperatures in a loop. (If CIF allows this.) for phasenam in sorted(self.Phases): rId = self.Phases[phasenam]['ranId'] @@ -4478,10 +4489,10 @@ def SelectDisAglFlags(event): str(self.shortauthorname) + "|PubInfo") writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template # ``template_publ.cif`` -- could be customized - + # overall info -- it is not strictly necessary to separate this from the previous # publication block, but I think this makes sense - + WriteCIFitem(self.fp, '\ndata_'+str(self.CIFname)+'_overall') WriteCIFitem(self.fp, '_pd_block_id', str(self.CIFdate) + "|" + str(self.CIFname) + "|" + @@ -4617,7 +4628,7 @@ def SelectDisAglFlags(event): s += PutInCol(elem,4) Orbs = G2el.GetXsectionCoeff(elem) FP,FPP,Mu = G2el.FPcalc(Orbs, keV) - s += ' {:8.3f}{:8.3f} https://github.com/AdvancedPhotonSource/GSAS-II/blob/master/GSASII/atmdata.py'.format(FP,FPP) + s += ' {:8.3f}{:8.3f} https://raw.githubusercontent.com/AdvancedPhotonSource/GSAS-II/refs/heads/main/GSASII/atmdata.py'.format(FP,FPP) WriteCIFitem(self.fp,s.rstrip()) WriteCIFitem(self.fp,'') if MM: @@ -4647,7 +4658,9 @@ def SelectDisAglFlags(event): class ExportProjectCIF(ExportCIF): '''Used to create a CIF of an entire project - also called directly in :func:`GSASIImiscGUI.ExportSequentialFullCIF` + also called directly in + :func:`~GSASII.GSASIImiscGUI.ExportSequentialFullCIF` + in :mod:`~GSASII.GSASIImiscGUI` :param wx.Frame G2frame: reference to main GSAS-II frame ''' @@ -4666,7 +4679,24 @@ def Exporter(self,event=None,seqData=None,Controls=None): #reload(G2mv) #print('reloaded GSASIImapvars') #### end debug stuff ############################## - + + if wx is None: + print('Unable to export without GUI access') + return + try: + from .. import GSASIIctrlGUI as G2G + except: + print('Unable to export without GSASIIctrlGUI access') + return + try: + import CifFile as cif # PyCifRW from James Hester as a package + except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + cif + except ImportError: + msg = 'The PyCifRW package is not installed. CIF templates cannot be accessed. Created CIFs will be incomplete' + G2G.G2MessageBox(self.G2frame,msg,'no PyCifRW') self.CIFname = '' self.seqData = seqData self.Controls = Controls @@ -4696,6 +4726,7 @@ def __init__(self,G2frame): self.author = '' def mergeMag(self,G2frame,ChemPhase,MagPhase): + from .. import GSASIIctrlGUI as G2G def onChange(*args,**kwargs): wx.CallLater(100,showMergeMag) def showMergeMag(): @@ -4732,7 +4763,7 @@ def showMergeMag(): except: mainSizer.Add(wx.StaticText(dlg,label=' Computational error: singular matrix?')) cellsSame = False - + if cellsSame: tmpPhase['Atoms'] = copy.deepcopy(self.Phases[ChemPhase]['Atoms']) _,atCodes = G2lat.TransformPhase(self.Phases[ChemPhase], @@ -4763,7 +4794,7 @@ def showMergeMag(): atompnl.Layout() atomSubSizer.Add(atompnl) atomSizer.Add(atomSubSizer) - + cellsSame = False # at least one atom must match atomSubSizer = wx.BoxSizer(wx.VERTICAL) atomSubSizer.Add(wx.StaticText(dlg,label='Chemical phase transformed')) @@ -4793,7 +4824,7 @@ def showMergeMag(): atompnl.SetupScrolling() atompnl.Layout() atomSubSizer.Add(atompnl) - atomSizer.Add(atomSubSizer) + atomSizer.Add(atomSubSizer) mainSizer.Add(atomSizer) mainSizer.Add((0,15)) OkBtn = wx.Button(dlg,wx.ID_ANY,"Merge phases") @@ -4807,7 +4838,7 @@ def showMergeMag(): btnSizer.Add((20,20),1) btnSizer.Add(cancelBtn) btnSizer.Add((20,20),1) - + mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10) dlg.Fit() wx.CallAfter(dlg.SendSizeEvent) @@ -4829,11 +4860,11 @@ def showMergeMag(): import nistlat cell1 = self.Phases[MagPhase]['General']['Cell'][1:7] cntr1 = self.Phases[MagPhase]['General']['SGData']['SpGrp'].strip()[0] - cell2 = self.Phases[ChemPhase]['General']['Cell'][1:7] + cell2 = self.Phases[ChemPhase]['General']['Cell'][1:7] cntr2 = self.Phases[ChemPhase]['General']['SGData']['SpGrp'].strip()[0] G2G.NISTlatUse() - out = nistlat.CompareCell(cell1, cntr1, cell2, cntr2) - #, tolerance=3*[0.2]+3*[1], mode='I', vrange=8, output=None) + out = nistlat.CompareCell(cell1, cntr1, cell2, cntr2) + #, tolerance=3*[0.2]+3*[1], mode='I', vrange=8, output=None) if len(out): print(len(out),'transform matrices found, selecting first below') for i in out: @@ -4847,18 +4878,18 @@ def showMergeMag(): Uvec = np.zeros(3) Vvec = np.zeros(3) if 'MagXform' in self.Phases[MagPhase]: - Trans,Uvec,Vvec = self.Phases[MagPhase]['MagXform'] + Trans,Uvec,Vvec = self.Phases[MagPhase]['MagXform'] tmpPhase = copy.deepcopy(self.Phases[MagPhase]) cxM,ctM,csM,ciaM = self.Phases[MagPhase]['General']['AtomPtrs'] cxT,ctT,csT,ciaT = self.Phases[ChemPhase]['General']['AtomPtrs'] - + showMergeMag() - + if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return None dlg.Destroy() - # restore atom type info from chemical phase but keep the mag cell & sym, etc. + # restore atom type info from chemical phase but keep the mag cell & sym, etc. combinedPhase = copy.deepcopy(self.Phases[MagPhase]) combinedPhase['General'] = copy.deepcopy(self.Phases[ChemPhase]['General']) combinedPhase['General']['Name'] += ' - merged' @@ -4878,9 +4909,9 @@ def showMergeMag(): match = True break if not match: # add atom to merged phase - combinedPhase['Atoms'].append(atom[:cxT+4]+[0.,0.,0.]+atom[cxT+4:]) + combinedPhase['Atoms'].append(atom[:cxT+4]+[0.,0.,0.]+atom[cxT+4:]) return combinedPhase - + def Exporter(self,event=None): # get a phase and file name # the export process starts here @@ -4895,7 +4926,7 @@ def Exporter(self,event=None): self.OpenFile(delayOpen=True) MagPhase = None ChemPhase = None - + if len(self.phasenam) == 2: for name in self.phasenam: if self.Phases[name]['General']['Type'] == 'nuclear': @@ -4903,7 +4934,7 @@ def Exporter(self,event=None): if self.Phases[name]['General']['Type'] == 'magnetic': MagPhase = name if MagPhase and ChemPhase: - newPhase = self.mergeMag(self.G2frame,ChemPhase,MagPhase) + newPhase = self.mergeMag(self.G2frame,ChemPhase,MagPhase) if newPhase is not None: self.openDelayed() newName = ChemPhase + '_merged' @@ -4940,8 +4971,11 @@ def __init__(self,G2frame): extension='.cif', longFormatName = 'Export data as CIF' ) - if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable - self.exporttype = ['powder'] + if G2frame is None: # prevent use from GSASIIscriptable + #raise AttributeError('CIF export requires GUI') + self.exporttype = [''] + else: + self.exporttype = ['powder'] # CIF-specific items self.author = '' @@ -4991,7 +5025,7 @@ def __init__(self,G2frame): self.exporttype = ['single'] # CIF-specific items self.author = '' - + def Writer(self,hist,filename=None): self.currentExportType = 'single' self.CIFname = filename @@ -5031,7 +5065,14 @@ def PickleCIFdict(fil): in .dic :returns: the dict with the definitions ''' - import CifFile as cif # PyCifRW from James Hester + try: + import CifFile as cif # PyCifRW from James Hester as a package + except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + except ImportError: + print('Warning: Unable to import PyCifRW') + return {} cifdic = {} try: fp = open(fil,'r') # patch: open file to avoid windows bug @@ -5168,7 +5209,14 @@ def dict2CIF(dblk,loopstructure,blockname='Template'): :returns: the newly created PyCifRW CIF object ''' - import CifFile as cif # PyCifRW from James Hester + try: + import CifFile as cif # PyCifRW from James Hester as a package + except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + except ImportError: + print('Warning: Unable to import PyCifRW') + return {} # compile a 'list' of items in loops loopnames = set() for i in loopstructure: @@ -5216,6 +5264,7 @@ class EditCIFtemplate(wx.Dialog): saving the CIF. ''' def __init__(self,parent,cifblk,loopstructure,defaultname): + from .. import GSASIIctrlGUI as G2G OKbuttons = [] self.cifblk = cifblk self.loopstructure = loopstructure @@ -5260,6 +5309,7 @@ def Post(self): return (self.ShowModal() == wx.ID_OK) def _onSave(self,event): 'Save CIF entries in a template file' + from .. import GSASIIctrlGUI as G2G pth = G2G.GetExportPath(self.G2frame) dlg = wx.FileDialog( self, message="Save as CIF template", @@ -5304,6 +5354,7 @@ class EditCIFpanel(wxscroll.ScrolledPanel): :param (other): optional keyword parameters for wx.ScrolledPanel ''' def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw): + from .. import GSASIIctrlGUI as G2G self.parent = parent wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw) self.vbox = None @@ -5462,6 +5513,7 @@ def CIFEntryWidget(self,dct,item,dataname): numerical values and highlights them as invalid. Use a selection widget when there are specific enumerated values for a string. ''' + from .. import GSASIIctrlGUI as G2G if self.cifdic.get(dataname): if self.cifdic[dataname].get('_enumeration'): values = ['?']+self.cifdic[dataname]['_enumeration'] @@ -5502,7 +5554,7 @@ class CIFtemplateSelect(wx.BoxSizer): :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine the type of template :param dict G2dict: GSAS-II dict where CIF should be placed. The key - specified in cifKey (defaults to "CIF_template") will be used to + specified in cifKey (defaults to "CIF_template") will be used to store either a list or a string. If a list, it will contain a dict and a list defining loops. If an str, it will contain a file name. @@ -5511,7 +5563,7 @@ class CIFtemplateSelect(wx.BoxSizer): :param str title: A line of text to show at the top of the window :param str defaultname: specifies the default file name to be used for saving the CIF. - :param str cifKey: key to be used for saving the CIF information in + :param str cifKey: key to be used for saving the CIF information in G2dict. Defaults to "CIF_template" ''' def __init__(self,frame,panel,tmplate,G2dict, repaint, title, @@ -5543,10 +5595,10 @@ def _onResetTemplate(event): txt.SetFont(txtfnt) self.Add((-1,3)) - # find default name for template + # find default name for template resetTemplate = None localTemplate = None - for pth in sys.path: # -- search with default name + for pth in [os.path.dirname(__file__)]+sys.path: # -- search with default name fil = os.path.join(pth,templateDefName) if os.path.exists(fil): resetTemplate = fil @@ -5618,6 +5670,7 @@ def _onResetTemplate(event): self.Add(hbox) def _onGetTemplateFile(self,event): 'select a template file' + from .. import GSASIIctrlGUI as G2G pth = G2G.GetImportPath(self.G2frame) if not pth: pth = '.' dlg = wx.FileDialog( @@ -5644,6 +5697,8 @@ def _onEditTemplateContents(self,event): dblk,loopstructure = copy.deepcopy(self.CIF_template) # don't modify original else: cf = G2obj.ReadCIF(self.CIF_template) + if len(cf) == 0: + raise Exception("No CIF data_ blocks found") dblk,loopstructure = CIF2dict(cf) dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname) val = dlg.Post() diff --git a/GSASII/exports/G2export_FIT2D.py b/GSASII/exports/G2export_FIT2D.py index 2a44ba984..52c0a4522 100644 --- a/GSASII/exports/G2export_FIT2D.py +++ b/GSASII/exports/G2export_FIT2D.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_FIT2D` follow: +'''Classes in :mod:`~GSASII.exports.G2export_FIT2D` follow: ''' from __future__ import division, print_function import os.path import numpy as np -import GSASIIfiles as G2fil -import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj class ExportPowderCHI(G2fil.ExportBaseclass): '''Used to create a CHI file for a powder data set @@ -72,7 +72,7 @@ def __init__(self,G2frame): self.multiple = True def Writer(self,TreeName,filename=None): - import GSASIIlattice as G2lat + from .. import GSASIIlattice as G2lat self.OpenFile(filename) histblk = self.Histograms[TreeName] inst = histblk['Instrument Parameters'][0] diff --git a/GSASII/exports/G2export_JSON.py b/GSASII/exports/G2export_JSON.py index 46a00f8e4..abd4c460e 100644 --- a/GSASII/exports/G2export_JSON.py +++ b/GSASII/exports/G2export_JSON.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_JSON` follow: +'''Classes in :mod:`~GSASII.exports.G2export_JSON` follow: This code is to honor my friend Robert Papoular, who wants to see what is inside a .gpx file. @@ -7,7 +7,7 @@ from __future__ import division, print_function import json import numpy as np -import GSASIIfiles as G2fil +from .. import GSASIIfiles as G2fil class JsonEncoder(json.JSONEncoder): '''This provides the ability to turn np arrays and masked arrays diff --git a/GSASII/exports/G2export_PDB.py b/GSASII/exports/G2export_PDB.py index a49d6f581..2fc5d63f7 100644 --- a/GSASII/exports/G2export_PDB.py +++ b/GSASII/exports/G2export_PDB.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_PDB` follow: +'''Classes in :mod:`~GSASII.exports.G2export_PDB` follow: ''' from __future__ import division, print_function import numpy as np import os.path -import GSASIIfiles as G2fil -import GSASIIlattice as G2lat +from .. import GSASIIfiles as G2fil +from .. import GSASIIlattice as G2lat class ExportPhasePDB(G2fil.ExportBaseclass): '''Used to create a PDB file for a phase diff --git a/GSASII/exports/G2export_csv.py b/GSASII/exports/G2export_csv.py index 25dd076f3..c03884b88 100644 --- a/GSASII/exports/G2export_csv.py +++ b/GSASII/exports/G2export_csv.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_csv` follow: +'''Classes in :mod:`~GSASII.exports.G2export_csv` follow: ''' # note documentation in docs/source/exports.rst # from __future__ import division, print_function import os.path import numpy as np -import GSASIIobj as G2obj -import GSASIImath as G2mth -import GSASIIpwd as G2pwd -import GSASIIlattice as G2lat -import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj +from .. import GSASIImath as G2mth +from .. import GSASIIpwd as G2pwd +from .. import GSASIIlattice as G2lat +from .. import GSASIIfiles as G2fil def WriteList(obj,headerItems): '''Write a CSV header diff --git a/GSASII/exports/G2export_examples.py b/GSASII/exports/G2export_examples.py index 96d33e30c..c8885eba3 100644 --- a/GSASII/exports/G2export_examples.py +++ b/GSASII/exports/G2export_examples.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_examples` follow: +'''Classes in :mod:`~GSASII.exports.G2export_examples` follow: ''' # note documentation in docs/source/exports.rst # from __future__ import division, print_function import os import numpy as np -import GSASIIobj as G2obj -import GSASIImath as G2mth -import GSASIIpwd as G2pwd -import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj +from .. import GSASIImath as G2mth +from .. import GSASIIpwd as G2pwd +from .. import GSASIIfiles as G2fil class ExportPhaseText(G2fil.ExportBaseclass): '''Used to create a text file for a phase diff --git a/GSASII/exports/G2export_image.py b/GSASII/exports/G2export_image.py index 92421385c..45a993d1d 100644 --- a/GSASII/exports/G2export_image.py +++ b/GSASII/exports/G2export_image.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_image` follow: +'''Classes in :mod:`~GSASII.exports.G2export_image` follow: ''' from __future__ import division, print_function import os.path -import scipy.misc -import GSASIIfiles as G2fil +try: + import matplotlib.pyplot as plt +except ImportError: + plt = None +from .. import GSASIIfiles as G2fil class ExportImagePNG(G2fil.ExportBaseclass): '''Used to create a PNG file for a GSAS-II image @@ -18,7 +21,10 @@ def __init__(self,G2frame): extension='.png', longFormatName = 'Export image in PNG format' ) - self.exporttype = ['image'] + if plt is None: + self.exporttype = [] + else: + self.exporttype = ['image'] #self.multiple = True def Exporter(self,event=None): '''Export an image @@ -29,13 +35,12 @@ def Exporter(self,event=None): self.loadTree() if self.ExportSelect(): return # select one image; ask for a file name # process the selected image(s) (at present only one image) - for i in sorted(self.histnam): + for i in sorted(self.histnam): filename = os.path.join( self.dirname, os.path.splitext(self.filename)[0] + self.extension ) imgFile = self.Histograms[i].get('Data',(None,None)) Image = G2fil.GetImageData(self.G2frame,imgFile,imageOnly=True) - scipy.misc.imsave(filename,Image) + plt.imsave(filename,Image) print('Image '+imgFile+' written to file '+filename) - diff --git a/GSASII/exports/G2export_map.py b/GSASII/exports/G2export_map.py index c1cf0c39e..f2b164557 100644 --- a/GSASII/exports/G2export_map.py +++ b/GSASII/exports/G2export_map.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_map` follow: +'''Classes in :mod:`~GSASII.exports.G2export_map` follow: ''' from __future__ import division, print_function import platform import os import numpy as np -import GSASIIfiles as G2fil +from .. import GSASIIfiles as G2fil class ExportMapASCII(G2fil.ExportBaseclass): '''Used to create a text file for a phase diff --git a/GSASII/exports/G2export_pwdr.py b/GSASII/exports/G2export_pwdr.py index cc27d4c65..5bd7745aa 100644 --- a/GSASII/exports/G2export_pwdr.py +++ b/GSASII/exports/G2export_pwdr.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_pwdr` follow: +'''Classes in :mod:`~GSASII.exports.G2export_pwdr` follow: ''' from __future__ import division, print_function import os.path import numpy as np -import GSASIIobj as G2obj -import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil class ExportPowderFXYE(G2fil.ExportBaseclass): '''Used to create a FXYE file for a powder data set diff --git a/GSASII/exports/G2export_shelx.py b/GSASII/exports/G2export_shelx.py index 23042aea1..daba07b34 100644 --- a/GSASII/exports/G2export_shelx.py +++ b/GSASII/exports/G2export_shelx.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -'''Classes in :mod:`G2export_shelx` follow: +'''Classes in :mod:`~GSASII.exports.G2export_shelx` follow: ''' from __future__ import division, print_function import os.path -import GSASIIfiles as G2fil -import GSASIIspc as G2spc +from .. import GSASIIfiles as G2fil +from .. import GSASIIspc as G2spc class ExportPhaseShelx(G2fil.ExportBaseclass): '''Used to create a SHELX .ins file for a phase @@ -38,7 +38,7 @@ def Exporter(self,event=None): i = self.Phases[phasenam]['pId'] if len(self.phasenam) > 1: # if more than one filename is included, add a phase # self.filename = os.path.splitext(filename)[1] + "_" + str(i) + self.extension - #fp = self.OpenFile() + self.OpenFile(filename) # title line self.Write("TITL from "+str(self.G2frame.GSASprojectfile)+", phase "+str(phasenam)) # get & write cell parameters diff --git a/GSASII/exports/__init__.py b/GSASII/exports/__init__.py index e69de29bb..c5e1d3711 100644 --- a/GSASII/exports/__init__.py +++ b/GSASII/exports/__init__.py @@ -0,0 +1,25 @@ +from . import G2export_Bracket +from . import G2export_CIF +from . import G2export_FIT2D +from . import G2export_JSON +from . import G2export_PDB +from . import G2export_csv +from . import G2export_examples +from . import G2export_image +from . import G2export_map +from . import G2export_pwdr +from . import G2export_shelx + +__all__ = [ + "G2export_Bracket", + "G2export_CIF", + "G2export_FIT2D", + "G2export_JSON", + "G2export_PDB", + "G2export_csv", + "G2export_examples", + "G2export_image", + "G2export_map", + "G2export_pwdr", + "G2export_shelx", +] diff --git a/GSASII/exports/meson.build b/GSASII/exports/meson.build new file mode 100644 index 000000000..7f3505265 --- /dev/null +++ b/GSASII/exports/meson.build @@ -0,0 +1,26 @@ +py.install_sources([ + 'G2export_Bracket.py', + 'G2export_CIF.py', + 'G2export_FIT2D.py', + 'G2export_JSON.py', + 'G2export_PDB.py', + 'G2export_csv.py', + 'G2export_examples.py', + 'G2export_image.py', + 'G2export_map.py', + 'G2export_pwdr.py', + 'G2export_shelx.py', + '__init__.py', + 'cif_core.cpickle', + 'cif_core.dic', + 'cif_pd.cpickle', + 'cif_pd.dic', + 'template_phase.cif', + 'template_powder.cif', + 'template_publ.cif', + 'template_single.cif', + ], + pure: false, # Will be installed next to binaries + subdir: 'GSASII/exports' # Folder relative to site-packages to install to +) + diff --git a/GSASII/fprime.py b/GSASII/fprime.py index bf4aadb51..3fb96813a 100644 --- a/GSASII/fprime.py +++ b/GSASII/fprime.py @@ -12,10 +12,9 @@ if sys.platform.lower() == 'darwin': wx.PyApp.IsDisplayAvailable = lambda _: True import numpy as np import matplotlib as mpl -import GSASIIpath -import GSASIIElem as G2elem -import GSASIIElemGUI as G2elemGUI -import GSASIIctrlGUI as G2ctrl +from . import GSASIIpath +from . import GSASIIElem as G2elem +from . import GSASIIElemGUI as G2elemGUI try: wx.NewIdRef @@ -33,19 +32,19 @@ Pwrm2 = chr(0x207b)+chr(0x0b2) Pwrm6 = chr(0x207b)+chr(0x2076) Pwrm4 = chr(0x207b)+chr(0x2074) -Angstr = chr(0x00c5) +Angstr = chr(0x00c5) [wxID_FPRIMECHOICE1, wxID_FPRIMECHOICE2, wxID_SPINTEXT1, wxID_SPINTEXT2, wxID_FPRIMERESULTS,wxID_FPRIMESLIDER1, wxID_SPINBUTTON, ] = [wx.NewId() for _init_ctrls in range(7)] -[wxID_FPRIMEEXIT, wxID_FPRIMEDELETE, wxID_FPRIMENEW, +[wxID_FPRIMEEXIT, wxID_FPRIMEDELETE, wxID_FPRIMENEW, ] = [wx.NewId() for _init_coll_FPRIME_Items in range(3)] -[wxID_FPRIMEKALPHAAGKA, wxID_FPRIMEKALPHACOKA, wxID_FPRIMEKALPHACRKA, - wxID_FPRIMEKALPHACUKA, wxID_FPRIMEKALPHAFEKA, wxID_FPRIMEKALPHAMNKA, - wxID_FPRIMEKALPHAMOKA, wxID_FPRIMEKALPHANIKA, wxID_FPRIMEKALPHAZNKA, -wxID_FPRIMEKALPHAGAKA,wxID_FPRIMEKALPHAINKA, +[wxID_FPRIMEKALPHAAGKA, wxID_FPRIMEKALPHACOKA, wxID_FPRIMEKALPHACRKA, + wxID_FPRIMEKALPHACUKA, wxID_FPRIMEKALPHAFEKA, wxID_FPRIMEKALPHAMNKA, + wxID_FPRIMEKALPHAMOKA, wxID_FPRIMEKALPHANIKA, wxID_FPRIMEKALPHAZNKA, +wxID_FPRIMEKALPHAGAKA,wxID_FPRIMEKALPHAINKA, ] = [wx.NewId() for _init_coll_KALPHA_Items in range(11)] [wxID_FPRIMEABOUT] = [wx.NewId() for _init_coll_ABOUT_Items in range(1)] @@ -144,7 +143,7 @@ def _init_utils(self): def _init_ctrls(self, parent): wx.Frame.__init__(self, parent=parent, - size=wx.Size(500, 300),style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX, title='Fprime') + size=wx.Size(500, 300),style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX, title='Fprime') self._init_utils() self.SetMenuBar(self.menuBar1) panel = wx.Panel(self) @@ -159,23 +158,23 @@ def _init_ctrls(self, parent): selSizer.Add((5,10),0) selSizer.Add(wx.StaticText(parent=panel, label='Wavelength:'),0,wx.EXPAND) selSizer.Add((5,10),0) - self.SpinText1 = wx.TextCtrl(id=wxID_SPINTEXT1, parent=panel, + self.SpinText1 = wx.TextCtrl(id=wxID_SPINTEXT1, parent=panel, size=wx.Size(100,20), value = "%6.4f" % (self.Wave),style=wx.TE_PROCESS_ENTER ) selSizer.Add(self.SpinText1,0) selSizer.Add((5,10),0) self.SpinText1.Bind(wx.EVT_TEXT_ENTER, self.OnSpinText1, id=wxID_SPINTEXT1) - + selSizer.Add(wx.StaticText(parent=panel, label='Energy:'),0,wx.EXPAND) selSizer.Add((5,10),0) - self.SpinText2 = wx.TextCtrl(id=wxID_SPINTEXT2, parent=panel, - size=wx.Size(100,20), value = "%7.4f" % (self.Energy),style=wx.TE_PROCESS_ENTER) + self.SpinText2 = wx.TextCtrl(id=wxID_SPINTEXT2, parent=panel, + size=wx.Size(100,20), value = "%7.4f" % (self.Energy),style=wx.TE_PROCESS_ENTER) selSizer.Add(self.SpinText2,0) self.SpinText2.Bind(wx.EVT_TEXT_ENTER, self.OnSpinText2, id=wxID_SPINTEXT2) mainSizer.Add(selSizer,0) mainSizer.Add((10,10),0) - + slideSizer = wx.BoxSizer(wx.HORIZONTAL) - self.SpinButton = wx.SpinButton(id=wxID_SPINBUTTON, parent=panel, + self.SpinButton = wx.SpinButton(id=wxID_SPINBUTTON, parent=panel, size=wx.Size(25,24), style=wx.SP_VERTICAL | wx.SP_ARROW_KEYS) slideSizer.Add(self.SpinButton) self.SpinButton.SetRange(-1,1) @@ -189,7 +188,7 @@ def _init_ctrls(self, parent): self.slider1.Bind(wx.EVT_SLIDER, self.OnSlider1, id=wxID_FPRIMESLIDER1) mainSizer.Add(slideSizer,0,wx.EXPAND) mainSizer.Add((10,10),0) - + choiceSizer = wx.BoxSizer(wx.HORIZONTAL) choiceSizer.Add((5,10),0) choiceSizer.Add(wx.StaticText(parent=panel, label='Plot scales:'),0,wx.EXPAND) @@ -264,7 +263,7 @@ def OnFPRIMENewMenu(self, event): self.Delete.Enable(True) self.CalcFPPS() self.SetWaveEnergy(self.Wave) - + def OnFPRIMEDeleteMenu(self, event): if len(self.Elems): ElList = [] @@ -281,7 +280,7 @@ def OnFPRIMEDeleteMenu(self, event): if not self.Elems: self.Delete.Enable(False) self.SetWaveEnergy(self.Wave) - + def OnKALPHACrkaMenu(self, event): self.SetWaveEnergy(2.28962) @@ -305,22 +304,22 @@ def OnKALPHAZnkaMenu(self, event): def OnKALPHAGakaMenu(self, event): self.SetWaveEnergy(1.34134) - + def OnKALPHAMokaMenu(self, event): self.SetWaveEnergy(0.70926) def OnKALPHAAgkaMenu(self, event): self.SetWaveEnergy(0.55936) - + def OnKALPHAInkaMenu(self, event): self.SetWaveEnergy(0.51357) - + def OnSpinText1(self, event): self.SetWaveEnergy(float(self.SpinText1.GetValue())) - + def OnSpinText2(self, event): self.SetWaveEnergy(self.Kev/(float(self.SpinText2.GetValue()))) - + def OnSpinButton(self, event): move = self.SpinButton.GetValue()/10000. self.Wave = min(max(self.Wave+move,self.Wmin),self.Wmax) @@ -333,11 +332,11 @@ def OnSlider1(self, event): else: Wave = self.Kev/(float(self.slider1.GetValue())/1000.) self.SetWaveEnergy(Wave) - + def OnKeyPress(self,event): if event.key == 'g': mpl.rcParams['axes.grid'] = not mpl.rcParams['axes.grid'] - self.SetWaveEnergy(self.Wave) + self.SetWaveEnergy(self.Wave) def UpDateFPlot(self,Wave,rePlot=True): """Plot f' & f" vs wavelength 0.05-3.0A""" @@ -367,7 +366,7 @@ def UpDateFPlot(self,Wave,rePlot=True): Ymin = 0.0 Ymax = 0.0 colors=['r','b','g','c','m','k'] - if self.FPPS: + if self.FPPS: for i,Fpps in enumerate(self.FPPS): Color = colors[i%6] Ymin = min(Ymin,min(Fpps[2]),min(Fpps[3])) @@ -377,7 +376,7 @@ def UpDateFPlot(self,Wave,rePlot=True): fppsP3 = np.array(Fpps[3]) self.ax.plot(fppsP1,fppsP2,Color,label=Fpps[0]+" f '") self.ax.plot(fppsP1,fppsP3,Color,linestyle='dashed',label=Fpps[0]+' f "') - if self.ifWave: + if self.ifWave: self.ax.set_xlabel(r'$\mathsf{\lambda, \AA}$',fontsize=14) self.ax.axvline(x=Wave,picker=3,color='black') else: @@ -451,7 +450,7 @@ def UpDateFPlot(self,Wave,rePlot=True): if self.Elems: self.bx.legend(loc='best') self.bx.set_ylim(0.0,Ymax+1.0) - + if newPlot: newPlot = False self.Page.canvas.draw() @@ -468,10 +467,10 @@ def UpDateFPlot(self,Wave,rePlot=True): self.bxylim = [] tb.push_current() self.Page.canvas.draw() - + def OnPick(self, event): self.linePicked = event.artist - + def OnMotion(self,event): xpos = event.xdata if xpos and xpos>0.1: @@ -487,7 +486,7 @@ def OnMotion(self,event): self.parent.G2plotNB.status.SetStatusText("%s: %.4f, f,f+f': %.3f"%(self.bxlabel,xpos,ypos),1) if self.linePicked: self.SetWaveEnergy(Wave) - + def OnRelease(self, event): if self.linePicked is None: return self.linePicked = None @@ -496,7 +495,7 @@ def OnRelease(self, event): if self.ifWave: Wave = xpos else: - Wave = self.Kev/xpos + Wave = self.Kev/xpos self.SetWaveEnergy(Wave) def SetWaveEnergy(self,Wave): @@ -611,19 +610,19 @@ def OnABOUTItems0Menu(self, event): info.Copyright = ''' Robert B. Von Dreele, 2008(C) Argonne National Laboratory -This product includes software developed -by the UChicago Argonne, LLC, as +This product includes software developed +by the UChicago Argonne, LLC, as Operator of Argonne National Laboratory. ''' info.Description = ''' -For calculating real and resonant X-ray scattering factors to 250keV; -based on Fortran program of Cromer & Liberman corrected for +For calculating real and resonant X-ray scattering factors to 250keV; +based on Fortran program of Cromer & Liberman corrected for Kissel & Pratt energy term; Jensen term not included (D. T. Cromer and D. A. Liberman, Acta Cryst. (1981). A37, 267-268.) ''' wxadv.AboutBox(info) if __name__ == "__main__": - import GSASIIplot as G2plt + from . import GSASIIplot as G2plt app = wx.App() GSASIIpath.InvokeDebugOpts() frm = wx.Frame(None) # create a frame diff --git a/GSASII/git_verinfo.py b/GSASII/git_verinfo.py index e4ce263a2..f16545cfe 100644 --- a/GSASII/git_verinfo.py +++ b/GSASII/git_verinfo.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # git_verinfo.py - GSAS-II version info from git -# Do not edit, generated by 'GSASII/install/tag-version.py' script -# Created 2025-04-22 20:32:33.394287-05:00 +# Do not edit, generated by 'GSASII/install/incr-version.py' script +# Created 2025-05-26 14:08:11.331511-05:00 -git_version = 'dfc4cc57bfa3a65d8b086eac2dfcc470d4c95187' -git_tags = ['5806'] -git_prevtaggedversion = '56a08a464b5470a65b3ff5c5e162b88146f7be5b' -git_prevtags = ['5805'] -git_versiontag = 'v5.3.0' +git_version = 'c37c8ba4cdc73a97e0a8ccf9d9f3c95758502e5b' +git_tags = ['5813'] +git_prevtaggedversion = '144bf1bd2a45ebb481af31fce19a1345cde48512' +git_prevtags = ['5812'] +git_versiontag = 'v5.4.6' diff --git a/GSASII/icons/meson.build b/GSASII/icons/meson.build new file mode 100644 index 000000000..58f3c3dbd --- /dev/null +++ b/GSASII/icons/meson.build @@ -0,0 +1,22 @@ +py.install_sources([ + 'darrow.ico', + 'exarrow.ico', + 'eyarrow.ico', + 'gsas2.icns', + 'gsas2.ico', + 'gsas2.png', + 'gsas2mac.ico', + 'help.ico', + 'key.ico', + 'larrow.ico', + 'publish.ico', + 'rarrow.ico', + 'sxarrow.ico', + 'syarrow.ico', + 'uarrow.ico', + + ], + pure: false, # Will be installed next to binaries + subdir: 'GSASII/icons' # Folder relative to site-packages to install to +) + diff --git a/GSASII/imports/G2img_1TIF.py b/GSASII/imports/G2img_1TIF.py index 2fc4b4072..0c85ec9df 100644 --- a/GSASII/imports/G2img_1TIF.py +++ b/GSASII/imports/G2img_1TIF.py @@ -7,16 +7,16 @@ from __future__ import division, print_function import struct as st -import GSASIIobj as G2obj -import GSASIIpath -import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj +from .. import GSASIIpath +from .. import GSASIIfiles as G2fil import numpy as np import time DEBUG = False class TIF_ReaderClass(G2obj.ImportImage): '''Reads TIF files using a routine (:func:`GetTifData`) that looks for files that can be identified from known instruments and will - correct for slightly incorrect TIF usage. + correct for slightly incorrect TIF usage. ''' def __init__(self): super(self.__class__,self).__init__( # fancy way to self-reference @@ -26,14 +26,22 @@ def __init__(self): longFormatName = 'Various .tif and pseudo-TIF formats using GSAS-II reader' ) self.scriptable = True + try: + import Image as Im + except ImportError: + try: + from PIL import Image as Im + except ImportError: + msg = 'TIF_Reader may not be able to read some less common image formats because the pillow module is not installed.' + G2fil.ImportErrorMsg(msg,{'TIF Image importer':['pillow']}) def ContentsValidator(self, filename): '''Does the header match the required TIF header? ''' return TIFValidator(filename) - + def Reader(self,filename, ParentFrame=None, **unused): - '''Read the TIF file using :func:`GetTifData` which attempts to + '''Read the TIF file using :func:`GetTifData` which attempts to recognize the detector type and set various parameters ''' self.Npix = 0 @@ -42,11 +50,11 @@ def Reader(self,filename, ParentFrame=None, **unused): return False self.LoadImage(ParentFrame,filename) return True - + def GetTifData(filename): '''Read an image in a pseudo-tif format, as produced by a wide variety of software, almost always - incorrectly in some way. + incorrectly in some way. ''' import struct as st import array as ar @@ -90,7 +98,7 @@ def GetTifData(filename): if DEBUG: G2fil.G2Print ('no metadata file found - will try to read file anyway') head = ['no metadata file found',] - + tag = File.read(2) if 'bytes' in str(type(tag)): tag = tag.decode('latin-1') @@ -99,7 +107,7 @@ def GetTifData(filename): IFD = int(st.unpack(byteOrd+'i',File.read(4))[0]) elif tag == 'MM' and int(st.unpack('>h',File.read(2))[0]) == 42: #big endian byteOrd = '>' - IFD = int(st.unpack(byteOrd+'i',File.read(4))[0]) + IFD = int(st.unpack(byteOrd+'i',File.read(4))[0]) else: lines = ['not a detector tiff file',] return lines,0,0,0 @@ -236,7 +244,7 @@ def GetTifData(filename): tifType = 'Gain map' image = File.read(4*Npix) image = np.array(np.frombuffer(image,dtype=byteOrd+'f4')*1000,dtype=np.int32) - + elif 262 in IFD and IFD[262][2][0] > 4: tifType = 'DND' pixy = [158.,158.] @@ -260,20 +268,20 @@ def GetTifData(filename): image = np.array(np.frombuffer(File.read(4*Npix),dtype=np.float32),dtype=np.int32) #fastest else: image = np.array(np.frombuffer(File.read(4*Npix),dtype=np.int32),dtype=np.int32) - elif IFD[258][2][0] == 16: + elif IFD[258][2][0] == 16: tifType = 'MedOptics D1' pixy = [46.9,46.9] File.seek(8) G2fil.G2Print ('Read MedOptics D1 tiff file: '+filename) image = np.array(np.frombuffer(File.read(2*Npix),dtype=np.uint16),dtype=np.int32) - + elif IFD[273][2][0] == 4096: if sizexy[0] == 3072: pixy = [73.,73.] - tifType = 'MAR225' + tifType = 'MAR225' else: pixy = [158.,158.] - tifType = 'MAR325' + tifType = 'MAR325' File.seek(4096) G2fil.G2Print ('Read MAR CCD tiff file: '+filename) image = np.array(np.frombuffer(File.read(2*Npix),dtype=np.uint16),dtype=np.int32) @@ -283,7 +291,7 @@ def GetTifData(filename): File.seek(512) G2fil.G2Print ('Read 11-ID-C tiff file: '+filename) image = np.array(np.frombuffer(File.read(2*Npix),dtype=np.uint16),dtype=np.int32) - + elif sizexy == [4096,4096]: if IFD[273][2][0] == 8: if IFD[258][2][0] == 16: @@ -339,7 +347,7 @@ def GetTifData(filename): dt = np.dtype(np.float32) dt = dt.newbyteorder(byteOrd) image = np.array(np.frombuffer(File.read(Npix*2),dtype=np.uint16),dtype=np.int32) - + # elif sizexy == [960,960]: # tiftype = 'PE-BE' # pixy = (200,200) @@ -347,15 +355,15 @@ def GetTifData(filename): # if not imageOnly: # print 'Read Gold tiff file:',filename # image = np.array(ar.array('H',File.read(2*Npix)),dtype=np.int32) - + if image is None: lines = ['not a known detector tiff file',] - File.close() + File.close() return lines,0,0,0 - + if sizexy[1]*sizexy[0] != image.size: # test is resize is allowed lines = ['not a known detector tiff file',] - File.close() + File.close() return lines,0,0,0 # if GSASIIpath.GetConfigValue('debug'): if DEBUG: @@ -372,7 +380,7 @@ def GetTifData(filename): G2fil.G2Print ('pixel size from metadata: '+str(pixy)) data = {'pixelSize':pixy,'wavelength':wavelength,'distance':distance,'center':center,'size':sizexy, 'setdist':distance,'PolaVal':[polarization,False],'samplechangerpos':samplechangerpos,'det2theta':0.0} - File.close() + File.close() return head,data,Npix,image def TIFValidator(filename): diff --git a/GSASII/imports/G2img_ADSC.py b/GSASII/imports/G2img_ADSC.py index ade108715..a5637d779 100644 --- a/GSASII/imports/G2img_ADSC.py +++ b/GSASII/imports/G2img_ADSC.py @@ -3,7 +3,7 @@ ''' from __future__ import division, print_function -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj import numpy as np class ADSC_ReaderClass(G2obj.ImportImage): '''Reads an ADSC .img file diff --git a/GSASII/imports/G2img_CBF.py b/GSASII/imports/G2img_CBF.py index 3ef7fb3b4..305140ef3 100644 --- a/GSASII/imports/G2img_CBF.py +++ b/GSASII/imports/G2img_CBF.py @@ -4,8 +4,8 @@ from __future__ import division, print_function import time -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath import numpy as np class CBF_ReaderClass(G2obj.ImportImage): '''Routine to read a Read cif image data .cbf file. @@ -16,7 +16,7 @@ def __init__(self): extensionlist=('.cbf',), strictExtension=True, formatName = 'CBF image', - longFormatName = 'CIF Binary Data Format image file (NB: Slow!)' + longFormatName = 'CIF Binary Data Format (CBF) image file (NB: Slow!)' ) def ContentsValidator(self, filename): @@ -36,9 +36,12 @@ def Reader(self,filename, ParentFrame=None, **unused): def GetCbfData(self,filename): 'Read cif binary detector data cbf file' - import unpack_cbf as cbf + if GSASIIpath.binaryPath: + import unpack_cbf as cbf + else: + from .. import unpack_cbf as cbf if GSASIIpath.GetConfigValue('debug'): - print ('Read cif binary detector data cbf file: '+filename) + print ('Reading cif binary detector data cbf file: '+filename) File = open(filename,'rb') sizexy = [0,0] pixSize = [172,172] #Pixium4700? @@ -59,6 +62,7 @@ def GetCbfData(self,filename): else: term = '\n' #LF only head = head.split(term) + compImageSize = None for line in head: fields = line.split() if 'Wavelength' in line: @@ -81,6 +85,10 @@ def GetCbfData(self,filename): Npix = int(fields[1]) elif 'Detector_2theta' in line: det2theta = float(fields[2]) + if compImageSize is None: + print('CBF error failed to read header') + return False + nxy = sizexy[0]*sizexy[1] cent = [cent[0]*pixSize[0]/1000.,cent[1]*pixSize[1]/1000.] File.seek(0) diff --git a/GSASII/imports/G2img_CheMin.py b/GSASII/imports/G2img_CheMin.py index 96754427a..2533eb038 100644 --- a/GSASII/imports/G2img_CheMin.py +++ b/GSASII/imports/G2img_CheMin.py @@ -3,16 +3,28 @@ ''' from __future__ import division, print_function -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj +from .. import GSASIIpath +from .. import GSASIIfiles as G2fil +try: + import imageio +except ImportError: + imageio = None class png_ReaderClass(G2obj.ImportImage): '''Reads standard PNG images; parameters are set to those of the Mars Rover (CheMin) diffractometer. ''' def __init__(self): + if imageio is None: + self.UseReader = False + msg = 'CheMin Reader skipped because imageio library is not installed' + if GSASIIpath.condaTest(): + msg += ' To fix this use command:\n\tconda install imageio' + G2fil.ImportErrorMsg(msg,{'CheMin image importer':['imageio']}) super(self.__class__,self).__init__( # fancy way to self-reference extensionlist=('.png',), strictExtension=True, - formatName = 'PNG image', + formatName = 'CheMin PNG image', longFormatName = 'PNG image from CheMin' ) @@ -20,12 +32,11 @@ def ContentsValidator(self, filename): '''no test at this time ''' return True - + def Reader(self,filename, ParentFrame=None, **unused): '''Reads using standard scipy PNG reader ''' - import scipy.misc - self.Image = scipy.misc.imread(filename,flatten=True) + self.Image = imageio.imread(filename,flatten=True) self.Npix = self.Image.size if self.Npix == 0: return False diff --git a/GSASII/imports/G2img_EDF.py b/GSASII/imports/G2img_EDF.py index 5c4966a6d..25ca1ac7b 100644 --- a/GSASII/imports/G2img_EDF.py +++ b/GSASII/imports/G2img_EDF.py @@ -5,7 +5,7 @@ from __future__ import division, print_function import os import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj class EDF_ReaderClass(G2obj.ImportImage): '''Routine to read a Read European detector data .edf file. This is a particularly nice standard. diff --git a/GSASII/imports/G2img_GE.py b/GSASII/imports/G2img_GE.py index 7e18b0160..8d5520644 100644 --- a/GSASII/imports/G2img_GE.py +++ b/GSASII/imports/G2img_GE.py @@ -5,8 +5,8 @@ from __future__ import division, print_function import os import numpy as np -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath class GE_ReaderClass(G2obj.ImportImage): '''Routine to read a GE image, typically from APS Sector 1. @@ -107,10 +107,7 @@ def GetGEsumData(self,filename,imagenum=1,sum=False): ''' import struct as st import platform - if '2' in platform.python_version_tuple()[0]: - import cPickle - else: - import pickle as cPickle + import pickle import time more = False time0 = time.time() @@ -172,7 +169,7 @@ def GetGEsumData(self,filename,imagenum=1,sum=False): File = open(filename,'wb') Data = {'pixelSize':[200.,200.],'wavelength':0.15,'distance':250.0,'center':[204.8,204.8],'size':sizexy} image = np.reshape(image,(sizexy[1],sizexy[0])) - cPickle.dump([head,Data,Npix,image],File,1) + pickle.dump([head,Data,Npix,image],File,1) File.close() self.sumfile = filename self.formatName = 'GSAS-II image' diff --git a/GSASII/imports/G2img_HDF5.py b/GSASII/imports/G2img_HDF5.py index c12b71253..d4f237a3d 100644 --- a/GSASII/imports/G2img_HDF5.py +++ b/GSASII/imports/G2img_HDF5.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -'''A reader for HDF-5 files. This should be as generic as possible, but -at present this is pretty much customized for XSD-MPE (APS) uses. +'''A reader for HDF-5 files. This should be as generic as possible, but +at present this is pretty much customized for XSD-MPE (APS) uses. ''' try: import h5py except ImportError: h5py = None -import GSASIIobj as G2obj -import GSASIIfiles as G2fil -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil +from .. import GSASIIpath class HDF5_Reader(G2obj.ImportImage): '''Routine to read a HDF-5 image, typically from APS Sector 1 or 6. - Initial version from B. Frosik/SDM. + Initial version from B. Frosik/SDM. Updated to also handle images from APS 1-ID-C. ''' def __init__(self): @@ -34,18 +34,18 @@ def ContentsValidator(self, filename): fp.close() return True except IOError: - return False + return False def Reader(self, filename, ParentFrame=None, **kwarg): '''Read an image from a HDF5 file. Note that images are using :meth:`readDataset`. - When called the first time on a file, the file structure is scanned + When called the first time on a file, the file structure is scanned using :meth:`visit` to map out locations of image(s). On subsequent calls, if more than one image is in the file, the map of file structure (in buffer arg) is reused. Depending on the Config setting for HDF5selection, - a window may be opened to allow selection of which images will be read. + a window may be opened to allow selection of which images will be read. - When an image is reread, the blocknum will be a list item with the location + When an image is reread, the blocknum will be a list item with the location to be read, so the file scan can be skipped. ''' try: @@ -75,7 +75,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): self.buffer['selectedImages'] = list(range(len(self.buffer['imagemap']))) if ParentFrame and len(self.buffer['imagemap']) > nsel and nsel >= 0: import wx - import GSASIIctrlGUI as G2G + from .. import GSASIIctrlGUI as G2G choices = [] for loc,num,siz in self.buffer['imagemap']: if num is None: @@ -142,10 +142,10 @@ def Reader(self, filename, ParentFrame=None, **kwarg): self.repeat = False fp.close() return True - + def visit(self, fp): - '''Recursively visit every node in an HDF5 file & look at dimensions - of contents. If the shape is length 2, 3, or 4 assume an image + '''Recursively visit every node in an HDF5 file & look at dimensions + of contents. If the shape is length 2, 3, or 4 assume an image and index in self.buffer['imagemap'] ''' head = [] @@ -168,7 +168,7 @@ def func(name, dset): print('Skipping entry '+str(dset.name)+'. Shape is '+str(dims)) fp.visititems(func) return head - + def readDataset(self,fp,imagenum=1,name=None,num=None): '''Read a specified image number from a file ''' @@ -190,7 +190,7 @@ def readDataset(self,fp,imagenum=1,name=None,num=None): raise Exception(msg) if quick: return {},None,image.T - sizexy = list(image.shape) + sizexy = list(image.shape) Npix = sizexy[0]*sizexy[1] # default pixel size (for APS sector 6?) pixelsize = [74.8,74.8] @@ -225,8 +225,8 @@ def readDataset(self,fp,imagenum=1,name=None,num=None): data['pixelSize'][0] = float(val)*1000. elif 'y_pixel_size' in name: data['pixelSize'][1] = float(val)*1000. - elif 'beam_center_x' in name: + elif 'beam_center_x' in name: data['center'][0] = float(val) - elif 'beam_center_y' in name: - data['center'][1] = float(val) + elif 'beam_center_y' in name: + data['center'][1] = float(val) return data,Npix,image.T diff --git a/GSASII/imports/G2img_MAR.py b/GSASII/imports/G2img_MAR.py index b0a775695..79882c1c1 100644 --- a/GSASII/imports/G2img_MAR.py +++ b/GSASII/imports/G2img_MAR.py @@ -5,8 +5,8 @@ from __future__ import division, print_function import platform import time -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath import numpy as np class MAR_ReaderClass(G2obj.ImportImage): '''Routine to read several MAR formats, .mar3450,.mar2300,.mar2560 @@ -23,7 +23,7 @@ def ContentsValidator(self, filename): '''no test at this time ''' return True - + def Reader(self,filename, ParentFrame=None, **unused): self.Comments,self.Data,self.Npix,self.Image = GetMAR345Data(filename) if self.Npix == 0 or not self.Comments: @@ -34,8 +34,11 @@ def Reader(self,filename, ParentFrame=None, **unused): def GetMAR345Data(filename,imageOnly=False): 'Read a MAR-345 image plate image' try: - import pack_f as pf - except: + if GSASIIpath.binaryPath: + import pack_f as pf + else: + from .. import pack_f as pf + except ImportError: print ('**** ERROR - Unable to load the GSAS-II MAR image decompression, pack_f') return None,None,None,None @@ -59,7 +62,7 @@ def GetMAR345Data(filename,imageOnly=False): elif 'CENTER' in line: values = line.split() center = [float(values[2])/10.,float(values[4])/10.] #make in mm from pixels - if line: + if line: head.append(line) data = {'pixelSize':pixel,'wavelength':wave,'distance':distance,'center':center,'det2theta':0.0} for line in head: @@ -77,7 +80,7 @@ def GetMAR345Data(filename,imageOnly=False): pos += 8 pos += 37 File.seek(pos) - image = np.zeros(shape=(sizex,sizey),dtype=np.int32) + image = np.zeros(shape=(sizex,sizey),dtype=np.int32) time0 = time.time() if '2' in platform.python_version_tuple()[0]: raw = File.read() @@ -92,4 +95,3 @@ def GetMAR345Data(filename,imageOnly=False): return image else: return head,data,Npix,image - diff --git a/GSASII/imports/G2img_PILTIF.py b/GSASII/imports/G2img_PILTIF.py index 38d456d50..5f09147d9 100644 --- a/GSASII/imports/G2img_PILTIF.py +++ b/GSASII/imports/G2img_PILTIF.py @@ -2,15 +2,15 @@ ''' Read TIF files using the PIL/Pillow module. -The metadata can be specified in a file with the same name and path as +The metadata can be specified in a file with the same name and path as the TIFF file except that the the extension is .metadata. The contents of that file are a series of lines of form:: keyword = value -Note that capitalization of keywords is ignored. Defined keywords are in table below. Any line -without one of these keywords will be ignored. +Note that capitalization of keywords is ignored. Defined keywords are in table below. Any line +without one of these keywords will be ignored. .. Next command allows \\AA to be used in HTML @@ -20,8 +20,8 @@ keyword explanation ============================== ==================================================== wavelength Wavelength in :math:`\\AA` -distance Distance to sample in mm -polarization Percentage polarized in horizontal plane +distance Distance to sample in mm +polarization Percentage polarized in horizontal plane sampleChangerCoordinate Used for sample changers to track sample pixelSizeX Pixel size in X direction (microns) pixelSizeY Pixel size in Y direction (microns) @@ -32,16 +32,16 @@ ''' from __future__ import division, print_function -import GSASIIobj as G2obj -import GSASIIfiles as G2fil -import G2img_1TIF +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil +from . import G2img_1TIF DEBUG = False class TIF_LibraryReader(G2obj.ImportImage): - '''Reads TIF files using a standard library routine. Metadata (such as pixel - size) must be specified by user, either in GUI or via a metadata file. - The library TIF reader can handle compression and other things that are not - commonly used at beamlines. + '''Reads TIF files using a standard library routine. Metadata (such as pixel + size) must be specified by user, either in GUI or via a metadata file. + The library TIF reader can handle compression and other things that are not + commonly used at beamlines. ''' def __init__(self): super(self.__class__,self).__init__( # fancy way to self-reference @@ -56,10 +56,10 @@ def ContentsValidator(self, filename): '''Does the header match the required TIF header? ''' return G2img_1TIF.TIFValidator(filename) - + def Reader(self,filename, ParentFrame=None, **unused): - '''Read the TIF file using the PIL/Pillow reader and give the - user a chance to edit the likely wrong default image parameters. + '''Read the TIF file using the PIL/Pillow reader and give the + user a chance to edit the likely wrong default image parameters. ''' import PIL.Image as PI self.Image = PI.open(filename,mode='r') diff --git a/GSASII/imports/G2img_Rigaku.py b/GSASII/imports/G2img_Rigaku.py index 24cd8a411..33456fd20 100644 --- a/GSASII/imports/G2img_Rigaku.py +++ b/GSASII/imports/G2img_Rigaku.py @@ -4,7 +4,7 @@ from __future__ import division, print_function import os -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj import numpy as np class Rigaku_ReaderClass(G2obj.ImportImage): '''Routine to read a Rigaku R-Axis IV image file. diff --git a/GSASII/imports/G2img_SFRM.py b/GSASII/imports/G2img_SFRM.py index 515713d78..4a5c79d63 100644 --- a/GSASII/imports/G2img_SFRM.py +++ b/GSASII/imports/G2img_SFRM.py @@ -5,8 +5,8 @@ from __future__ import division, print_function import time import os -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath import numpy as np class SFRM_ReaderClass(G2obj.ImportImage): '''Routine to read a Read Bruker Advance image data .sfrm/.grfm file. diff --git a/GSASII/imports/G2img_SumG2.py b/GSASII/imports/G2img_SumG2.py index 7ec9fc11d..a3d3a2935 100644 --- a/GSASII/imports/G2img_SumG2.py +++ b/GSASII/imports/G2img_SumG2.py @@ -3,8 +3,8 @@ ''' from __future__ import division, print_function -import pickle as cPickle -import GSASIIobj as G2obj +import pickle +from .. import GSASIIobj as G2obj class G2_ReaderClass(G2obj.ImportImage): '''Routine to read an image that has been pickled in Python. Images in this format are created by the "Sum image data" command. At least for @@ -15,7 +15,7 @@ def __init__(self): extensionlist=('.G2img',), strictExtension=True, formatName = 'GSAS-II image', - longFormatName = 'cPickled image from GSAS-II' + longFormatName = 'pickled image from GSAS-II' ) def ContentsValidator(self, filename): @@ -23,17 +23,17 @@ def ContentsValidator(self, filename): ''' try: fp = open(filename,'rb') - cPickle.load(fp) + pickle.load(fp) fp.close() except: return False return True def Reader(self,filename, ParentFrame=None, **unused): - '''Read using cPickle + '''Read using pickle ''' Fp = open(filename,'rb') - self.Comments,self.Data,self.Npix,self.Image = cPickle.load(Fp) + self.Comments,self.Data,self.Npix,self.Image = pickle.load(Fp) Fp.close() self.LoadImage(ParentFrame,filename) return True diff --git a/GSASII/imports/G2img_pixirad_1ID_16bit.py b/GSASII/imports/G2img_pixirad_1ID_16bit.py index d8b763034..2c817f3dc 100644 --- a/GSASII/imports/G2img_pixirad_1ID_16bit.py +++ b/GSASII/imports/G2img_pixirad_1ID_16bit.py @@ -9,8 +9,8 @@ from __future__ import division, print_function import struct as st -import GSASIIobj as G2obj -import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil import numpy as np import time DEBUG = False @@ -48,9 +48,7 @@ def ContentsValidator(self, filename): return True def Reader(self,filename, ParentFrame=None, **unused): - '''Read the TIF file using :func:`GetTifData`. If that fails, - use :func:`scipy.misc.imread` and give the user a chance to - edit the likely wrong default image parameters. + '''Read the TIF file using :func:`GetTifData`. ''' self.Comments,self.Data,self.Npix,self.Image = GetTifData(filename) if self.Npix == 0: diff --git a/GSASII/imports/G2pdf_gr.py b/GSASII/imports/G2pdf_gr.py index 34e9cc0ea..90169f5b4 100644 --- a/GSASII/imports/G2pdf_gr.py +++ b/GSASII/imports/G2pdf_gr.py @@ -5,7 +5,7 @@ from __future__ import division, print_function import os.path as ospath import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj class txt_FSQReaderClass(G2obj.ImportPDFData): 'Routines to import S(Q) data from a .fq file' diff --git a/GSASII/imports/G2phase.py b/GSASII/imports/G2phase.py index d0dd2d58f..1c557e748 100644 --- a/GSASII/imports/G2phase.py +++ b/GSASII/imports/G2phase.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # -''' +'''There are several classes in :mod:`~GSASII.imports.G2phase`. +The documentation for them follows. ''' from __future__ import division, print_function @@ -13,9 +14,9 @@ import wx except ImportError: wx = None -import GSASIIobj as G2obj -import GSASIIspc as G2spc -import GSASIIlattice as G2lat +from .. import GSASIIobj as G2obj +from .. import GSASIIspc as G2spc +from .. import GSASIIlattice as G2lat try: # fails on doc build R2pisq = 1./(2.*np.pi**2) except TypeError: @@ -51,7 +52,7 @@ def ContentsValidator(self, filename): return False def Reader(self,filename, ParentFrame=None, **unused): - 'Read a PDF file using :meth:`ReadPDBPhase`' + 'Read a PDB file using :meth:`ReadPDBPhase`' self.Phase = self.ReadPDBPhase(filename, ParentFrame) return True @@ -192,7 +193,9 @@ def ContentsValidator(self, filename): return False def Reader(self,filename,ParentFrame=None,usedRanIdList=[],**unused): - 'Read a phase from a GSAS .EXP file using :meth:`ReadEXPPhase`' + '''Read a phase from a GSAS .EXP file using + :meth:`~EXP_ReaderClass.ReadEXPPhase` + ''' self.Phase = G2obj.SetNewPhase(Name='new phase') # create a new empty phase dict while self.Phase['ranId'] in usedRanIdList: self.Phase['ranId'] = ran.randint(0,sys.maxsize) @@ -612,7 +615,8 @@ def ReadJANAPhase(self,filename,parent=None): return Phase class PDF_ReaderClass(G2obj.ImportPhase): - 'Routine to import Phase information from ICDD PDF Card files' + '''Routine to import Phase information from ICDD Powder Diffraction + File(r) Card, exported by their software.''' def __init__(self): super(self.__class__,self).__init__( # fancy way to say ImportPhase.__init__ extensionlist=('.str',), diff --git a/GSASII/imports/G2phase_CIF.py b/GSASII/imports/G2phase_CIF.py index 659d11b66..5b27350b0 100644 --- a/GSASII/imports/G2phase_CIF.py +++ b/GSASII/imports/G2phase_CIF.py @@ -9,33 +9,47 @@ import re import copy import os.path -import GSASIIobj as G2obj -import GSASIIspc as G2spc -import GSASIIElem as G2elem -import GSASIIlattice as G2lat -import GSASIIpath -import GSASIIfiles as G2fil -import CifFile as cif # PyCifRW from James Hester +from .. import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIspc as G2spc +from .. import GSASIIElem as G2elem +from .. import GSASIIlattice as G2lat +from .. import GSASIIfiles as G2fil +try: + import CifFile as cif # PyCifRW from James Hester as a package +except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + except ImportError: + cif = None debug = GSASIIpath.GetConfigValue('debug') #debug = False class CIFPhaseReader(G2obj.ImportPhase): 'Implements a phase importer from a possibly multi-block CIF file' def __init__(self): + if cif is None: + self.UseReader = False + msg = 'CIFPhase Reader skipped because PyCifRW (CifFile) module is not installed.' + G2fil.ImportErrorMsg(msg,{'CIF Phase importer':['pycifrw']}) super(self.__class__,self).__init__( # fancy way to say ImportPhase.__init__ extensionlist=('.CIF','.cif','.mcif'), strictExtension=False, formatName = 'CIF', longFormatName = 'Crystallographic Information File import' ) - + def ContentsValidator(self, filename): fp = open(filename,'r') ok = self.CIFValidator(fp) + #print('validator: ',ok) fp.close() return ok def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): + if cif is None: # unexpected, but worth a specific error message + print('Attempting to read a CIF without PyCifRW installed') + raise Exception('Attempting to read a CIF without PyCifRW installed') isodistort_warnings = '' # errors that would prevent an isodistort analysis self.Phase = G2obj.SetNewPhase(Name='new phase') # create a new empty phase dict # make sure the ranId is really unique! @@ -65,7 +79,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): try: cf = G2obj.ReadCIF(filename) except cif.StarError as msg: - msg = 'Unreadable cif file\n'+str(msg) + msg = f'\nThis file does not have valid CIF syntax. Web site https://checkcif.iucr.org/ can help find CIF errors. If VESTA or https://addie.ornl.gov/conf_viewer is able to read this CIF, it may allow you to rewrite it as a valid file. \n\nError from PyCifRW: {msg}' self.errors = msg self.warnings += msg return False @@ -116,7 +130,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): sg = sg.replace('_','') if sg: choice[-1] += ', (' + sg.strip() + ')' try: - import GSASIIctrlGUI as G2G + from .. import GSASIIctrlGUI as G2G selblk = G2G.PhaseSelector(choice,ParentFrame=ParentFrame, title= 'Select a phase from one the CIF data_ blocks below',size=(600,100)) except: # no wxPython @@ -147,12 +161,12 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): if not sspgrp: #might be incommensurate magnetic MSSpGrp = blk.get("_space_group.magn_ssg_name_BNS",'') if not MSSpGrp: - MSSpGrp = blk.get("_space_group.magn_ssg_name",'') + MSSpGrp = blk.get("_space_group.magn_ssg_name",'') if not MSSpGrp: msg = 'No incommensurate space group name was found in the CIF.' self.errors = msg self.warnings += '\n'+msg - return False + return False if 'X' in MSSpGrp: msg = 'Ad hoc incommensurate magnetic space group '+MSSpGrp+' is not allowed in GSAS-II' self.warnings += '\n'+msg @@ -231,10 +245,10 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): SpGrpNorm = G2spc.StandardizeSpcName(SpGrp) if SpGrpNorm: E,SGData = G2spc.SpcGroup(SpGrpNorm) - # if E: #try lookup from number - found full symbol? + # if E: #try lookup from number - found full symbol? # SpGrpNorm = G2spc.spgbyNum[int(blk.get('_symmetry_Int_Tables_number'))] # if SpGrpNorm: - # E,SGData = G2spc.SpcGroup(SpGrpNorm) + # E,SGData = G2spc.SpcGroup(SpGrpNorm) # nope, try the space group "out of the Box" if E: self.warnings += 'ERROR in space group symbol '+SpGrp @@ -277,7 +291,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): sgoploop = blk.GetLoop('_space_group_symop_magn_ssg_operation.id') sgcenloop = blk.GetLoop('_space_group_symop_magn_ssg_centering.id') opid = sgoploop.GetItemPosition('_space_group_symop_magn_ssg_operation.algebraic')[1] - centid = sgcenloop.GetItemPosition('_space_group_symop_magn_ssg_centering.algebraic')[1] + centid = sgcenloop.GetItemPosition('_space_group_symop_magn_ssg_centering.algebraic')[1] except KeyError: #old mag cif names sgoploop = blk.GetLoop('_space_group_symop.magn_ssg_id') sgcenloop = blk.GetLoop('_space_group_symop.magn_ssg_centering_id') @@ -305,12 +319,12 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): centid = sgcenloop.GetItemPosition('_space_group_symop_magn_centering.xyz')[1] except KeyError: sgcenloop = None - except KeyError: + except KeyError: try: sgoploop = blk.GetLoop('_space_group_symop_magn.id') sgcenloop = blk.GetLoop('_space_group_symop_magn_centering.id') opid = sgoploop.GetItemPosition('_space_group_symop_magn_operation.xyz')[1] - centid = sgcenloop.GetItemPosition('_space_group_symop_magn_centering.xyz')[1] + centid = sgcenloop.GetItemPosition('_space_group_symop_magn_centering.xyz')[1] except KeyError: #old mag cif names sgoploop = blk.GetLoop('_space_group_symop.magn_id') sgcenloop = blk.GetLoop('_space_group_symop.magn_centering_id') @@ -334,7 +348,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): else: M,C,S = G2spc.MagText2MTS('x,y,z,+1') SGData['SGCen'].append(C) - censpn += list(np.array(spnflp)*S) + censpn += list(np.array(spnflp)*S) self.MPhase['General']['SGData'] = SGData self.MPhase['General']['SGData']['SpnFlp'] = censpn G2spc.GenMagOps(SGData) #set magMom @@ -355,7 +369,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): Volume = G2lat.calc_V(G2lat.cell2A(cell)) self.Phase['General']['Cell'] = [False,]+cell+[Volume,] if magnetic: - self.MPhase['General']['Cell'] = [False,]+cell+[Volume,] + self.MPhase['General']['Cell'] = [False,]+cell+[Volume,] if Super: waveloop = blk.GetLoop('_cell_wave_vector_seq_id') waveDict = dict(waveloop.items()) @@ -389,7 +403,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): '_atom_site_moment_crystalaxis_x':7, '_atom_site_moment_crystalaxis_y':8, '_atom_site_moment_crystalaxis_z':9} - + if blk.get('_atom_site_aniso_label'): anisoloop = blk.GetLoop('_atom_site_aniso_label') anisokeys = [i.lower() for i in anisoloop.keys()] @@ -421,7 +435,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): #position modulation if blk.get('_atom_site_displace_Fourier_atom_site_label'): displFloop = blk.GetLoop('_atom_site_displace_Fourier_atom_site_label') - displFdict = dict(displFloop.items()) + displFdict = dict(displFloop.items()) # if blk.get('_atom_site_displace_special_func_atom_site_label'): #sawtooth # displSloop = blk.GetLoop('_atom_site_displace_special_func_atom_site_label') # displSdict = dict(displSloop.items()) @@ -438,7 +452,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): '_atom_site_moment_fourier_param_sin','_atom_site_moment_fourier_param_cos'] elif blk.get('_atom_site_moment_Fourier.atom_site_label'): MagFloop = blk.GetLoop('_atom_site_moment_Fourier.atom_site_label') - MagFdict = dict(MagFloop.items()) + MagFdict = dict(MagFloop.items()) Mnames = ['_atom_site_moment_fourier.atom_site_label', '_atom_site_moment_fourier.axis','_atom_site_moment_fourier.wave_vector_seq_id', '_atom_site_moment_fourier_param.sin','_atom_site_moment_fourier_param.cos'] @@ -474,11 +488,11 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): atomlist[9] = 'A' elif key == '_atom_site_u_iso_or_equiv': uisoval = cif.get_number_with_esd(val)[0] - if uisoval is not None: + if uisoval is not None: atomlist[10] = uisoval elif key == '_atom_site_b_iso_or_equiv': uisoval = cif.get_number_with_esd(val)[0] - if uisoval is not None: + if uisoval is not None: atomlist[10] = uisoval/(8*np.pi**2) if not atomlist[1] and atomlist[0]: typ = atomlist[0].rstrip('0123456789-+') @@ -502,7 +516,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): self.Phase['Atoms'].append(atomlist) ranIdlookup[atomlist[0]] = atomlist[-1] if atomlist[0] in atomlbllist: - self.warnings += ' ERROR: repeated atom label: '+atomlist[0] + self.warnings += f' note: repeated atom label: {atomlist[0]}\n' else: atomlbllist.append(atomlist[0]) @@ -529,7 +543,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): val = occCdict['_atom_site_occ_special_func_crenel_w'][i] Sfrac[0][1] = cif.get_number_with_esd(val)[0] nim = 1 - + if nim >= 0: Sfrac = [waveType,]+[[sfrac,False] for sfrac in Sfrac[:nim]] else: @@ -538,7 +552,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): if displFdict: for i,item in enumerate(displFdict['_atom_site_displace_fourier_atom_site_label']): if item == atomlist[0]: - waveType = 'Fourier' + waveType = 'Fourier' ix = ['x','y','z'].index(displFdict['_atom_site_displace_fourier_axis'][i]) im = int(displFdict['_atom_site_displace_fourier_wave_vector_seq_id'][i]) if im != nim: @@ -613,7 +627,7 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): self.Phase = copy.deepcopy(self.Phase) #clean copy if magnetic: self.MPhase = copy.deepcopy(self.MPhase) #clean copy - self.MPhase['General']['Type'] = 'magnetic' + self.MPhase['General']['Type'] = 'magnetic' self.MPhase['General']['Name'] = name.strip()+' mag' self.MPhase['General']['Super'] = Super if Super: @@ -659,49 +673,49 @@ def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused): if self.Phase['General']['SGData']['SpGrp'] in G2spc.spg2origins: msg += '\nThis is likely due to the space group being set in Origin 1 rather than 2.\n' msg += ''' -Do you want to use Bilbao's "CIF to Standard Setting" web service to +Do you want to use Bilbao's "CIF to Standard Setting" web service to transform this into a standard setting? ''' if self.Phase['General']['SGData']['SpGrp'] in G2spc.spg2origins and not centro: msg += ''' -If you say "no" here, later, you will get the chance to apply a simple origin +If you say "no" here, later, you will get the chance to apply a simple origin shift later as an alternative to the above.''' else: msg += '\nIf you say "no" here you will need to do this yourself manually.' try: - import GSASIIctrlGUI as G2G + from .. import GSASIIctrlGUI as G2G ans = G2G.askQuestion(ParentFrame,msg,'xform structure?') except Exception as err: # fails if non-interactive (no wxPython) print(err) print('\nCIF symops do not agree with GSAS-II, calling Bilbao "CIF to Standard Setting" web service.\n') ans = True if ans: - import SUBGROUPS + from .. import SUBGROUPS SUBGROUPS.createStdSetting(filename,self) return returnstat - + def ISODISTORT_test(self,blk): '''Test if there is any ISODISTORT information in CIF - At present only _iso_displacivemode... and _iso_occupancymode... are - tested. + At present only _iso_displacivemode... and _iso_occupancymode... are + tested. ''' for i in ('_iso_displacivemode_label', '_iso_occupancymode_label'): if blk.get(i): return True return False - + def ISODISTORT_proc(self,blk,atomlbllist,ranIdlookup,filename): '''Process ISODISTORT items to create constraints etc. - Constraints are generated from information extracted from - loops beginning with _iso_ and are placed into - self.Constraints, which contains a list of + Constraints are generated from information extracted from + loops beginning with _iso_ and are placed into + self.Constraints, which contains a list of :ref:`constraints tree items ` - and one dict. + and one dict. The dict contains help text for each generated ISODISTORT variable - At present only _iso_displacivemode... and _iso_occupancymode... are - processed. Not yet processed: _iso_magneticmode..., + At present only _iso_displacivemode... and _iso_occupancymode... are + processed. Not yet processed: _iso_magneticmode..., _iso_rotationalmode... & _iso_strainmode... ''' varLookup = {'dx':'dAx','dy':'dAy','dz':'dAz','do':'Afrac'} @@ -770,7 +784,7 @@ def ISODISTORT_proc(self,blk,atomlbllist,ranIdlookup,filename): if error: print (self.warnings) raise Exception("Error decoding variable labels") - + error = False coordOffset = {xyz:i for i,xyz in enumerate(('dx','dy','dz'))} for id,lbl,val in zip( @@ -839,7 +853,7 @@ def ISODISTORT_proc(self,blk,atomlbllist,ranIdlookup,filename): constraint.append([k/norm,G2varObj[j]]) modeVar = G2obj.G2VarObj( (self.Phase['ranId'],None,shortmodelist[i],None)) - modeVarList.append(modeVar) + modeVarList.append(modeVar) constraint += [modeVar,False,'f'] self.Constraints.append(constraint) #---------------------------------------------------------------------- @@ -968,7 +982,7 @@ def ISODISTORT_proc(self,blk,atomlbllist,ranIdlookup,filename): constraint.append([k,G2varObj[j]]) modeVar = G2obj.G2VarObj( (self.Phase['ranId'],None,shortmodelist[i],None)) - modeVarList.append(modeVar) + modeVarList.append(modeVar) constraint += [modeVar,False,'f'] self.Constraints.append(constraint) # normilization constants @@ -1009,7 +1023,7 @@ def ISODISTORT_proc(self,blk,atomlbllist,ranIdlookup,filename): #---------------------------------------------------------------------- def fmtEqn(i,head,l,var,k): - 'format a section of a row of variables and multipliers' + 'format a section of a row of variables and multipliers' if np.isclose(k,0): return head,l if len(head) + len(l) > 65: print(head+l) @@ -1018,7 +1032,7 @@ def fmtEqn(i,head,l,var,k): if k < 0 and i > 0: l += ' - ' k = -k - elif i > 0: + elif i > 0: l += ' + ' if k == 1: l += '%s ' % str(var) @@ -1114,7 +1128,7 @@ def fmtEqn(i,head,l,var,k): l1 += ' + ' l += '{:} {:3g} * {:4g} * {:}'.format( l1, k1, n, self.Phase['ISODISTORT']['IsoModeList'][j]) - + s += n * modeVarDelta[self.Phase['ISODISTORT']['IsoModeList'][j]] * k print(head,l) print(lbl,'=',s) @@ -1180,7 +1194,7 @@ def fmtEqn(i,head,l,var,k): # transform matrices #occupancymodeInvmatrix = self.Phase['ISODISTORT']['Var2OccMatrix'] #occupancymodematrix = self.Phase['ISODISTORT']['Occ2VarMatrix'] - + print( 70*'=') print('\nVar2OccMatrix' ,'OccVarList' ) for i,row in enumerate(occupancymodeInvmatrix): @@ -1243,9 +1257,9 @@ def fmtEqn(i,head,l,var,k): l += str(n)+' * '+str(modeVarList[j])+' * '+str(k) s += n * modeVarDelta[modelist[j]] * k print( lbl,'=',str(G2varObj[i]),'=',l,'=',s,'\n') - j = lbl.split('_')[0] + j = lbl.split('_')[0] Occ[j] = ParentOcc[j]+s - + # determine the coordinate delta values from deviations from the parent structure print('\nOccupancy from CIF vs computed') for atmline in self.Phase['Atoms']: @@ -1270,7 +1284,7 @@ def fmtEqn(i,head,l,var,k): ".Phase['ISODISTORT']['G2OccModeList']") for mode,G2mode in zip(modelist,modeVarList): print(" ?::"+str(G2mode),' ==>', mode) - + def ISODISTORT_shortLbl(lbl,shortmodelist): '''Shorten model labels and remove special characters ''' @@ -1289,4 +1303,3 @@ def ISODISTORT_shortLbl(lbl,shortmodelist): lbl = lbl.replace('+','_') lbl = lbl.replace('-','_') G2obj.MakeUniqueLabel(lbl,shortmodelist) # make unique and add to list - diff --git a/GSASII/imports/G2phase_GPX.py b/GSASII/imports/G2phase_GPX.py index 051c77cea..1e6411008 100644 --- a/GSASII/imports/G2phase_GPX.py +++ b/GSASII/imports/G2phase_GPX.py @@ -6,8 +6,8 @@ import sys import pickle import random as ran -import GSASIIobj as G2obj -import GSASIIstrIO as G2stIO +from .. import GSASIIobj as G2obj +from .. import GSASIIstrIO as G2stIO class PhaseReaderClass(G2obj.ImportPhase): 'Opens a .GPX file and pulls out a selected phase' @@ -18,12 +18,12 @@ def __init__(self): formatName = 'GSAS-II gpx', longFormatName = 'GSAS-II project (.gpx file) import' ) - + def ContentsValidator(self, filename): "Test if the 1st section can be read as a pickle block, if not it can't be .GPX!" if True: fp = open(filename,'rb') - try: + try: if '2' in platform.python_version_tuple()[0]: data = pickle.load(fp) else: @@ -48,8 +48,8 @@ def Reader(self,filename, ParentFrame=None, **unused): return False # no blocks with coordinates elif len(phasenames) == 1: # one block, no choices selblk = 0 - else: # choose from options - import GSASIIctrlGUI as G2G + else: # choose from options + from .. import GSASIIctrlGUI as G2G selblk = G2G.PhaseSelector(phasenames,ParentFrame=ParentFrame, title= 'Select a phase from the list below',) if selblk is None: diff --git a/GSASII/imports/G2phase_INS.py b/GSASII/imports/G2phase_INS.py index 48f8d34fe..a1fe43728 100644 --- a/GSASII/imports/G2phase_INS.py +++ b/GSASII/imports/G2phase_INS.py @@ -5,9 +5,9 @@ import sys import numpy as np import random as ran -import GSASIIobj as G2obj -import GSASIIspc as G2spc -import GSASIIlattice as G2lat +from .. import GSASIIobj as G2obj +from .. import GSASIIspc as G2spc +from .. import GSASIIlattice as G2lat class PhaseReaderClass(G2obj.ImportPhase): 'Opens a .INS file and pulls out a selected phase' diff --git a/GSASII/imports/G2phase_rmc6f.py b/GSASII/imports/G2phase_rmc6f.py index 4dc63a552..21d6b3612 100644 --- a/GSASII/imports/G2phase_rmc6f.py +++ b/GSASII/imports/G2phase_rmc6f.py @@ -6,8 +6,8 @@ import os.path import numpy as np import random as ran -import GSASIIobj as G2obj -import GSASIIlattice as G2lat +from .. import GSASIIobj as G2obj +from .. import GSASIIlattice as G2lat class PhaseReaderClass(G2obj.ImportPhase): 'Opens a .rmc6f file and pulls out the phase' @@ -30,7 +30,9 @@ def ContentsValidator(self, filename): return True def Reader(self,filename,filepointer, ParentFrame=None, **unused): - 'Read a rmc6f file using :meth:`ReadINSPhase`' + '''Read a rmc6f file using + :meth:`~GSASII.imports.G2phase_rmc6f.PhaseReaderClass.Readrmc6fPhase` + ''' self.Phase = self.Readrmc6fPhase(filename, ParentFrame) return True diff --git a/GSASII/imports/G2phase_xyz.py b/GSASII/imports/G2phase_xyz.py index 9be2e267c..71b9f9ed3 100644 --- a/GSASII/imports/G2phase_xyz.py +++ b/GSASII/imports/G2phase_xyz.py @@ -7,8 +7,8 @@ import sys import os.path import random as ran -import GSASIIobj as G2obj -import GSASIIlattice as G2lat +from .. import GSASIIobj as G2obj +from .. import GSASIIlattice as G2lat class XYZ_ReaderClass(G2obj.ImportPhase): 'Routine to import Phase information from a XYZ file' @@ -32,7 +32,7 @@ def ContentsValidator(self, filename): return True def Reader(self,filename, ParentFrame=None, **unused): - 'Read a PDF file using :meth:`ReadPDBPhase`' + 'Read a phase from an XYZ file.' self.errors = 'Error opening file' fp = open(filename, 'r') self.Phase = {} diff --git a/GSASII/imports/G2pwd_BrukerBRML.py b/GSASII/imports/G2pwd_BrukerBRML.py index 09cba957e..98eca2ce8 100644 --- a/GSASII/imports/G2pwd_BrukerBRML.py +++ b/GSASII/imports/G2pwd_BrukerBRML.py @@ -4,14 +4,14 @@ import os import shutil import numpy as np -import GSASIIpath +from .. import GSASIIpath try: import xmltodict as xml except Exception as msg: #if GSASIIpath.GetConfigValue('debug'): print(f'Debug: xmltodict error = {msg}') xml = None -import GSASIIobj as G2obj -import GSASIIfiles as G2fil +from .. import GSASIIobj as G2obj + class brml_ReaderClass(G2obj.ImportPowderData): 'Routines to import powder data from a zip Bruker .brml file' def __init__(self): @@ -24,8 +24,7 @@ def __init__(self): if xml is None: self.UseReader = False msg = 'Bruker .brml Reader skipped because xmltodict module is not installed.' - if GSASIIpath.condaTest(): - msg += ' To fix this press "Install packages" button below' + from .. import GSASIIfiles as G2fil G2fil.ImportErrorMsg(msg,{'Bruker .brml Importer':['xmltodict']}) self.scriptable = True @@ -44,16 +43,16 @@ def ContentsValidator(self, filename): return False except: return False - + def Reader(self,filename, ParentFrame=None, **kwarg): 'Read a Bruker brml file' def XtractXMLScan(): '''Read the XML info into the GSAS-II reader structure. This structure seems to be for where the detector is scanned. - Code from Bob with some modifications to read a wider + Code from Bob with some modifications to read a wider assortment of files and sample temperature - :returns: True if read suceeds. May also throw an exception + :returns: True if read suceeds. May also throw an exception on failure ''' self.idstring = f'{os.path.basename(filename)} {os.path.basename(fil)}' @@ -69,7 +68,7 @@ def XtractXMLScan(): if nSteps <= 10: return False # too short x = np.zeros(nSteps, dtype=float) y = np.zeros(nSteps, dtype=float) - w = np.zeros(nSteps, dtype=float) + w = np.zeros(nSteps, dtype=float) if datano: effTime = float(data['RawData']['DataRoutes']['DataRoute'][datano]['ScanInformation']['TimePerStepEffective']) @@ -106,7 +105,7 @@ def XtractXMLScan(): except: pass #breakpoint() - try: # is there some range in col 4 values? + try: # is there some range in col 4 values? if abs(1. - min(y)/max(y)) < 1e-4: raise Exception except: y = np.array(y3) @@ -117,19 +116,19 @@ def XtractXMLScan(): break self.powderdata = [x,y,w,np.zeros(nSteps),np.zeros(nSteps),np.zeros(nSteps)] return True - + def XtractXMLNoscan(): '''Read the XML info into the GSAS-II reader structure. This structure seems to be for where the detector is stationary. - :returns: True if read suceeds. May also throw an exception + :returns: True if read suceeds. May also throw an exception on failure ''' self.idstring = f'{os.path.basename(filename)} {os.path.basename(fil)}' self.powderentry[0] = filename self.powderentry[2] = filNum self.comments = [] - try: + try: scandata = data['RawData']['DataRoutes']['DataRoute']['ScanInformation']['ScaleAxes'] if not scandata: return if 'ScaleAxisInfo' not in scandata: return @@ -153,7 +152,7 @@ def XtractXMLNoscan(): w = np.where(y>0,1/y,0.) self.powderdata = [x,y,w,np.zeros(nSteps),np.zeros(nSteps),np.zeros(nSteps)] return True - + #### beginning of Reader if xml is None: return False diff --git a/GSASII/imports/G2pwd_BrukerRAW.py b/GSASII/imports/G2pwd_BrukerRAW.py index 1ac251abf..d9ee51580 100644 --- a/GSASII/imports/G2pwd_BrukerRAW.py +++ b/GSASII/imports/G2pwd_BrukerRAW.py @@ -4,7 +4,7 @@ import os import struct as st import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj class raw_ReaderClass(G2obj.ImportPowderData): 'Routines to import powder data from a binary Bruker .RAW file' def __init__(self): diff --git a/GSASII/imports/G2pwd_CIF.py b/GSASII/imports/G2pwd_CIF.py index 6309243b2..6fe291bed 100644 --- a/GSASII/imports/G2pwd_CIF.py +++ b/GSASII/imports/G2pwd_CIF.py @@ -4,14 +4,25 @@ from __future__ import division, print_function import numpy as np import os.path -import GSASIIobj as G2obj -import CifFile as cif # PyCifRW from James Hester -import GSASIIpath +from .. import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil +try: + import CifFile as cif # PyCifRW from James Hester as a package +except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + except ImportError: + cif = None asind = lambda x: 180.*np.arcsin(x)/np.pi class CIFpwdReader(G2obj.ImportPowderData): 'Routines to import powder data from a CIF file' def __init__(self): + if cif is None: + self.UseReader = False + msg = 'CIF PWDR Reader skipped because PyCifRW (CifFile) module is not installed.' + G2fil.ImportErrorMsg(msg,{'CIF powder importer':['pycifrw']}) super(self.__class__,self).__init__( # fancy way to self-reference extensionlist=('.CIF','.cif'), strictExtension=False, @@ -64,7 +75,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): '_pd_proc_ls_weight', '_pd_meas_counts_total' ) - + ModDataItems = ( # items that modify the use of the data '_pd_meas_step_count_time', '_pd_meas_counts_monitor', @@ -167,7 +178,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): choices.append( 'Block '+str(blk)+', '+str(l)+' points. X='+sx+' & Y='+sy ) - import GSASIIctrlGUI as G2G + from .. import GSASIIctrlGUI as G2G selections = G2G.MultipleBlockSelector( choices, ParentFrame=ParentFrame, @@ -207,7 +218,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): choices.append(such) chlbls.append('Divide intensities by data item') choices.append(['none']+modch) - import GSASIIctrlGUI as G2G + from .. import GSASIIctrlGUI as G2G res = G2G.MultipleChoicesSelector(choices,chlbls) if not res: self.errors = "Abort: data items not selected" @@ -236,7 +247,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): else: w,e = cif.get_number_with_esd(val) if w: wl.append(w) - if wl: + if wl: if len(wl) > 1: self.instdict['wave'] = wl else: @@ -244,7 +255,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): if cf[blk].get('_diffrn_ambient_temperature'): val = cf[blk]['_diffrn_ambient_temperature'] w,e = cif.get_number_with_esd(val) - if w: + if w: self.Sample['Temperature'] = w xcf = xch[xi] if type(xcf) is tuple: @@ -301,7 +312,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): elif v <= 0: vl.append(1.) else: - vl.append(1./v) + vl.append(1./v) elif sucf == '_pd_meas_counts_total': for val in cf[blk].get(sucf,'?'): v,e = cif.get_number_with_esd(val) diff --git a/GSASII/imports/G2pwd_FP.py b/GSASII/imports/G2pwd_FP.py index 296862824..e57fdd36d 100644 --- a/GSASII/imports/G2pwd_FP.py +++ b/GSASII/imports/G2pwd_FP.py @@ -5,8 +5,8 @@ from __future__ import division, print_function import os.path as ospath import numpy as np -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath class fp_ReaderClass(G2obj.ImportPowderData): 'Routines to import powder data from a FullProf 1-10 column .dat file' def __init__(self): diff --git a/GSASII/imports/G2pwd_GPX.py b/GSASII/imports/G2pwd_GPX.py index 8e3e91a24..bec4017cf 100644 --- a/GSASII/imports/G2pwd_GPX.py +++ b/GSASII/imports/G2pwd_GPX.py @@ -3,15 +3,12 @@ ''' from __future__ import division, print_function import platform -import pickle as cPickle +import pickle import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj -def cPickleLoad(fp): - if '2' in platform.python_version_tuple()[0]: - return cPickle.load(fp) - else: - return cPickle.load(fp,encoding='latin-1') +def pickleLoad(fp): + return pickle.load(fp,encoding='latin-1') class GSAS2_ReaderClass(G2obj.ImportPowderData): """Routines to import powder data from a GSAS-II file @@ -27,19 +24,19 @@ def __init__(self): ) def ContentsValidator(self, filename): - "Test if the 1st section can be read as a cPickle block, if not it can't be .GPX!" + "Test if the 1st section can be read as a pickle block, if not it can't be .GPX!" fp = open(filename,'rb') try: - data = cPickleLoad(fp) + data = pickleLoad(fp) except: - self.errors = 'This is not a valid .GPX file. Not recognized by cPickle' + self.errors = 'This is not a valid .GPX file. Not recognized by pickle' fp.close() return False fp.seek(0) nhist = 0 while True: try: - data = cPickleLoad(fp) + data = pickleLoad(fp) except EOFError: break if data[0][0][:4] == 'PWDR': @@ -72,7 +69,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): while True: pos = fp.tell() try: - data = cPickleLoad(fp) + data = pickleLoad(fp) except EOFError: break if data[0][0][:4] == 'PWDR': @@ -105,7 +102,7 @@ def Reader(self,filename, ParentFrame=None, **kwarg): fp = open(filename,'rb') fp.seek(poslist[selblk]) - data = cPickleLoad(fp) + data = pickleLoad(fp) N = len(data[0][1][1][0]) #self.powderdata = data[0][1][1] self.powderdata = [ diff --git a/GSASII/imports/G2pwd_MIDAS.py b/GSASII/imports/G2pwd_MIDAS.py index 60b9992cf..a6c2863ba 100644 --- a/GSASII/imports/G2pwd_MIDAS.py +++ b/GSASII/imports/G2pwd_MIDAS.py @@ -10,9 +10,9 @@ except ImportError: zarr = None import numpy as np -import GSASIIobj as G2obj -import GSASIIfiles as G2fil -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil +from .. import GSASIIpath instprmList = [('Bank',1.0), ('Lam',0.413263), ('Polariz.',0.99), ('SH/L',0.002), ('Type','PXC'), ('U',1.163), ('V',-0.126), @@ -178,7 +178,6 @@ def readMidas(self, filename, fpbuffer={}): # 2thetas: fpbuffer['REtaMap'][1][:,iAzm][unmasked[iAzm]] fpbuffer['2Read'] = [(i%Nazim,i//Nazim) for i in sel if i%Nazim in mAzm] # xfrom Zarr dict into a native dict - #self.MIDASinstprm = {i:j[0] for i,j in fp['InstrumentParameters'].items()} self.MIDASinstprm = {i:fp['InstrumentParameters'][i][0] for i in fp['InstrumentParameters']} # change a few keys for key,newkey in [('Polariz','Polariz.'),('SH_L','SH/L')]: diff --git a/GSASII/imports/G2pwd_Panalytical.py b/GSASII/imports/G2pwd_Panalytical.py index 065c3d1b6..9f62d0bc5 100644 --- a/GSASII/imports/G2pwd_Panalytical.py +++ b/GSASII/imports/G2pwd_Panalytical.py @@ -4,7 +4,7 @@ import os.path as ospath import xml.etree.ElementTree as ET import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj class Panalytical_ReaderClass(G2obj.ImportPowderData): '''Routines to import powder data from a Pananalytical.xrdm (xml) file. diff --git a/GSASII/imports/G2pwd_csv.py b/GSASII/imports/G2pwd_csv.py index 1517046a3..a04c762e7 100644 --- a/GSASII/imports/G2pwd_csv.py +++ b/GSASII/imports/G2pwd_csv.py @@ -5,8 +5,8 @@ from __future__ import division, print_function import os.path as ospath import numpy as np -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath class csv_ReaderClass(G2obj.ImportPowderData): 'Routines to import powder data from a .xye file' def __init__(self): diff --git a/GSASII/imports/G2pwd_fxye.py b/GSASII/imports/G2pwd_fxye.py index 60d997551..ca4a82fac 100644 --- a/GSASII/imports/G2pwd_fxye.py +++ b/GSASII/imports/G2pwd_fxye.py @@ -5,13 +5,13 @@ import os.path as ospath import platform import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj class GSAS_ReaderClass(G2obj.ImportPowderData): 'Routines to import powder data from a GSAS files' def __init__(self): super(self.__class__,self).__init__( # fancy way to self-reference - extensionlist=('.fxye','.raw','.gsas','.gda','.gsa','.gss','.RAW','.GSAS','.GDA','.GSA','.dat'), + extensionlist=('.fxye','.raw','.gsas','.gda','.gsa','.gss','.RAW','.GSAS','.GDA','.GSA','.dat', 'XRA'), strictExtension=False, formatName = 'GSAS powder data', longFormatName = 'GSAS powder data files (.fxye, .raw, .gsas...)' @@ -64,12 +64,12 @@ def ContentsValidator(self, filename): def Reader(self,filename, ParentFrame=None, **kwarg): '''Read a GSAS (old formats) file of type FXY, FXYE, ESD or STD types. If multiple datasets are requested, use self.repeat and buffer caching. - - EDS data is only in the STD format (10 values per line separated by spaces); - the 1st line contains at col 60 the word "Two-Theta " followed by the appropriate value. - The BANK record contains the 3 values (4th not used) after 'EDS' for converting MCA - channel number (c) to keV via E = A + Bc + Cc^2; these coefficients are - generally predetermined by calibration of the MCA. They & 2-theta are transferred to + + EDS data is only in the STD format (10 values per line separated by spaces); + the 1st line contains at col 60 the word "Two-Theta " followed by the appropriate value. + The BANK record contains the 3 values (4th not used) after 'EDS' for converting MCA + channel number (c) to keV via E = A + Bc + Cc^2; these coefficients are + generally predetermined by calibration of the MCA. They & 2-theta are transferred to the Instrument parameters data. ''' def GetFXYEdata(File,Pos,Bank): @@ -91,8 +91,8 @@ def GetFXYEdata(File,Pos,Bank): w.append(1.0/float(vals[2])**2) S = File.readline() N = len(x) - return [np.array(x),np.array(y),np.array(w),np.zeros(N),np.zeros(N),np.zeros(N)] - + return [np.array(x),np.array(y),np.array(w),np.zeros(N),np.zeros(N),np.zeros(N)] + def GetFXYdata(File,Pos,Bank): File.seek(Pos) x = [] @@ -106,13 +106,13 @@ def GetFXYdata(File,Pos,Bank): if f > 0.0: y.append(f) w.append(1.0/f) - else: + else: y.append(0.0) w.append(0.0) S = File.readline() N = len(x) return [np.array(x),np.array(y),np.array(w),np.zeros(N),np.zeros(N),np.zeros(N)] - + def GetESDdata(File,Pos,Bank): File.seek(Pos) cons = Bank.split() @@ -140,7 +140,7 @@ def GetESDdata(File,Pos,Bank): if yi > 0.0: y.append(yi) w.append(1.0/ei**2) - else: + else: y.append(0.0) w.append(0.0) j += 1 @@ -153,7 +153,7 @@ def GetESDdata(File,Pos,Bank): x = Tmap2TOF(self.TimeMap[cons[5]],self.clockWd[cons[5]]) x = x[:len(y)] #Tmap2TOF add extra step(s) return [np.array(x),np.array(y),np.array(w),np.zeros(N),np.zeros(N),np.zeros(N)] - + def GetSTDdata(File,Pos,Bank): File.seek(Pos) cons = Bank.split() @@ -208,7 +208,7 @@ def GetSTDdata(File,Pos,Bank): x = Tmap2TOF(self.TimeMap[cons[5]],self.clockWd[cons[5]]) x = x[:len(y)] #Tmap2TOF add extra step(s) return [np.array(x),np.array(y),np.array(w),np.zeros(N),np.zeros(N),np.zeros(N)] - + def GetALTdata(File,Pos,Bank): File.seek(Pos) cons = Bank.split() @@ -230,7 +230,7 @@ def GetALTdata(File,Pos,Bank): if yi > 0.0: y.append(yi) w.append(1.0/ei**2) - else: + else: y.append(0.0) w.append(0.0) j += 1 @@ -240,7 +240,7 @@ def GetALTdata(File,Pos,Bank): x = Tmap2TOF(self.TimeMap[cons[5]],self.clockWd[cons[5]]) x = x[:len(y)] #Tmap2TOF add extra step(s) return [np.array(x),np.array(y),np.array(w),np.zeros(N),np.zeros(N),np.zeros(N)] - + def GetTimeMap(File,Pos,TimeMap): File.seek(Pos) cons = TimeMap[8:].split() @@ -264,7 +264,7 @@ def GetTimeMap(File,Pos,TimeMap): TMap = TMap.T TMap[0] -= 1 return TMap.T,clockWd,mapNo - + def Tmap2TOF(TMap,clockWd): TOF = [] Tch,T,Step = TMap[0] @@ -295,14 +295,14 @@ def Tmap2TOF(TMap,clockWd): # Save the offset (Pos), BANK line (Banks), comments for each bank # # This is going to need a fair amount of work to track line numbers - # in the input file. + # in the input file. if len(Banks) != len(Pos) or len(Banks) == 0: i = -1 while True: i += 1 S = fp.readline() if len(S) == 0: break - + if i==0: # first line is always a comment self.errors = 'Error reading title' title = S[:-1] @@ -330,8 +330,8 @@ def Tmap2TOF(TMap,clockWd): self.errors = 'Error reading time map after bank:\n '+str(Banks[-1]) timemap,clockwd,mapNo = GetTimeMap(fp,fp.tell(),S) self.TimeMap[mapNo] = timemap - self.clockWd[mapNo] = clockwd - + self.clockWd[mapNo] = clockwd + # Now select the bank to read if not Banks: # use of ContentsValidator should prevent this error @@ -418,7 +418,7 @@ def Tmap2TOF(TMap,clockWd): try: self.Sample['Chi'] = float(S.split('=')[1]) except: - pass + pass elif 'Phi' in S.split('=')[0]: try: self.Sample['Phi'] = float(S.split('=')[1]) @@ -427,10 +427,10 @@ def Tmap2TOF(TMap,clockWd): if 'EDS' in Bank: S = self.comments[0].lower().split('theta') if len(S) > 1: - self.Inst['2-theta'] = [float(S[1]),float(S[1]),False] + self.Inst['2-theta'] = [float(S[1]),float(S[1]),False] self.Sample['Temperature'] = Temperature fp.close() - return True + return True def sfloat(S): 'convert a string to a float, treating an all-blank string as zero' @@ -445,4 +445,3 @@ def sint(S): return int(S) else: return 0 - diff --git a/GSASII/imports/G2pwd_rigaku.py b/GSASII/imports/G2pwd_rigaku.py index 66d213e73..e5b1b2c47 100644 --- a/GSASII/imports/G2pwd_rigaku.py +++ b/GSASII/imports/G2pwd_rigaku.py @@ -3,7 +3,7 @@ import os import os.path as ospath import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj class Rigaku_txtReaderClass(G2obj.ImportPowderData): '''Routines to import powder data from a Rigaku .txt file with an angle and then 1 or 11(!) intensity values on the line. The example file is proceeded diff --git a/GSASII/imports/G2pwd_xye.py b/GSASII/imports/G2pwd_xye.py index 876b7c096..3721a887b 100644 --- a/GSASII/imports/G2pwd_xye.py +++ b/GSASII/imports/G2pwd_xye.py @@ -5,10 +5,10 @@ ''' from __future__ import division, print_function -import os.path as ospath +import os.path import numpy as np -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath asind = lambda x: 180.*np.arcsin(x)/np.pi @@ -38,9 +38,10 @@ def ContentsValidator(self, filename): Qchi = False self.Wave = None fp = open(filename,'r') - if '.chi' in filename: + ext = os.path.splitext(filename)[1] + if ext == '.chi': self.Chi = True - if '.qchi' in filename: + if ext == '.qchi': Qchi = True if2theta = False ifQ = False @@ -83,7 +84,7 @@ def ContentsValidator(self, filename): gotCcomment = True continue if S[0] in ["'",'#','!']: - if 'wavelength' in S and not self.Wave: + if 'wavelength' in S and not self.Wave and '.q' in ext: wave = S.split()[2] if wave: try: @@ -212,7 +213,7 @@ def Reader(self,filename, ParentFrame=None, **unused): self.powderentry[0] = filename #self.powderentry[1] = pos # bank offset (N/A here) #self.powderentry[2] = 1 # xye file only has one bank - self.idstring = ospath.basename(filename) + self.idstring = os.path.basename(filename) # scan comments for temperature Temperature = 300. for S in self.comments: diff --git a/GSASII/imports/G2rfd_Panalytical.py b/GSASII/imports/G2rfd_Panalytical.py index 23aa8e6a7..b3e5dca51 100644 --- a/GSASII/imports/G2rfd_Panalytical.py +++ b/GSASII/imports/G2rfd_Panalytical.py @@ -4,7 +4,7 @@ import os.path as ospath import xml.etree.ElementTree as ET import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj sind = lambda x: np.sin(x*np.pi/180.) class Panalytical_ReaderClass(G2obj.ImportReflectometryData): '''Routines to import reflectivity data from a Panalytical.xrdm (xml) file. diff --git a/GSASII/imports/G2rfd_rigaku.py b/GSASII/imports/G2rfd_rigaku.py index e4858aa11..66dcf9b68 100644 --- a/GSASII/imports/G2rfd_rigaku.py +++ b/GSASII/imports/G2rfd_rigaku.py @@ -4,7 +4,7 @@ import os import os.path as ospath import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj npsind = lambda x: np.sin(np.pi*x/180.) class Rigaku_txtReaderClass(G2obj.ImportReflectometryData): '''Routines to import powder data from a Rigaku .txt file with an angle and diff --git a/GSASII/imports/G2rfd_xye.py b/GSASII/imports/G2rfd_xye.py index 88cd3a6f7..f4ccd0658 100644 --- a/GSASII/imports/G2rfd_xye.py +++ b/GSASII/imports/G2rfd_xye.py @@ -5,7 +5,7 @@ from __future__ import division, print_function import os.path as ospath import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj npasind = lambda x: 180.*np.arcsin(x)/np.pi npsind = lambda x: np.sin(np.pi*x/180.) try: # fails on doc build diff --git a/GSASII/imports/G2sad_xye.py b/GSASII/imports/G2sad_xye.py index 208b48173..cbbc11046 100644 --- a/GSASII/imports/G2sad_xye.py +++ b/GSASII/imports/G2sad_xye.py @@ -5,8 +5,8 @@ from __future__ import division, print_function import os.path as ospath import numpy as np -import GSASIIobj as G2obj -import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIpath npasind = lambda x: 180.*np.arcsin(x)/np.pi class txt_XRayReaderClass(G2obj.ImportSmallAngleData): diff --git a/GSASII/imports/G2sfact.py b/GSASII/imports/G2sfact.py index d7f82ec18..7c076cc90 100644 --- a/GSASII/imports/G2sfact.py +++ b/GSASII/imports/G2sfact.py @@ -5,7 +5,7 @@ from __future__ import division, print_function import sys import numpy as np -import GSASIIobj as G2obj +from .. import GSASIIobj as G2obj def ColumnValidator(parent, filepointer,nCol=5): 'Validate a file to check that it contains columns of numbers' diff --git a/GSASII/imports/G2sfact_CIF.py b/GSASII/imports/G2sfact_CIF.py index f63069f71..f758f2d90 100644 --- a/GSASII/imports/G2sfact_CIF.py +++ b/GSASII/imports/G2sfact_CIF.py @@ -2,16 +2,28 @@ '''Class to read single-crystal data from a CIF ''' # routines to read in structure factors from a CIF -# +# from __future__ import division, print_function import numpy as np import os.path -import GSASIIobj as G2obj -import CifFile as cif # PyCifRW from James Hester +from .. import GSASIIpath +from .. import GSASIIobj as G2obj +from .. import GSASIIfiles as G2fil +try: + import CifFile as cif # PyCifRW from James Hester as a package +except ImportError: + try: + from .. import CifFile as cif # PyCifRW, as distributed w/G2 (old) + except ImportError: + cif = None class CIFhklReader(G2obj.ImportStructFactor): 'Routines to import Phase information from a CIF file' def __init__(self): + if cif is None: + self.UseReader = False + msg = 'CIF Xtal Reader skipped because PyCifRW (CifFile) module is not installed.' + G2fil.ImportErrorMsg(msg,{'CIF HKLF importer':['pycifrw']}) super(self.__class__,self).__init__( # fancy way to self-reference extensionlist = ('.CIF','.cif','.FCF','.fcf','.HKL','.hkl'), strictExtension = False, @@ -39,7 +51,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): Fdatanames = ('_refln_f_meas','_refln.f_meas','_refln.f_meas_au', ) - + F2datanames = ('_refln_f_squared_meas','_refln.f_squared_meas', '_refln_intensity_meas','_refln.intensity_meas', ) @@ -52,14 +64,14 @@ def Reader(self, filename, ParentFrame=None, **kwarg): Fcalcnames = ('_refln_f_calc','_refln.f_calc','_refln.f_calc_au', ) - + F2calcnames = ('_refln_f_squared_calc','_refln.f_squared_calc', ) Fsignames = ('_refln_f_meas_sigma','_refln.f_meas_sigma','_refln.f_meas_sigma_au', '_refln_f_sigma', ) - + F2signames = ('_refln_f_squared_meas_sigma','_refln.f_squared_meas_sigma', '_refln_f_squared_sigma','_refln.f_squared_sigma', '_refln_intensity_meas_sigma','_refln.intensity_meas_sigma', @@ -70,7 +82,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): SGdataname = ('_symmetry_space_group_name_H-M', '_symmetry.space_group_name_H-M') - + phasenamefields = ( '_chemical_name_common', '_pd_phase_name', @@ -99,13 +111,13 @@ def Reader(self, filename, ParentFrame=None, **kwarg): break else: break # no reflections - for dn in Fdatanames: + for dn in Fdatanames: if dn in blkkeys: blklist.append(blk) gotFo = True break if gotFo: break - for dn in F2datanames: + for dn in F2datanames: if dn in blkkeys: blklist.append(blk) break @@ -139,7 +151,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): if s: choice[-1] += ', cell: ' + s for dn in SGdataname: sg = cf[blknm].get(dn) - if sg: + if sg: choice[-1] += ', (' + sg.strip() + ')' break choice.append('Import all of the above') @@ -148,7 +160,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): self.repeatcount += 1 if self.repeatcount >= len(blklist): self.repeat = False else: - import GSASIIctrlGUI as G2G + from .. import GSASIIctrlGUI as G2G selblk = G2G.BlockSelector( choice, ParentFrame=ParentFrame, @@ -183,7 +195,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): # prepare an index to the CIF reflection loop for i,key in enumerate(refloop.keys()): itemkeys[key.lower()] = i - + # scan for obs & sig data names: F2dn = None Fdn = None @@ -214,9 +226,9 @@ def Reader(self, filename, ParentFrame=None, **kwarg): msg += "A CIF reflection file needs to have at least one of\n" for dn in F2datanames+Fdatanames: msg += dn + ', ' - self.errors += msg + self.errors += msg return False - + # scan for calc data names - might be different! for dm in F2calcnames: if dm in itemkeys: @@ -231,7 +243,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): if dn in itemkeys: Phdn = dn break - + # loop over all reflections for item in refloop: F2c = 0.0 @@ -246,9 +258,9 @@ def Reader(self, filename, ParentFrame=None, **kwarg): HKL.append('.') #h,k,l,tw,dsp,Fo2,sig,Fc2,Fot2,Fct2,phase,Ext if im: - ref = HKL+[1,0,0,0,1, 0,0,0,0,0, 0,0] + ref = HKL+[1,0,0,0,1, 0,0,0,0,0, 0,0] else: - ref = HKL+[1,0,0,1,0, 0,0,0,0,0, 0] + ref = HKL+[1,0,0,1,0, 0,0,0,0,0, 0] if F2dn: F2 = item[itemkeys[F2dn]] if '(' in F2: @@ -272,7 +284,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): sig = 0.0 F2 = F**2 sigF2 = 2.0*F*sig - + try: if F2cdn: F2c = float(item[itemkeys[F2cdn]]) @@ -284,7 +296,7 @@ def Reader(self, filename, ParentFrame=None, **kwarg): F2c = Fc*Fc except: pass - + ref[8+im] = F2 ref[5+im] = F2 ref[6+im] = sigF2 diff --git a/GSASII/imports/__init__.py b/GSASII/imports/__init__.py new file mode 100644 index 000000000..3d2b8ba1c --- /dev/null +++ b/GSASII/imports/__init__.py @@ -0,0 +1,77 @@ +from . import G2img_1TIF +from . import G2img_ADSC +from . import G2img_CBF +from . import G2img_CheMin +from . import G2img_EDF +from . import G2img_GE +from . import G2img_HDF5 +from . import G2img_MAR +from . import G2img_PILTIF +from . import G2img_Rigaku +from . import G2img_SFRM +from . import G2img_SumG2 +from . import G2img_pixirad_1ID_16bit +from . import G2pdf_gr +from . import G2phase +from . import G2phase_CIF +from . import G2phase_GPX +from . import G2phase_INS +from . import G2phase_rmc6f +from . import G2phase_xyz +from . import G2pwd_BrukerBRML +from . import G2pwd_BrukerRAW +from . import G2pwd_CIF +from . import G2pwd_FP +from . import G2pwd_GPX +from . import G2pwd_MIDAS +from . import G2pwd_Panalytical +from . import G2pwd_csv +from . import G2pwd_fxye +from . import G2pwd_rigaku +from . import G2pwd_xye +from . import G2rfd_Panalytical +from . import G2rfd_rigaku +from . import G2rfd_xye +from . import G2sad_xye +from . import G2sfact +from . import G2sfact_CIF + +__all__ = [ + "G2img_1TIF", + "G2img_ADSC", + "G2img_CBF", + "G2img_CheMin", + "G2img_EDF", + "G2img_GE", + "G2img_HDF5", + "G2img_MAR", + "G2img_PILTIF", + "G2img_Rigaku", + "G2img_SFRM", + "G2img_SumG2", + "G2img_pixirad_1ID_16bit", + "G2pdf_gr", + "G2phase", + "G2phase_CIF", + "G2phase_GPX", + "G2phase_INS", + "G2phase_rmc6f", + "G2phase_xyz", + "G2pwd_BrukerBRML", + "G2pwd_BrukerRAW", + "G2pwd_CIF", + "G2pwd_FP", + "G2pwd_GPX", + "G2pwd_MIDAS", + "G2pwd_Panalytical", + "G2pwd_csv", + "G2pwd_fxye", + "G2pwd_rigaku", + "G2pwd_xye", + "G2rfd_Panalytical", + "G2rfd_rigaku", + "G2rfd_xye", + "G2sad_xye", + "G2sfact", + "G2sfact_CIF", +] diff --git a/GSASII/imports/meson.build b/GSASII/imports/meson.build new file mode 100644 index 000000000..88c9ea026 --- /dev/null +++ b/GSASII/imports/meson.build @@ -0,0 +1,43 @@ +py.install_sources([ + '__init__.py', + 'G2img_1TIF.py', + 'G2img_ADSC.py', + 'G2img_CBF.py', + 'G2img_CheMin.py', + 'G2img_EDF.py', + 'G2img_GE.py', + 'G2img_HDF5.py', + 'G2img_MAR.py', + 'G2img_PILTIF.py', + 'G2img_Rigaku.py', + 'G2img_SFRM.py', + 'G2img_SumG2.py', + 'G2img_pixirad_1ID_16bit.py', + 'G2pdf_gr.py', + 'G2phase.py', + 'G2phase_CIF.py', + 'G2phase_GPX.py', + 'G2phase_INS.py', + 'G2phase_rmc6f.py', + 'G2phase_xyz.py', + 'G2pwd_BrukerBRML.py', + 'G2pwd_BrukerRAW.py', + 'G2pwd_CIF.py', + 'G2pwd_FP.py', + 'G2pwd_GPX.py', + 'G2pwd_MIDAS.py', + 'G2pwd_Panalytical.py', + 'G2pwd_csv.py', + 'G2pwd_fxye.py', + 'G2pwd_rigaku.py', + 'G2pwd_xye.py', + 'G2rfd_Panalytical.py', + 'G2rfd_rigaku.py', + 'G2rfd_xye.py', + 'G2sad_xye.py', + 'G2sfact.py', + 'G2sfact_CIF.py', + ], + pure: false, # Will be installed next to binaries + subdir: 'GSASII/imports' # Folder relative to site-packages to install to +) diff --git a/GSASII/inputs/BinariesCache.txt b/GSASII/inputs/BinariesCache.txt index 5e7f86b78..cded0eb8c 100644 --- a/GSASII/inputs/BinariesCache.txt +++ b/GSASII/inputs/BinariesCache.txt @@ -1,6 +1,14 @@ linux_64_p3.11_n1.26 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/linux_64_p3.11_n1.26.tgz +linux_64_p3.12_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/linux_64_p3.12_n2.2.tgz +linux_64_p3.13_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/linux_64_p3.13_n2.2.tgz linux_arm32_p3.11_n1.24 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/linux_arm32_p3.11_n1.24.tgz linux_arm64_p3.11_n1.26 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/linux_arm64_p3.11_n1.26.tgz mac_64_p3.11_n1.26 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/mac_64_p3.11_n1.26.tgz +mac_64_p3.12_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/mac_64_p3.12_n2.2.tgz +mac_64_p3.13_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/mac_64_p3.13_n2.2.tgz mac_arm_p3.11_n1.26 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/mac_arm_p3.11_n1.26.tgz +mac_arm_p3.12_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/mac_arm_p3.12_n2.2.tgz +mac_arm_p3.13_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/mac_arm_p3.13_n2.2.tgz win_64_p3.11_n1.26 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/win_64_p3.11_n1.26.tgz +win_64_p3.12_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/win_64_p3.12_n2.2.tgz +win_64_p3.13_n2.2 : https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/download/v1.0.1/win_64_p3.13_n2.2.tgz diff --git a/GSASII/inputs/meson.build b/GSASII/inputs/meson.build new file mode 100644 index 000000000..a696940ad --- /dev/null +++ b/GSASII/inputs/meson.build @@ -0,0 +1,15 @@ +foreach file: ['DefaultExpressions.txt', 'OriginTemplate2.otpu', 'versioninfo.txt', 'Xsect.dat'] + + install_data( + file, + install_tag: 'data', + install_dir: py.get_install_dir(subdir: 'GSASII/inputs') + ) + +endforeach + +install_subdir( + 'GSASIImacros', + install_tag: 'data', + install_dir: py.get_install_dir(subdir: 'GSASII/inputs') +) diff --git a/GSASII/install/git_filters.py b/GSASII/install/git_filters.py.old similarity index 100% rename from GSASII/install/git_filters.py rename to GSASII/install/git_filters.py.old diff --git a/GSASII/install/tag-version.py b/GSASII/install/incr-version.py similarity index 57% rename from GSASII/install/tag-version.py rename to GSASII/install/incr-version.py index bd315ca4c..507e91588 100644 --- a/GSASII/install/tag-version.py +++ b/GSASII/install/incr-version.py @@ -1,28 +1,32 @@ -# create numerical tag number for the latest git check in and record that in -# the git_version.py file. This also advances the minor version number -# for the GSAS-II version number (from 5.X.Y to 5.X+1.0) +# create a new numerical tag number for the most recent git checkin +# and advance the "mini" GSAS-II version number (from 5.X.Y to 5.X.Y+1) +# or the minor GSAS-II version number (from 5.X.Y to 5.X+1.0) +# Record the hash and version numbers in the git_version.py file. -# perhaps someday this should be made automatic in some fashion (perhaps -# not used on every check-in but don't go too many days/mods without a new -# version # +# This routine can only be used where HEAD and HEAD^1 have not been a tag -# perhaps someday include as a clean (run on git add) or smudge -# step (run on git pull). -# Alternately, on commit/pull might get a count of how many untagged -# check-ins there have been. -# -# [filter "createVersionFile"] -# clean = python git_filters.py --tag-version -# smudge = python git_filters.py --record-version -# for debug of auto-run scripts, include a redirect in the script to -# send output to a log file: -# sys.stderr = sys.stdout = open('/tmp/gitfilter.log','a') -# import os import sys import datetime as dt import git +mode = 'mini' +if len(sys.argv) > 1: + if sys.argv[1].lower() == 'minor': + mode = 'minor' + elif sys.argv[1].lower() == 'mini': + mode = 'mini' + else: + mode = 'help' + +if mode == 'help': + print('Use this command either as ') + print(f'\t{sys.argv[0]} minor') + print('or') + print(f'\t{sys.argv[0]} mini') + print('If no argument is supplied "mini" is assumed') + sys.exit() + # get location of the GSAS-II files # assumed to be the parent of location of this file path2GSAS2 = os.path.dirname(os.path.dirname( @@ -32,17 +36,40 @@ if __name__ == '__main__': - g2repo = git.Repo(path2repo) -# if g2repo.active_branch.name != 'master': -# print('Not on master branch') -# sys.exit() + try: + g2repo = git.Repo(path2repo) + except: + print('Launch of gitpython for version file failed'+ + f' with path {path2repo}') + sys.exit() + if g2repo.active_branch.name != 'main': + print('Not on main branch') + sys.exit() if g2repo.head.is_detached: print(f'Detached head {commit0[:7]!r}') sys.exit() - # make a list of tags without a dash; get the largest numeric tag + # make a list of tags without a v; get the largest numeric tag # someday use the packaging module (but no more dependencies for now) - numtag = [i for i in g2repo.tags if '-' not in i.name] + numtag = [i for i in g2repo.tags if 'v' not in i.name] max_numeric = max([int(i.name) for i in numtag if i.name.isdecimal()]) + commit = g2repo.head.commit + now = dt.datetime.now().replace( + tzinfo=commit.committed_datetime.tzinfo) + commit0 = commit.hexsha + # is the newest commit tagged? + tags0 = g2repo.git.tag('--points-at',commit) + if tags0: tags0 = tags0.split('\n') + if tags0: + print(f'Latest commit ({commit.hexsha[:7]}) is already tagged ({", ".join(tags0)}).') + sys.exit() + prev = g2repo.head.commit.parents + if len(prev) == 1: + tagsm1 = g2repo.git.tag('--points-at',prev[0]) + if tagsm1: tagsm1 = tagsm1.split('\n') + if tagsm1: + print(f'Previous commit ({prev[0].hexsha[:7]}) is already tagged ({", ".join(tagsm1)}).') + sys.exit() + # get the latest version number releases = [i for i in g2repo.tags if '.' in i.name and i.name.startswith('v')] majors = [i.name.split('.')[0][1:] for i in releases] @@ -54,42 +81,29 @@ # for now, ignore anything with letters or decimals mini = max([int(i) for i in minis if i.isdecimal()]) latest = f'{major}.{minor}.{mini}' - #nextmini = f'v{major}.{minor}.{mini+1}' - nextminor = f'v{major}.{minor+1}.0' - versiontag = nextminor + if mode == 'mini': + versiontag = f'v{major}.{minor}.{mini+1}' + elif mode == 'minor': + versiontag = f'v{major}.{minor+1}.0' + else: + print('unexpected mode',mode) + sys.exit() if versiontag in releases: print(f'Versioning problem, generated next version {versiontag} already defined!') versiontag = '?' - - # is the newest commit tagged? - c = g2repo.head.commit - tags = g2repo.git.tag('--points-at',c).split('\n') - if tags != ['']: - print(f'Latest commit ({c.hexsha[:7]}) is already tagged ({tags}).') sys.exit() - # add a tag to the newest commit + if versiontag != '?': + g2repo.create_tag(str(versiontag),ref=commit) + print(f'created version # {versiontag} for {commit.hexsha[:7]}') + + # add a numeric tag to the newest commit as well tagnum = max_numeric + 1 while str(tagnum) in g2repo.tags: print(f'Error: {tagnum} would be repeated') tagnum += 1 - g2repo.create_tag(str(tagnum),ref=c) - print(f'created tag {tagnum} for {c.hexsha[:7]}') - if versiontag != '?': - g2repo.create_tag(str(versiontag),ref=c) - print(f'created version # {versiontag} for {c.hexsha[:7]}') + g2repo.create_tag(str(tagnum),ref=commit) + print(f'created tag {tagnum} for {commit.hexsha[:7]}') - # create a file with GSAS-II version information - try: - g2repo = git.Repo(path2repo) - except: - print('Launch of gitpython for version file failed'+ - f' with path {path2repo}') - sys.exit() - commit = g2repo.head.commit - #ctim = commit.committed_datetime.strftime('%d-%b-%Y %H:%M') - now = dt.datetime.now().replace( - tzinfo=commit.committed_datetime.tzinfo) - commit0 = commit.hexsha tags0 = [i for i in g2repo.git.tag('--points-at',commit).split('\n') if i.isdecimal()] history = list(g2repo.iter_commits('HEAD')) for i in history[1:]: @@ -99,6 +113,7 @@ tagsm1 = [i for i in tags.split('\n') if i.isdecimal()] if not tagsm1: continue break + # create a file with GSAS-II version information pyfile = os.path.join(path2GSAS2,'git_verinfo.py') try: fp = open(pyfile,'w') @@ -120,9 +135,12 @@ fp.close() print(f'Created git version file {pyfile} at {now} for {commit0[:7]!r}') - print('Now do:\n\t git add \n\t git commit \n\t git push \n\t git push --tags (better than git push --follow-tags?)') + g2repo.index.add([pyfile]) + g2repo.index.commit('increment {mode} version') + print('committed',pyfile,'mode=',mode) + g2repo.remote(name='origin').push() + g2repo.remotes.origin.push(versiontag) + g2repo.remotes.origin.push(str(tagnum)) + print('pushed update and tags',versiontag,tagnum) -# Git 2.4 has added the push.followTags option to turn that flag on by default which you can set with: -# -# git config --global push.followTags true -# or by adding followTags = true to the [push] section of your ~/.gitconfig file. +# print('Now do:\n\t git add \n\t git commit \n\t git push \n\t git push --tags\n (try "git push origin HEAD --tags")') diff --git a/GSASII/install/macStartScript.py b/GSASII/install/macStartScript.py new file mode 100644 index 000000000..61ee33621 --- /dev/null +++ b/GSASII/install/macStartScript.py @@ -0,0 +1,280 @@ +# create a MacOS applet to run GSAS-II. The purpose of the Applet is +# so that the Apple menus are named GSAS-II rather than Python. It also +# allows .gpx files to be dropped on the applet. + +# this runs but has not been fully tested + +import sys +import os +import subprocess +import shutil +import plistlib +import platform + +makePath = os.path.dirname(__file__) # location of this script +path2GSAS = os.path.dirname(makePath) +appPath = '/tmp/GSAS-II.app' +projectname = 'GSAS-II' +#G2script = os.path.abspath(os.path.join(path2GSAS,"G2.py")) + +if __name__ == '__main__' and sys.platform == "darwin": + iconfile = os.path.join(path2GSAS,'icons','gsas2.icns') # optional icon file + if not os.path.exists(iconfile): # patch 3/2024 for svn dir organization + iconfile = os.path.join(path2GSAS,'gsas2.icns') # optional icon file + +# AppleScript = '''(* GSAS-II AppleScript by B. Toby (brian.toby@anl.gov) +# It can launch GSAS-II by double clicking or by dropping a data file +# or folder over the app. +# It runs GSAS-II in a terminal window. +# *) + +# (* test if a file is present and exit with an error message if it is not *) +# on TestFilePresent(appwithpath) +# tell application "System Events" +# if (file appwithpath exists) then +# else +# display dialog "Error: file " & appwithpath & " not found. If you have moved this file recreate the AppleScript with bootstrap.py." with icon caution buttons {{"Quit"}} +# return +# end if +# end tell +# end TestFilePresent + +# (* +# ------------------------------------------------------------------------ +# this section responds to a double-click. No file is supplied to GSAS-II +# ------------------------------------------------------------------------ +# *) +# on run +# set python to "{:s}" +# set appwithpath to "{:s}" +# set env to "{:s}" +# TestFilePresent(appwithpath) +# TestFilePresent(python) +# tell application "Terminal" +# do script env & python & " " & appwithpath & "; exit" +# end tell +# end run + +# (* +# ----------------------------------------------------------------------------------------------- +# this section handles starting with files dragged into the AppleScript +# o it goes through the list of file(s) dragged in +# o then it converts the colon-delimited macintosh file location to a POSIX filename +# o for every non-directory file dragged into the icon, it starts GSAS-II, passing the file name +# ------------------------------------------------------------------------------------------------ +# *) + +# on open names +# set python to "{:s}" +# set appwithpath to "{:s}" +# set env to "{:s}" + +# TestFilePresent(appwithpath) +# repeat with filename in names +# set filestr to (filename as string) +# if filestr ends with ":" then +# (* should not happen, skip directories *) +# else +# (* if this is an input file, open it *) +# set filename to the quoted form of the POSIX path of filename +# tell application "Terminal" +# activate +# do script env & python & " " & appwithpath & " " & filename & "; exit" +# end tell +# end if +# end repeat +# end open +# ''' + AppleScript = '''(* GSAS-II AppleScript by B. Toby (brian.toby@anl.gov) + It can launch GSAS-II by double clicking or by dropping a data file + or folder over the app. + It runs GSAS-II in a terminal window. + + This is intended for use with a conda-based GSAS-II distribution where + Python is linked from a file (./Contents/MacOS/GSAS-II) inside the current app, + and where the GSAS-II .py files are located in the same directory as this + script. This can be renamed but cannot be moved. +*) + +on GetPythonLocation() + (* find python in this script's bundle *) + tell application "Finder" + set scriptpath to the POSIX path of (path to me) + end tell + set python to (scriptpath & "Contents/MacOS/GSAS-II") + TestFilePresent(python) + return python +end GetPythonLocation + +on GetG2Location() + (* find the GSAS-II.py script in the same directory as this script *) + tell application "Finder" + set scriptdir to the POSIX path of (container of (path to me) as alias) + end tell + set g2script to (scriptdir & "GSAS-II.py") + TestFilePresent(g2script) + return g2script +end GetG2Location + +on TestFilePresent(filepath) + (* test if a file is present and exit with an error message if it is not *) + tell application "System Events" + if (file filepath exists) then + else + display dialog "Error: file " & filepath & " not found. Was this app moved from the GSASII directory?" with icon caution buttons {"Quit"} + error number -128 + end if + end tell +end TestFilePresent + +(* +---------------------------------------------------------------------------- +this section responds to a double-click. No file is supplied to GSAS-II +---------------------------------------------------------------------------- +*) +on run + set python to GetPythonLocation() + set appwithpath to GetG2Location() + + tell application "Terminal" + activate + do script (quoted form of python) & " " & (quoted form of appwithpath) & "; exit" + end tell +end run + +(* +----------------------------------------------------------------------------------------------- +this section handles starting with files dragged into the AppleScript + o it goes through the list of file(s) dragged in + o then it converts the colon-delimited macintosh file location to a POSIX filename + o for every non-directory file dragged into the icon, it starts GSAS-II, passing the file name +------------------------------------------------------------------------------------------------ +*) +on open names + set python to GetPythonLocation() + set appwithpath to GetG2Location() + + repeat with filename in names + set filestr to (filename as string) + if filestr ends with ":" then + (* should not happen, skip directories *) + else + (* if this is an input file, open it *) + set filename to the quoted form of the POSIX path of filename + tell application "Terminal" + activate + do script python & " " & appwithpath & " " & filename & "; exit" + end tell + end if + end repeat +end open +''' + # # create a link named GSAS-II.py to the script + # newScript = os.path.join(path2GSAS,'GSAS-II.py') + # if os.path.exists(newScript): # cleanup + # print("\nRemoving sym link",newScript) + # os.remove(newScript) + # os.symlink(os.path.split(G2script)[1],newScript) + # G2script=newScript + + # find Python used to run GSAS-II and set a new to use to call it + # inside the app that will be created + pythonExe = os.path.realpath(sys.executable) + newpython = os.path.join(appPath,"Contents","MacOS",projectname) + + # create an app using this python and if that fails to run wx, look for a + # pythonw and try that with wx + for i in 1,2,3: + if os.path.exists(appPath): # cleanup + print("\nRemoving old "+projectname+" app ("+str(appPath)+")") + shutil.rmtree(appPath) + + shell = os.path.join("/tmp/","appscrpt.script") + f = open(shell, "w") + #f.write(AppleScript.format(newpython,G2script,'',newpython,G2script,'')) + f.write(AppleScript) + f.close() + + try: + subprocess.check_output(["osacompile","-o",appPath,shell],stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as msg: + print('''Error compiling AppleScript. + Report the next message along with details about your Mac to toby@anl.gov''') + print(msg.output) + sys.exit() + # create a link to conda python relative to this app, named to match the project + os.symlink('../../../../bin/python',newpython) + + # # test if newpython can run wx + # def RunPython(image,cmd): + # 'Run a command in a python image' + # try: + # err=None + # p = subprocess.Popen([image,'-c',cmd],stdout=subprocess.PIPE,stderr=subprocess.PIPE) + # out = p.stdout.read() + # err = p.stderr.read() + # p.communicate() + # return out,err + # except Exception(err): + # return '','Exception = '+err + + # testout,errout = RunPython(newpython,'import numpy; import wx; wx.App(); print("-"+"OK-")') + # if isinstance(testout,bytes): testout = testout.decode() + # if "-OK-" in testout: + # print('wxpython app ran',testout) + # break + # elif i == 1: + # print('Run of wx in',pythonExe,'failed, looking for pythonw') + # pythonExe = os.path.join(os.path.split(sys.executable)[0],'pythonw') + # if not os.path.exists(pythonExe): + # print('Warning no pythonw found with ',sys.executable, + # '\ncontinuing, hoping for the best') + # elif i == 2: + # print('Warning could not run wx with',pythonExe, + # 'will try with that external to app') + # newpython = pythonExe + # else: + # print('Warning still could not run wx with',pythonExe, + # '\ncontinuing, hoping for the best') + + # change the icon + oldicon = os.path.join(appPath,"Contents","Resources","droplet.icns") + #if os.path.exists(iconfile) and os.path.exists(oldicon): + if os.path.exists(iconfile): + shutil.copyfile(iconfile,oldicon) + + # Edit the app plist file to restrict the type of files that can be dropped + if hasattr(plistlib,'load'): + fp = open(os.path.join(appPath,"Contents",'Info.plist'),'rb') + d = plistlib.load(fp) + fp.close() + else: + d = plistlib.readPlist(os.path.join(appPath,"Contents",'Info.plist')) + d['CFBundleDocumentTypes'] = [{ + 'CFBundleTypeExtensions': ['gpx'], + 'CFBundleTypeName': 'GSAS-II project', + 'CFBundleTypeRole': 'Editor'}] + + if hasattr(plistlib,'dump'): + fp = open(os.path.join(appPath,"Contents",'Info.plist'),'wb') + plistlib.dump(d,fp) + fp.close() + else: + plistlib.writePlist(d,os.path.join(appPath,"Contents",'Info.plist')) + + # Big Sur: open & save the file in the editor to set authorization levels + osascript = ''' + tell application "Script Editor" + set MyName to open "{}" + save MyName + (* close MyName *) + (* quit *) + end tell +'''.format(appPath) + # detect MacOS 11 (11.0 == 10.16!) + if platform.mac_ver()[0].split('.')[0] == '11' or platform.mac_ver()[0][:5] == '10.16': + print("\nFor Big Sur and later, save the app in Script Editor before using it\n") + subprocess.Popen(["osascript","-e",osascript]) + print("\nCreated "+projectname+" app ("+str(appPath)+ + ").\nViewing app in Finder so you can drag it to the dock if, you wish.") + subprocess.call(["open","-R",appPath]) diff --git a/GSASII/install/makeBat.py b/GSASII/install/makeBat.py index c8c5b6f5a..719f95628 100644 --- a/GSASII/install/makeBat.py +++ b/GSASII/install/makeBat.py @@ -2,21 +2,21 @@ ''' This script creates a file named ``RunGSASII.bat`` and a desktop shortcut to that file. It registers the filetype .gpx so that the GSAS-II project files exhibit the -GSAS-II icon and so that double-clicking on them opens them in GSAS-II. +GSAS-II icon and so that double-clicking on them opens them in GSAS-II. -Run this script with no arguments; the path to the ``GSASII.py`` file -is assumed to be in the parent directory to the one where this file +Run this script with no arguments; the path to the ``G2.py`` file +is assumed to be in the parent directory to the one where this file (``makeBat.py``) is found. The contents of this file may also be run from inside the gitstrap.py -installation script. In that case, the following variables are already -defined: +installation script. In that case, the following variables are already +defined: * path2GSAS2 is the directory with all GSAS-II Python code - * G2script has the location of the GSASII.py file + * G2script has the location of the G2.py file * path2repo is the location of the GSAS-II git repository -The path to Python is determined from the version of Python used to +The path to Python is determined from the version of Python used to run this script. ''' # @@ -40,8 +40,8 @@ pause ''' - -app = None # delay starting wx until we need it. Likely not needed. + +app = None # delay starting wx until we need it. Likely not needed. if __name__ == '__main__': try: import winreg @@ -56,7 +56,7 @@ invokedDirectly = True path2GSAS2 = os.path.dirname(os.path.dirname(__file__)) path2repo = os.path.dirname(path2GSAS2) - G2script = os.path.join(path2GSAS2,'GSASII.py') + G2script = os.path.join(path2GSAS2,'G2.py') else: print(f"running makeBat.py indirectly inside {__file__!r}") invokedDirectly = False @@ -68,7 +68,7 @@ print('Python installed at ',pythonexe) print('GSAS-II installed at',path2GSAS2) - print('GSASII.py at ',G2script) + print('G2.py at ',G2script) print('GSASII icon at ',G2icon) print('.bat file to be at ',G2bat) @@ -97,7 +97,7 @@ fp.write(Script.format(activate,pexe,G2s)) fp.close() print(f'\nCreated GSAS-II batch file {G2bat}') - + # create a reset script gitstrap = os.path.abspath( os.path.normpath(os.path.join(path2repo,'..','gitstrap.py'))) @@ -147,7 +147,7 @@ if app is None: import wx app = wx.App() - dlg = wx.MessageDialog(None,'gpx files already assigned in registry to: \n'+oldBat+'\n Replace with: '+G2bat+'?','GSAS-II gpx in use', + dlg = wx.MessageDialog(None,'gpx files already assigned in registry to: \n'+oldBat+'\n Replace with: '+G2bat+'?','GSAS-II gpx in use', wx.YES_NO | wx.ICON_QUESTION | wx.STAY_ON_TOP) dlg.Raise() if dlg.ShowModal() == wx.ID_YES: diff --git a/GSASII/install/makeLinux.py b/GSASII/install/makeLinux.py index afde9bccc..5584d2f9f 100644 --- a/GSASII/install/makeLinux.py +++ b/GSASII/install/makeLinux.py @@ -1,30 +1,30 @@ #!/usr/bin/env python ''' This script creates a menu entry and dektop shortcut for Gnome -(and perhaps KDE) desktop managers. The most recent testing +(and perhaps KDE) desktop managers. The most recent testing has been on Raspberry Pi OS. -My hope is to improve this further to work conveniently with a wider -range of Linux desktop managers. +My hope is to improve this further to work conveniently with a wider +range of Linux desktop managers. -Run this script with one optional argument, the location of the GSASII.py +Run this script with one optional argument, the location of the G2.py file. That location may be specified relative to the current path or given -an absolute path, but will be accessed via an absolute path. -If no arguments are supplied, the path to the ``GSASII.py`` file -is assumed to be in the parent directory to the one where this file -(``makeLinux.py``) is found. +an absolute path, but will be accessed via an absolute path. +If no arguments are supplied, the path to the ``G2.py`` file +is assumed to be in the parent directory to the one where this file +(``makeLinux.py``) is found. The contents of this file may also be run from inside the gitstrap.py -installation script. In that case, the following variables are already -defined: +installation script. In that case, the following variables are already +defined: * path2GSAS2 is the directory with all GSAS-II Python code - * G2script has the location of the GSASII.py file + * G2script has the location of the G2.py file * path2repo is the location of the GSAS-II git repository -The path to Python is determined from the version of Python used to +The path to Python is determined from the version of Python used to run this script. ''' -import sys, os, os.path, stat, shutil, subprocess, plistlib +import sys, os, os.path, stat, shutil import datetime desktop_template = """ [Desktop Entry] @@ -45,7 +45,7 @@ def Usage(): # find the main GSAS-II script if not on command line path2GSAS2 = os.path.dirname(os.path.dirname(__file__)) path2repo = os.path.dirname(path2GSAS2) - G2script = os.path.abspath(path2GSAS2,"GSASII.py") + G2script = os.path.abspath(path2GSAS2,"G2.py") elif __file__.endswith("makeLinux.py") and len(sys.argv) == 2: G2script = os.path.abspath(sys.argv[1]) path2GSAS2 = os.path.dirname(G2script) @@ -56,10 +56,10 @@ def Usage(): print(f"running makeLinux.py indirectly inside {__file__!r}") pythonexe = os.path.realpath(sys.executable) G2icon = os.path.join(path2GSAS2,'icons','gsas2.png') - + print('Python installed at ',pythonexe) print('GSAS-II installed at',path2GSAS2) - print('GSASII.py at ',G2script) + print('G2.py at ',G2script) print('GSASII icon at ',G2icon) # make sure we have the stuff we need @@ -72,7 +72,7 @@ def Usage(): if os.path.splitext(G2script)[1] != '.py': print("\nScript "+G2script+" does not have extension .py") Usage() - + mfile = None # menu file if "XDG_DATA_HOME" in os.environ and os.path.exists( os.path.join(os.environ.get("XDG_DATA_HOME"),"applications")): @@ -104,7 +104,6 @@ def Usage(): stat.S_IXUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IXOTH) script = '' - import shutil for term in ("lxterminal", "gnome-terminal", 'konsole', "xterm", "terminator", "terminology", "tilix"): try: @@ -139,7 +138,7 @@ def Usage(): elif term == "terminology": terminal = term + ' -T="GSAS-II console" --hold -e' script = "echo; echo This window can now be closed" - break + break else: print("unknown terminal",term) sys.exit() @@ -147,13 +146,12 @@ def Usage(): print("No terminal found -- no shortcuts created") sys.exit() add2script = '' - if script: add2script = '; ' + script + if script: add2script = '; ' + script for f,t in zip((dfile,mfile),('Desktop','Menu')): if f is None: continue try: - open(f,'w').write(desktop_template.format( - terminal, - G2start+add2script,G2icon)) + with open(f,'w') as fp: + fp.write(desktop_template.format(terminal,G2start+add2script,G2icon)) os.chmod( dfile, stat.S_IWUSR | stat.S_IXUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IXOTH) diff --git a/GSASII/install/makeMacApp.py b/GSASII/install/makeMacApp.py index ecbdbe22c..feee3a81a 100644 --- a/GSASII/install/makeMacApp.py +++ b/GSASII/install/makeMacApp.py @@ -2,88 +2,87 @@ #------------------------------------------------------------ # this version intended for use with git installations #------------------------------------------------------------ +# TODO: clean up use of args: installLoc will always be the parent of path2GSAS ''' -This routine creates an app bundle named GSAS-II.app. Inside the -bundle is a symbolic link to the Python executable named "GSAS-II" +This routine creates an app bundle named GSAS-II.app. Inside the +bundle is a symbolic link to the Python executable named "GSAS-II" that will be used to run GSAS-II. Having this link named that -way causes the name of the app to shows in the menu bar as -"GSAS-II" rather than "Python". Also used by the app, is another -symbolic link named GSAS-II.py, which must be placed in the same -directory as the app bundle. This file is linked to the GSASII.py -script and the link is run using the link to Python. This also -causes other items in the app to be labeled as GSAS-II (but not -with the right capitalization, alas). - -The original contents of the app bundle was created interactively -and, after some manual edits, the contents of that was placed into -a tar file distributed with GSAS-II, and is expanded in this script. -This method seems to be needed for MacOS 11.0+ (Big Sur and later) -where Apple's security constraints seem to prevent creation of the -app directly. Older code (not currently in use) created the +way causes the name of the app to shows in the menu bar as +"GSAS-II" rather than "Python". Also used by the app, is another +symbolic link named GSAS-II.py, which must be placed in the same +directory as the app bundle. This file is linked to the G2.py +script and the link is run using the link to Python. This also +causes other items in the app to be labeled as GSAS-II (but not +with the right capitalization, alas). + +The original contents of the app bundle was created interactively +and, after some manual edits, the contents of that was placed into +a tar file distributed with GSAS-II, and is expanded in this script. +This method seems to be needed for MacOS 11.0+ (Big Sur and later) +where Apple's security constraints seem to prevent creation of the +app directly. Older code (not currently in use) created the app from "scratch" using the osacompile utility, but that no longer seems to work. -Three different paths are needed to run this script:: +Three different paths are needed to run this script:: - path2GSAS: The location where the GSASII.py (and other GSAS-II - Python files) are found. - installLoc: The location where the GSAS-II.app app bundle and - the GSAS-II.py will be placed. - pythonLoc: The location of the Python executable. + path2GSAS: The location where the GSAS-II Python files are found. + installLoc: The location where the GSAS-II.app app bundle and + the GSAS-II.py will be placed. This will be the parent of path2GSAS + pythonLoc: The location of the Python executable. -Under normal circumstances, the locations for all of these paths -can be determined from the location of the makeMacApp.py file. -Note that when GSAS-II is installed from git using gitstrap.py, -the git repository is placed at /GSAS-II and the GSAS-II -Python scripts are placed at the GSASII child directory, so that -GSAS-II is started from the GSASII.py script at /GSAS-II/GSASII/ -and the current script (makeMacApp.py) will be found in -/GSAS-II/GSASII/install/. +Under normal circumstances, the locations for all of these paths +can be determined from the location of the makeMacApp.py file. +Note that when GSAS-II is installed from git using gitstrap.py, +the git repository is placed at /GSAS-II and the GSAS-II +Python scripts are placed in the /GSAS-II/GSASII child directory. +GSAS-II is started from the /GSAS-II/GSAS-II.py script created here +and the current script (makeMacApp.py) will be found in +/GSAS-II/GSASII/install/. -When the GSAS-II conda installers -are used, the git repository is placed at $CONDA_HOME/GSAS-II so that +When the GSAS-II conda installers +are used, the git repository is placed at $CONDA_HOME/GSAS-II so that above is $CONDA_HOME. Also, the Python executable will be found -in $CONDA_HOME/bin/Python. Thus, if this file is in makePath (typically -/GSAS-II/GSASII/install), then +in $CONDA_HOME/bin/Python. Thus, if this file is in makePath (typically +/GSAS-II/GSASII/install), then + + * path2GSAS will be makePath/.. and + * installLoc will be path2GSAS/.. and + * pythonLoc will be installLoc/../bin/python, - * path2GSAS will be makePath/.. and - * installLoc will be path2GSAS/.. and - * pythonLoc will be installLoc/../bin/python, +but these locations can be overridden from the command-line arguments. +If a Python location is not supplied and is not at the default location +(installLoc/../bin/python) then the Python executable currently +running this script (from sys.executable) is used. -but these locations can be overridden from the command-line arguments. -If a Python location is not supplied and is not at the default location -(installLoc/../bin/python) then the Python executable currently -running this script (from sys.executable) is used. - Run this script with no arguments or with one or two arguments. -The first argument, if supplied, provides the path to be used for the +The first argument, if supplied, provides the path to be used for the app bundle will be created. Note that GSAS-II.app and GSAS-II.py will -be created in this directory. +be created in this directory. -The second argument, if supplied, is path2GSAS, a path to the -location GSASII.py script, which can be a relative path -(the absolute path is determined). If not supplied, the GSASII.py script -is expected to be located in the directory above where this -(makeMacApp.py) script is found. +The second argument, if supplied, is path2GSAS, a path to the +location G2.py script, which can be a relative path +(the absolute path is determined). If not supplied, the G2.py script +is expected to be located in the directory above where this +(makeMacApp.py) script is found. -The third argument, if supplied, provides the full path for the Python -installation to be used inside the app bundle that will be created. If not -supplied, and Python exists at installLoc/../bin/python, that will be used. +The third argument, if supplied, provides the full path for the Python +installation to be used inside the app bundle that will be created. If not +supplied, and Python exists at installLoc/../bin/python, that will be used. If that does not exist, then the location of the current Python executable -(from sys.executable) will be used. +(from sys.executable) will be used. ''' from __future__ import division, print_function -import sys, os, os.path, stat, shutil, subprocess, plistlib -import platform +import sys, os, os.path, subprocess def Usage(): print(f"\nUsage:\n\tpython {os.path.abspath(sys.argv[0])} [install_path] [] [Python_loc]\n") sys.exit() AppleScript = '' -'''Will be set to contain an AppleScript to start GSAS-II by launching +'''Will be set to contain an AppleScript to start GSAS-II by launching Python and the GSAS-II Python script. Not currently used. ''' @@ -104,13 +103,13 @@ def Usage(): if len(sys.argv) == 4: pythonLoc = os.path.abspath(sys.argv[3]) # sanity checking - G2script = os.path.abspath(os.path.join(path2GSAS,"GSASII.py")) - if not os.path.exists(G2script): - print(f"\nERROR: File {G2script!r} not found") - Usage() - if os.path.splitext(G2script)[1].lower() != '.py': - print(f"\nScript {G2script!r} does not have extension .py") - Usage() + # G2script = os.path.abspath(os.path.join(path2GSAS,"G2.py")) + # if not os.path.exists(G2script): + # print(f"\nERROR: File {G2script!r} not found") + # Usage() + # if os.path.splitext(G2script)[1].lower() != '.py': + # print(f"\nScript {G2script!r} does not have extension .py") + # Usage() if not os.path.exists(installLoc): print(f"\nERROR: directory {installLoc!r} not found") Usage() @@ -127,25 +126,36 @@ def Usage(): Usage() print(f'Using Python: {pythonLoc}') - print(f'Using GSAS-II script: {G2script}') + #print(f'Using GSAS-II script: {G2script}') print(f'Install location: {installLoc}') # files to be created appName = os.path.abspath(os.path.join(installLoc,project+".app")) g2Name = os.path.abspath(os.path.join(installLoc,project+'.py')) -# new approach, use previously created tar (.tgz) file +# new approach, use previously created tar (.tgz) file +# with pre-built startup app. See macStartScript.py if __name__ == '__main__' and sys.platform == "darwin": if os.path.exists(appName): print(f"\nRemoving old Mac app {appName!r}") subprocess.call(["rm","-rf",appName]) subprocess.call(["mkdir","-p",appName]) subprocess.call(["tar","xzvf",tarLoc,'-C',appName]) - # create a link named GSAS-II.py to the script + # create a script named GSAS-II.py to be run by the AppleScript if os.path.exists(g2Name): # cleanup - print(f"\nRemoving sym link {g2Name!r}") + print(f"\nRemoving {g2Name!r}") os.remove(g2Name) - os.symlink(G2script,g2Name) + #os.symlink(G2script,g2Name) + with open(g2Name,'w') as fp: + fp.write('''# this script starts GSAS-II when not installed into Python +# it will be called from the GSAS-II.app AppleScript +# it should be not be renamed or moved +import sys,os +print('Hacking sys.path to provide access to GSAS-II') +sys.path.insert(0,os.path.dirname(__file__)) +from GSASII.GSASIIGUI import main +main() +''') if pythonLoc != os.path.join(installLoc,'../bin',"python"): link = os.path.join(appName,'Contents','MacOS','GSAS-II') try: @@ -158,183 +168,5 @@ def Usage(): print(f"\nCreated app {appName!r} and {g2Name!r}" + "\nViewing app in Finder so you can drag it to the dock if, you wish.") subprocess.call(["open","-R",appName]) - - sys.exit() - -#if __name__ == '__main__' and sys.platform == "darwin": -if False: - iconfile = os.path.join(path2GSAS,'icons','gsas2.icns') # optional icon file - if not os.path.exists(iconfile): # patch 3/2024 for svn dir organization - iconfile = os.path.join(path2GSAS,'gsas2.icns') # optional icon file - - AppleScript = '''(* GSAS-II AppleScript by B. Toby (brian.toby@anl.gov) - It can launch GSAS-II by double clicking or by dropping a data file - or folder over the app. - It runs GSAS-II in a terminal window. -*) - -(* test if a file is present and exit with an error message if it is not *) -on TestFilePresent(appwithpath) - tell application "System Events" - if (file appwithpath exists) then - else - display dialog "Error: file " & appwithpath & " not found. If you have moved this file recreate the AppleScript with bootstrap.py." with icon caution buttons {{"Quit"}} - return - end if - end tell -end TestFilePresent - -(* ------------------------------------------------------------------------- -this section responds to a double-click. No file is supplied to GSAS-II ------------------------------------------------------------------------- -*) -on run - set python to "{:s}" - set appwithpath to "{:s}" - set env to "{:s}" - TestFilePresent(appwithpath) - TestFilePresent(python) - tell application "Terminal" - do script env & python & " " & appwithpath & "; exit" - end tell -end run - -(* ------------------------------------------------------------------------------------------------ -this section handles starting with files dragged into the AppleScript - o it goes through the list of file(s) dragged in - o then it converts the colon-delimited macintosh file location to a POSIX filename - o for every non-directory file dragged into the icon, it starts GSAS-II, passing the file name ------------------------------------------------------------------------------------------------- -*) -on open names - set python to "{:s}" - set appwithpath to "{:s}" - set env to "{:s}" - - TestFilePresent(appwithpath) - repeat with filename in names - set filestr to (filename as string) - if filestr ends with ":" then - (* should not happen, skip directories *) - else - (* if this is an input file, open it *) - set filename to the quoted form of the POSIX path of filename - tell application "Terminal" - activate - do script env & python & " " & appwithpath & " " & filename & "; exit" - end tell - end if - end repeat -end open -''' - # create a link named GSAS-II.py to the script - newScript = os.path.join(path2GSAS,'GSAS-II.py') - if os.path.exists(newScript): # cleanup - print("\nRemoving sym link",newScript) - os.remove(newScript) - os.symlink(os.path.split(G2script)[1],newScript) - G2script=newScript - - # find Python used to run GSAS-II and set a new to use to call it - # inside the app that will be created - pythonExe = os.path.realpath(sys.executable) - newpython = os.path.join(appPath,"Contents","MacOS",projectname) - - # create an app using this python and if that fails to run wx, look for a - # pythonw and try that with wx - for i in 1,2,3: - if os.path.exists(appPath): # cleanup - print("\nRemoving old "+projectname+" app ("+str(appPath)+")") - shutil.rmtree(appPath) - - shell = os.path.join("/tmp/","appscrpt.script") - f = open(shell, "w") - f.write(AppleScript.format(newpython,G2script,'',newpython,G2script,'')) - f.close() - - try: - subprocess.check_output(["osacompile","-o",appPath,shell],stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as msg: - print('''Error compiling AppleScript. - Report the next message along with details about your Mac to toby@anl.gov''') - print(msg.output) - sys.exit() - # create a link to the python inside the app, if named to match the project - if pythonExe != newpython: os.symlink(pythonExe,newpython) - - # test if newpython can run wx - def RunPython(image,cmd): - 'Run a command in a python image' - try: - err=None - p = subprocess.Popen([image,'-c',cmd],stdout=subprocess.PIPE,stderr=subprocess.PIPE) - out = p.stdout.read() - err = p.stderr.read() - p.communicate() - return out,err - except Exception(err): - return '','Exception = '+err - - testout,errout = RunPython(newpython,'import numpy; import wx; wx.App(); print("-"+"OK-")') - if isinstance(testout,bytes): testout = testout.decode() - if "-OK-" in testout: - print('wxpython app ran',testout) - break - elif i == 1: - print('Run of wx in',pythonExe,'failed, looking for pythonw') - pythonExe = os.path.join(os.path.split(sys.executable)[0],'pythonw') - if not os.path.exists(pythonExe): - print('Warning no pythonw found with ',sys.executable, - '\ncontinuing, hoping for the best') - elif i == 2: - print('Warning could not run wx with',pythonExe, - 'will try with that external to app') - newpython = pythonExe - else: - print('Warning still could not run wx with',pythonExe, - '\ncontinuing, hoping for the best') - - # change the icon - oldicon = os.path.join(appPath,"Contents","Resources","droplet.icns") - #if os.path.exists(iconfile) and os.path.exists(oldicon): - if os.path.exists(iconfile): - shutil.copyfile(iconfile,oldicon) - - # Edit the app plist file to restrict the type of files that can be dropped - if hasattr(plistlib,'load'): - fp = open(os.path.join(appPath,"Contents",'Info.plist'),'rb') - d = plistlib.load(fp) - fp.close() - else: - d = plistlib.readPlist(os.path.join(appPath,"Contents",'Info.plist')) - d['CFBundleDocumentTypes'] = [{ - 'CFBundleTypeExtensions': ['gpx'], - 'CFBundleTypeName': 'GSAS-II project', - 'CFBundleTypeRole': 'Editor'}] - - if hasattr(plistlib,'dump'): - fp = open(os.path.join(appPath,"Contents",'Info.plist'),'wb') - plistlib.dump(d,fp) - fp.close() - else: - plistlib.writePlist(d,os.path.join(appPath,"Contents",'Info.plist')) - - # Big Sur: open & save the file in the editor to set authorization levels - osascript = ''' - tell application "Script Editor" - set MyName to open "{}" - save MyName - (* close MyName *) - (* quit *) - end tell -'''.format(appPath) - # detect MacOS 11 (11.0 == 10.16!) - if platform.mac_ver()[0].split('.')[0] == '11' or platform.mac_ver()[0][:5] == '10.16': - print("\nFor Big Sur and later, save the app in Script Editor before using it\n") - subprocess.Popen(["osascript","-e",osascript]) - print("\nCreated "+projectname+" app ("+str(appPath)+ - ").\nViewing app in Finder so you can drag it to the dock if, you wish.") - subprocess.call(["open","-R",appPath]) + sys.exit() diff --git a/GSASII/install/makeVarTbl.py b/GSASII/install/makeVarTbl.py index 018245944..af9220d08 100644 --- a/GSASII/install/makeVarTbl.py +++ b/GSASII/install/makeVarTbl.py @@ -127,4 +127,15 @@ def main(): if __name__ == '__main__': print('Running makeVarTbl.py') + import importlib.util + try: + pkginfo = importlib.util.find_spec('GSASII.GSASIIobj') + except ModuleNotFoundError: + pkginfo = None + if pkginfo is None: # hack path if GSASII not installed into Python + import os + import sys + parent = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + print(f'GSAS-II not installed; Hacking sys.path to {parent}') + sys.path.insert(0,parent) main() diff --git a/GSASII/install/save-versions.py b/GSASII/install/save-versions.py new file mode 100644 index 000000000..a312d4c94 --- /dev/null +++ b/GSASII/install/save-versions.py @@ -0,0 +1,86 @@ +# record tag number and git hash into a saved_version.py file. +# +import os +import sys +import datetime as dt +import git + +# get location of the GSAS-II files +# assumed to be the parent of location of this file +path2GSAS2 = os.path.dirname(os.path.dirname( + os.path.abspath(os.path.expanduser(__file__)))) +# and the repo is in the parent of that +path2repo = os.path.dirname(path2GSAS2) + +if __name__ == '__main__': + + g2repo = git.Repo(path2repo) + # for now allow this to be used on the develop branch + #if g2repo.active_branch.name != 'master': + # print(f'Not on master branch {commit0[:6]!r}') + # sys.exit() + + # create a file with GSAS-II version information + try: + g2repo = git.Repo(path2repo) + except: + print('Launch of gitpython for version file failed'+ + f' with path {path2repo}') + sys.exit() + commit = g2repo.head.commit + commitm1 = '?' + tagsm1 = None + #ctim = commit.committed_datetime.strftime('%d-%b-%Y %H:%M') + now = dt.datetime.now().replace( + tzinfo=commit.committed_datetime.tzinfo) + commit0 = commit.hexsha + + #tags0 = g2repo.git.tag('--points-at',commit).split('\n') + tags0 = [i for i in g2repo.git.tag('--points-at',commit).split('\n') if i.isdecimal()] + history = list(g2repo.iter_commits('HEAD')) + for i in history[1:]: + tags = g2repo.git.tag('--points-at',i) + if not tags: continue + commitm1 = i.hexsha + #tagsm1 = tags.split('\n') + tagsm1 = [i for i in tags.split('\n') if i.isdecimal()] + if not tagsm1: continue + break + pyfile = os.path.join(path2GSAS2,'saved_version.py') + try: + fp = open(pyfile,'w') + except: + print(f'Creation of git version file {pyfile} failed') + sys.exit() + fp.write('# -*- coding: utf-8 -*-\n') + fp.write(f'# {os.path.split(pyfile)[1]} - GSAS-II version info from git\n') + fp.write(f'# Do not edit, generated by {" ".join(sys.argv)!r} script\n') + fp.write(f'# Created {now}\n\n') + fp.write(f'git_version = {commit0!r}\n') + if tags0: + fp.write(f'git_tags = {tags0}\n') + else: + fp.write('git_tags = []\n') + fp.write(f'git_prevtaggedversion = {commitm1!r}\n') + if tagsm1: + fp.write(f'git_prevtags = {tagsm1}\n') + else: + fp.write(f'git_prevtags = []\n') + # get the latest version number + releases = [i for i in g2repo.tags if '.' in i.name and i.name.startswith('v')] + if releases: + majors = [i.name.split('.')[0][1:] for i in releases] + major = max([int(i) for i in majors if i.isdecimal()]) + minors = [i.name.split('.')[1] for i in releases if i.name.startswith(f'v{major}.')] + minor = max([int(i) for i in minors if i.isdecimal()]) + minis = [i.name.split('.',2)[2] for i in releases if i.name.startswith(f'v{major}.{minor}')] + # mini can be integer, float or even have letters (5.2.1.1rc1) + # for now, ignore anything with letters or decimals + mini = max([int(i) for i in minis if i.isdecimal()]) + versiontag = f'v{major}.{minor}.{mini}' + else: + versiontag = '?' + fp.write(f'git_versiontag = {versiontag!r}\n') + # + fp.close() + print(f'Created git version file {pyfile} at {now} for {commit0[:6]!r}') diff --git a/GSASII/k_vector_search.py b/GSASII/k_vector_search.py index 024c08086..adb0765bb 100644 --- a/GSASII/k_vector_search.py +++ b/GSASII/k_vector_search.py @@ -27,18 +27,34 @@ # acknowledged for their useful comments. # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # -import numpy as np import sys -from scipy.optimize import linear_sum_assignment +import time import math +import numpy as np +from scipy.optimize import linear_sum_assignment +from . import GSASIIfiles as G2fil +from . import GSASIIpath +GSASIIpath.SetBinaryPath() + +gen_option_avail = True try: import seekpath - from kvec_general import parallel_proc - gen_option_avail = True except ModuleNotFoundError: + G2fil.NeededPackage({'magnetic k-vector search':['seekpath']}) + print('k_vector_search: seekpath could not be imported') gen_option_avail = False -import time +try: + if GSASIIpath.binaryPath: + import kvec_general + else: + from . import kvec_general +except ImportError: + print('binary load error: kvec_general not found') + gen_option_avail = False +except ModuleNotFoundError: + print('k_vector_search: kvec_general could not be imported') + gen_option_avail = False def unique_id_gen(string_list: list) -> list: """Generate unique IDs for strings included in the string list and the same @@ -622,7 +638,7 @@ def kOptFinder(self) -> list: points = np.array(np.meshgrid(a_array, b_array, c_array)) points = points.T.reshape(-1, 3) - results = parallel_proc( + results = kvec_general.parallel_proc( points, self.nucPeaks, self.superPeaks, diff --git a/GSASII/meson.build b/GSASII/meson.build new file mode 100644 index 000000000..04bde1249 --- /dev/null +++ b/GSASII/meson.build @@ -0,0 +1,77 @@ +py.install_sources([ + '__init__.py', + '__main__.py', + 'Absorb.py', + 'ElementTable.py', + 'FormFactors.py', + 'G2compare.py', + 'G2shapes.py', + 'GSASIIGUI.py', + 'GSASIIElem.py', + 'GSASIIElemGUI.py', + # 'GSASIIIO.py', + 'GSASIImiscGUI.py', + 'GSASIIIntPDFtool.py', + 'GSASIIconstrGUI.py', + 'GSASIIctrlGUI.py', + 'GSASIIdata.py', + 'GSASIIdataGUI.py', + 'GSASIIddataGUI.py', + 'GSASIIexprGUI.py', + 'GSASIIfiles.py', + 'GSASIIfpaGUI.py', + 'GSASIIimage.py', + 'GSASIIimgGUI.py', + 'GSASIIindex.py', + 'GSASIIlattice.py', + # 'GSASIIlog.py', + 'GSASIImapvars.py', + 'GSASIImath.py', + 'GSASIImpsubs.py', + 'GSASIIobj.py', + 'GSASIIpath.py', + 'GSASIIphsGUI.py', + 'GSASIIplot.py', + 'GSASIIpwd.py', + 'GSASIIpwdGUI.py', + 'GSASIIpwdplot.py', + 'GSASIIrestrGUI.py', + 'GSASIIsasd.py', + 'GSASIIscriptable.py', + 'GSASIIseqGUI.py', + 'GSASIIspc.py', + 'GSASIIstrIO.py', + 'GSASIIstrMain.py', + 'GSASIIstrMath.py', + 'GSASIItestplot.py', + 'ISODISTORT.py', + 'ImageCalibrants.py', + 'PlotXNFF.py', + 'ReadMarCCDFrame.py', + 'SUBGROUPS.py', + 'Substances.py', + 'atmdata.py', + 'config_example.py', + 'defaultIparms.py', + 'dmp.py', + 'fprime.py', + 'git_verinfo.py', + 'gltext.py', + 'k_vector_search.py', + 'nistlat.py', + 'scanCCD.py', + 'testDeriv.py', + 'testSSymbols.py', + 'testSytSym.py', + 'testXNFF.py', + 'tutorialIndex.py', +], + pure: false, # Will be installed next to binaries + subdir: 'GSASII' # Folder relative to site-packages to install to +) + +subdir('NIST_profile') +subdir('imports') +subdir('exports') +subdir('icons') +subdir('inputs') diff --git a/GSASII/nistlat.py b/GSASII/nistlat.py index 045738892..b3e1f5645 100644 --- a/GSASII/nistlat.py +++ b/GSASII/nistlat.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- ''' -This implements an interface to the NIST*LATTICE code using -the Spring 1991 program version. NIST*LATTICE, "A Program to Analyze +This implements an interface to the NIST*LATTICE code using +the Spring 1991 program version. NIST*LATTICE, "A Program to Analyze Lattice Relationships" was created by Vicky Lynn Karen and Alan D. Mighell -(National Institute of Standards and Technology, Materials Science and +(National Institute of Standards and Technology, Materials Science and Engineering Laboratory, Gaithersburg, Maryland 20899.) Minor code modifications made to provide more significant digits for -cell reduction matrix terms. +cell reduction matrix terms. Please cite V. L. Karen and A. D. Mighell, NIST Technical Note 1290 (1991), https://nvlpubs.nist.gov/nistpubs/Legacy/TN/nbstechnicalnote1290.pdf; and V. L. Karen & A. D. Mighell, U.S. Patent 5,235,523, -https://patents.google.com/patent/US5235523A/en?oq=5235523 if this module -is used. +https://patents.google.com/patent/US5235523A/en?oq=5235523 if this module +is used. ''' @@ -21,29 +21,32 @@ import subprocess import re import numpy as np -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIIlattice as G2lat +from . import GSASIIlattice as G2lat -nistlattice = os.path.join(GSASIIpath.binaryPath,"LATTIC") -convcell = os.path.join(GSASIIpath.binaryPath,"convcell") -is_exe = lambda fpath: os.path.isfile(fpath) and os.access(fpath, os.X_OK) +if GSASIIpath.binaryPath: + nistlattice = os.path.join(GSASIIpath.binaryPath,"LATTIC") + convcell = os.path.join(GSASIIpath.binaryPath,"convcell") +else: # binaries should be in path + nistlattice = "LATTIC" + convcell = "convcell" debug = False #debug = True centerLbl = {'P':'Primitive', 'A':'A-centered', 'B':'B-centered', 'C':'C-centered', 'F':'Face-centered', 'I':' Body-centered', 'R':'Rhombohedral', 'H':'Rhombohedral'} - + def showCell(cell,center='P',setting=' ',*ignored): '''show unit cell input or output nicely formatted. :param list cell: six lattice constants as float values; a 7th volume value is ignored if present. :param str center: cell centering code; one of P/A/B/C/F/I/R - Note that 'R' is used for rhombohedral lattices in either + Note that 'R' is used for rhombohedral lattices in either rhombohedral (primitive) or hexagonal cells. - :param str setting: is ' ' except for rhombohedral symmetry where + :param str setting: is ' ' except for rhombohedral symmetry where it will be R or H for the cell type. :returns: a formatted string ''' @@ -59,7 +62,7 @@ def uniqCells(cellList): '''remove duplicated cells from a cell output list from :func:`ReduceCell` :param list cellList: A list of reduced cells where each entry represents a - reduced cell as (_,cell,_,_,center,...) where cell has six lattice + reduced cell as (_,cell,_,_,center,...) where cell has six lattice constants and center is the cell centering code (P/A/B/C/F/I/R). :returns: a list as above, but where each unique cell is listed only once ''' @@ -74,51 +77,51 @@ def uniqCells(cellList): def _emulateLP(line,fp): '''Emulate an antique 132 column line printer, where the first column - that is printed is used for printer control. '1' starts a new page + that is printed is used for printer control. '1' starts a new page and '0' double-spaces. Not implemented is '+' which overprints. - If the file pointer is not None, the line is copied to the - file, removing the first column but adding a bit of extra formatting - to separate pages or add a blank line where needed. + If the file pointer is not None, the line is copied to the + file, removing the first column but adding a bit of extra formatting + to separate pages or add a blank line where needed. :param str line: string to be printed :param file fp: file pointer object ''' if not fp: return if line[0] == '1': - fp.write('\n'+130*'='+'\n'+line[1:]) + fp.write('\n'+130*'='+'\n'+line[1:]) elif line[0] == '0': - fp.write('\n'+line[1:]) + fp.write('\n'+line[1:]) elif len(line) == 1: - fp.write('\n') + fp.write('\n') else: - fp.write(line[1:]) - + fp.write(line[1:]) + def ReduceCell(center, cellin, mode=0, deltaV=0, output=None): '''Compute reduced cell(s) with NIST*LATTICE - + :param str center: cell centering code; one of P/A/B/C/F/I/R - Note that 'R' is used for rhombohedral lattices in either + Note that 'R' is used for rhombohedral lattices in either hexagonal or rhombohedral (primitive) cells :param list cellin: six lattice constants as float values - :param int mode: + :param int mode: - * 0: reduction, - * 1: generate supercells, + * 0: reduction, + * 1: generate supercells, * 2: generate subcells * 3: generate sub- and supercells - :param int deltaV: volume ratios for sub/supercells if mode != 0 as - ratio of original cell to smallest subcell or largest supercell + :param int deltaV: volume ratios for sub/supercells if mode != 0 as + ratio of original cell to smallest subcell or largest supercell to original cell. Ignored if mode=0. Otherwise should be 2, 3, 4 or 5 :param str output: name of file to write the NIST*LATTICE output. - Default is None, which does not produce a file. + Default is None, which does not produce a file. :returns: a dict with two items, 'input' and 'output'. The value for 'input' is the input cell as (cell,center,setting). The value for 'output' is a list of reduced cells of form - (d,cell,vol,mat,center,setting). In these: + (d,cell,vol,mat,center,setting). In these: * cell: a list with the six cell dimensions; - * center: is as above (always 'P' on output); + * center: is as above (always 'P' on output); * setting: is ' ' except for rhombohedral symmetry where it may be R or H for the cell type; * d: is the volume ratio for new cell over input cell; * vol: is volume of output cell @@ -126,9 +129,9 @@ def ReduceCell(center, cellin, mode=0, deltaV=0, output=None): ''' # setting is non-blank for rhombohedral lattices (center=R) only. - # setting = R for rhob axes + # setting = R for rhob axes # setting = H for hex. axes - setting = " " + setting = " " (a,b,c,alpha,beta,gamma) = cellin celldict = {} if center == "R" and alpha == 90 and beta == 90 and gamma == 120: @@ -142,35 +145,34 @@ def ReduceCell(center, cellin, mode=0, deltaV=0, output=None): if os.path.exists('NIST10'): # cleanup print("Removing old NIST10 file") os.remove('NIST10') - p = subprocess.Popen([nistlattice], + # import shutil + # print(shutil.which(nistlattice)) + with subprocess.Popen([nistlattice],encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - p.stdin.write(bytearray(inp,'utf8')) - p.stdin.close() - # read output and parse - err = p.stderr.read() + stderr=subprocess.PIPE) as p: + o,err = p.communicate(input=inp) + + cellout = o.split('\n') celldict['input'] = (cellin,center,setting) celldict['output'] = [] d = 1 line = '?' - linenum = 0 fp = None if output: fp = open(output,'w') try: - for b in p.stdout.readlines(): + for linenum,line in enumerate(cellout,1): linenum += 1 - line = b.decode() _emulateLP(line,fp) pat = r"T 2= (.*)/ (.*)/ (.*)" # transform matrix s = re.split(pat,line) - if len(s) > 1: + if len(s) > 1: mat = [[float(j) for j in re.split(r" *([\d\.-]*) *",i,maxsplit=3)[1::2]] for i in s[1:-1]] - + pat = r"Delta =(.*)" # Volume ratio (if mode >0) s = re.split(pat,line) - if len(s) > 1: + if len(s) > 1: d = float(eval(s[1])) pat = r"CELL 2=(.*)V2=(.*)" # cell lattice and volume @@ -178,59 +180,57 @@ def ReduceCell(center, cellin, mode=0, deltaV=0, output=None): if len(s) > 1: lat = [float(i) for i in re.split(r" *([\d\.-]*) *",s[1],maxsplit=6)[1::2]] vol = float(re.split(r" *([\d\.-]*) *",s[2],maxsplit=1)[1]) - celldict['output'].append((d,lat,vol,mat,'P',' ')) # note reduced cells are all primitive + celldict['output'].append((d,lat,vol,mat,'P',' ')) # note reduced cells are all primitive except: - print('ReduceCell parse error at line ',linenum) - print(line) + print(f'ReduceCell parse error at line {linenum}\n{line}') return celldict - finally: - p.terminate() if len(celldict['output']) == 0 or len(err) > 0: - print('Error:' ,err.decode()) + print(f'ReduceCell Error = {err}') + if output: fp.close() return celldict def ConvCell(redcell): '''Converts a reduced cell to a conventional cell - :param list redcell: unit cell parameters as 3 cell lengths + :param list redcell: unit cell parameters as 3 cell lengths and 3 angles (in degrees) :returns: tuple (cell,center,setting,mat) where: - + * cell: has the six cell dimensions for the conventional cell; * center: is P/A/B/C/F/I/R; - * setting: is ' ' except for rhombohedral symmetry (center=R), where + * setting: is ' ' except for rhombohedral symmetry (center=R), where it will always be H (for hexagonal cell choice); - * mat: is the matrix that gives the conventional cell when the reduced + * mat: is the matrix that gives the conventional cell when the reduced cell is multiplied by mat. ''' inp = "{:10.5f}{:10.5f}{:10.5f}{:10.4f}{:10.4f}{:10.4f}".format(*redcell) if os.path.exists('NIST10'): # cleanup print("Removing old NIST10 file") os.remove('NIST10') - p = subprocess.Popen([convcell], + with subprocess.Popen([convcell],encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - p.stdin.write(bytearray(inp,'utf8')) - p.stdin.close() - # read output and parse - err = p.stderr.read() + stderr=subprocess.PIPE) as p: + o,err = p.communicate(input=inp) + + out = o.split('\n') if debug and err: - print('ConvCell err=',err) + print(f'ConvCell err = {err}') line = '?' linenum = 0 cell = [] center = ' ' setting = ' ' + mat = '' try: - for b in p.stdout.readlines(): - line = b.decode() - if '**WARNING**' in line: + while out: + line=out.pop(0) + if not line.strip(): continue + linenum += 1 + if '**WARNING**' in line: print('Note: Warning generated in conversion of reduced\n cell', redcell,'\n (Probably OK to ignore)') continue - if not line.strip(): continue - linenum += 1 if linenum == 1: cell = [float(i) for i in line.split()[:6]] center = line.split()[-1] @@ -240,49 +240,44 @@ def ConvCell(redcell): if linenum == 2: mat = np.array([float(i) for i in line.split()]).reshape(3,3) except: - print('ConvCell parse error at line ',linenum) - print(line) - if debug: - print("\nRemaining lines:") - for b1 in p.stdout.readlines(): - print(b1.decode()) + print(f'ConvCell parse error at line {linenum}:\n{line}') + if debug and out: + print("\nUnprocessed convcell output:") + for line in out: print(line) return None - #return cell - finally: - p.terminate() if len(err) > 0: - print('Error:' ,err.decode()) + print(f'ConvCell Error: {err}') return (cell,center,setting,mat) def CompareCell(cell1, center1, cell2, center2, tolerance=3*[0.2]+3*[1], mode='I', vrange=8, output=None): - '''Search for matrices that relate two unit cells - + '''Search for matrices that relate two unit cells + :param list cell1: six lattice constants as float values for 1st cell :param str center1: cell centering code for 1st cell; one of P/A/B/C/F/I/R - Note that 'R' is used for rhombohedral lattices in either + Note that 'R' is used for rhombohedral lattices in either hexagonal or rhombohedral (primitive) cells :param list cell2: six lattice constants as float values for 2nd cell :param str center2: cell centering code for 2nd cell (see center1) - :param list tolerance: comparison tolerances for a, b, c, alpha, beta + :param list tolerance: comparison tolerances for a, b, c, alpha, beta & gamma (defaults to [0.2,0.2,0.2,1.,1.,1.] :param str mode: search mode, which should be either 'I' or 'F' 'I' provides searching with integral matrices or 'F' provides searching with integral and fractional matrices - :param int vrange: maximum matrix term range. - Must be 1 <= vrange <= 10 for mode='F' or - Must be 1 <= vrange <= 40 for mode='I' + :param int vrange: maximum matrix term range. + Must be 1 <= vrange <= 10 for mode='F' or + Must be 1 <= vrange <= 40 for mode='I' :param str output: name of file to write the NIST*LATTICE output. - Default is None, which does not produce a file. + Default is None, which does not produce a file. - :returns: A list of matrices that match cell1 to cell2 where + :returns: A list of matrices that match cell1 to cell2 where each entry contains (det, im, m, tol, one2two, two2one) where: * det is the determinant, giving the volume ratio between cells * im relates the reduced cell for cell1 to the reduced cell for cell2 * m relates the reduced cell for cell2 to the reduced cell for cell1 - * tol shows the quality of agreement, as six differences between the + * tol shows the quality of agreement, as six differences between the two reduced cells * one2two: a numpy matrix that transforms cell1 to cell2 * two2one: a numpy matrix that transforms cell2 to cell1 @@ -305,19 +300,14 @@ def CompareCell(cell1, center1, cell2, center2, tolerance=3*[0.2]+3*[1], if os.path.exists('NIST10'): # cleanup print("Removing old NIST10 file") os.remove('NIST10') - p = subprocess.Popen([nistlattice], + with subprocess.Popen([nistlattice],encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - p.stdin.write(bytearray(inp,'utf8')) - p.stdin.close() - err = p.stderr.read() - line = '?' + stderr=subprocess.PIPE) as p: + o,err = p.communicate(input=inp) + + lines = o.split('\n') fp = None - if output: fp = open(output,'w') - # read output and parse - lines = [b.decode() for b in p.stdout.readlines()] - p.terminate() if fp: for line in lines: _emulateLP(line,fp) fp.close() @@ -344,18 +334,18 @@ def CompareCell(cell1, center1, cell2, center2, tolerance=3*[0.2]+3*[1], im[0] = sl[3:6] tol[0:3] = sl[6:9] det = sl[9] - + lnum += 1 sl = [float(i) for i in lines[lnum].split()] m[1] = sl[0:3] im[1] = sl[3:6] tol[3:] = sl[6:9] - + lnum += 1 sl = [float(i) for i in lines[lnum].split()] m[2] = sl[0:3] im[2] = sl[3:6] - + xmatI = np.dot(np.dot(np.linalg.inv(rcVmat[0][2]),m),rcVmat[1][2]) xmat = np.dot(np.dot(np.linalg.inv(rcVmat[1][2]),im),rcVmat[0][2]) if reverseCells: @@ -370,47 +360,47 @@ def CompareCell(cell1, center1, cell2, center2, tolerance=3*[0.2]+3*[1], lnum += 1 if len(err) > 0: - print('Execution error:' ,err.decode()) + print(f'CompareCell error: {err}') return xforms def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, deltaV=2, output=None): '''Search for a higher symmetry lattice related to an input unit cell, - and optionally to the supercells and/or subcells with a specified - volume ratio to the input cell. + and optionally to the supercells and/or subcells with a specified + volume ratio to the input cell. :param list cellin: six lattice constants as float values :param str center: cell centering code; one of P/A/B/C/F/I/R - Note that 'R' is used for rhombohedral lattices in either + Note that 'R' is used for rhombohedral lattices in either hexagonal or rhombohedral (primitive) cells - :param list tolerance: comparison tolerances for a, b, c, alpha, beta + :param list tolerance: comparison tolerances for a, b, c, alpha, beta & gamma (defaults to [0.2,0.2,0.2,1.,1.,1.] - :param int mode: + :param int mode: * 0: use only input cell, - * 1: generate supercells, + * 1: generate supercells, * 2: generate subcells * 3: generate sub- and supercells - :param int deltaV: volume ratios for sub/supercells if mode != 0 as - ratio of original cell to smallest subcell or largest supercell + :param int deltaV: volume ratios for sub/supercells if mode != 0 as + ratio of original cell to smallest subcell or largest supercell to original cell. Ignored if mode=0. Otherwise should be 2, 3, 4 or 5 :param str output: name of file to write the NIST*LATTICE output. Default is None, which does not produce a file. - :returns: a list of processed cells (only one entry in list when mode=0) - where for each cell the the following items are included: + :returns: a list of processed cells (only one entry in list when mode=0) + where for each cell the the following items are included: - * conventional input cell; - * reduced input cell; - * symmetry-generated conventional cell; - * symmetry-generated reduced cell; + * conventional input cell; + * reduced input cell; + * symmetry-generated conventional cell; + * symmetry-generated reduced cell; * matrix to convert sym-generated output cell to input conventional cell ''' # setting is non-blank for rhombohedral lattices (center=R) only. - # setting = R for rhob axes + # setting = R for rhob axes # setting = H for hex. axes - setting = " " + setting = " " (a,b,c,alpha,beta,gamma) = cellin celldict = {} if center == "R" and alpha == 90 and beta == 90 and gamma == 120: @@ -426,18 +416,14 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, if os.path.exists('NIST10'): # cleanup print("Removing old NIST10 file") os.remove('NIST10') - p = subprocess.Popen([nistlattice], + with subprocess.Popen([nistlattice],encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - p.stdin.write(bytearray(inp,'utf8')) - p.stdin.close() - # read output and parse - err = p.stderr.read() - + stderr=subprocess.PIPE) as p: + o,err = p.communicate(input=inp) + + lines = o.split('\n') d = 1 - lines = [b.decode() for b in p.stdout.readlines()] - p.terminate() fp = None if output: fp = open(output,'w') if fp: @@ -454,12 +440,12 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, lnum += 1 pat = r"T 2= (.*)/ (.*)/ (.*)" # transform matrix s = re.split(pat,line) - if len(s) > 1: + if len(s) > 1: mat = [[float(j) for j in re.split(r" *([\d\.-]*) *",i,maxsplit=3)[1::2]] for i in s[1:-1]] pat = r"Delta =(.*)" # Volume ratio (if mode >0) s = re.split(pat,line) - if len(s) > 1: + if len(s) > 1: d = float(eval(s[1])) pat = r"CELL 2=(.*)V2=(.*)" # cell lattice and volume @@ -471,7 +457,7 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, if " ** Symmetry" in line: break #====================================================================== - # for each input-generated cell, get sets of generated H-matrices + # for each input-generated cell, get sets of generated H-matrices for icell,cellstuff in enumerate(startCellList): while lnum < len(lines): line = lines[lnum] @@ -509,13 +495,13 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, im[0] = sl[3:6] tol[0:3] = sl[6:9] #det = sl[9] - + lnum += 1 sl = [float(i) for i in lines[lnum].split()] m[1] = sl[0:3] im[1] = sl[3:6] tol[3:] = sl[6:9] - + lnum += 1 sl = [float(i) for i in lines[lnum].split()] m[2] = sl[0:3] @@ -523,8 +509,8 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, xformSum += G2lat.TransformCell(lat,m) xformCount += 1 lnum += 1 - - # got to end of output for current cell, process this input + + # got to end of output for current cell, process this input if 50*'-' in line: if xformCount: inRedCell = (startCellList[icell][2], 'P', ' ') @@ -551,20 +537,18 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, cnvCell[:3], # symmetry-generated conventional cell redCell, # symmetry-generated reduced cell cnv2origMat, # matrix to convert sym-generated output cell to input conventional cell - )) + )) break # go on to next input-generated cell - + except Exception as msg: print('CellSymSearch parse error at line ',lnum,'\nNote error:',msg) print(line) return celldict - finally: - p.terminate() if len(symCellList) == 0 or len(err) > 0: - print('Error:' ,err.decode()) + print(f'CellSymSearch error: {err}') return symCellList - + if __name__ == '__main__': # test code cell = [14.259, 22.539, 8.741, 90., 114.1, 90.] @@ -573,7 +557,7 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, for i in CellSymSearch(cell, center, output='/tmp/cellsym.txt'): print('\ninp-conv, inp-red, out-conv, out-red, out(conv)->inp(conv)') for j in i: print(j) - + print('\n\nCellSymSearch output') for i in CellSymSearch(cell, center, output='/tmp/cellsym.txt', mode=3): print('\ninp-conv, inp-red, out-conv, out-red, out(conv)->inp(conv)') @@ -605,7 +589,7 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, print('cell2 ',showCell(cell1,center1,' ')) print('cell1-->cell2',G2lat.TransformCell(cell2,out[0][4])) - + center2 = 'I' print('\n\ncomparing ',showCell(cell1,center1,' '),showCell(cell2,center2,' ')) out = CompareCell(cell1, center1, cell2, center2, tolerance) @@ -620,40 +604,40 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, import sys; sys.exit() - + cellin = [5.,5.,5.,85.,85.,85.,] cellList = ConvCell(cellin) - if cellList is None: + if cellList is None: print('ConvCell failed',cellin) sys.exit() print('Input reduced cell',showCell(cellin,'P',' '),'\nout',showCell(*cellList)) mat = cellList[-1] print('test with\n',mat) print(G2lat.TransformCell(cellin,mat)) - + print('\ntest from [5,6,7,90,105,90] C-Centered') # ==> [5,6,7,90,105,90] C-Centered cellin = [3.9051,3.9051,7,99.537,99.537,100.389] cellList = ConvCell(cellin) - if cellList is None: + if cellList is None: print('ConvCell failed',cellin) sys.exit() print('Input reduced cell',showCell(cellin,'P',' '),'\nout',showCell(*cellList)) mat = cellList[-1] print('test with\n',mat) print(G2lat.TransformCell(cellin,mat)) - + print('\nHexagonal test (no change)') cellin = [5.,5.,7.,90.,90.,120.,] cellList = ConvCell(cellin) - if cellList is None: + if cellList is None: print('ConvCell failed',cellin) sys.exit() print('Input reduced cell',showCell(cellin,'P',' '),'\nout',showCell(*cellList)) mat = cellList[-1] print('test with\n',mat) print(G2lat.TransformCell(cellin,mat)) - - + + # cell = ReduceCell('F',[3,3,5,90,90,90],1,2) # print('supercell',showCell(*cell['input'])) # for i in cell['output']: print(i) @@ -664,13 +648,13 @@ def CellSymSearch(cellin, center, tolerance=3*[0.2]+3*[1], mode=0, cell = ReduceCell('F',[3,3,5,90,90,90],3,2) print('\nunique from both mode',showCell(*cell['input'])) for i in uniqCells(cell['output']): print(i) - + cell = ReduceCell('I',[3,3,5,90,90,90]) print('\ndefault',showCell(*cell['input'])) for i in cell['output']: print(i) print('invert back',ConvCell(cell['output'][0][1])) - - + + print('\nF-tetragonal is not standard setting') cell = ReduceCell('F',[3,3,5,90,90,90]) print('default',showCell(*cell['input'])) diff --git a/GSASII/pathHacking.py b/GSASII/pathHacking.py new file mode 100644 index 000000000..889f7e393 --- /dev/null +++ b/GSASII/pathHacking.py @@ -0,0 +1,89 @@ +# modules for use where GSAS-II binaries are not co-located with +# the main GSAS-II files and the path is modified (I can hear Tom +# saying "Shame, Shame!"). + +import glob +import os +import sys +import numpy as np +from . import GSASIIpath + +def _path_discovery(printInfo=False): + def appendIfExists(searchpathlist,loc,subdir): + newpath = os.path.join(loc,subdir) + if os.path.exists(newpath): + if newpath in searchpathlist: return False + searchpathlist.append(newpath) + inpver = GSASIIpath.intver(np.__version__) + + if GSASIIpath.path2GSAS2 not in sys.path: + sys.path.insert(0,GSASIIpath.path2GSAS2) # make sure current path is used + binpath = None + binprfx = GSASIIpath.GetBinaryPrefix() + # places to look for the GSAS-II binary directory + binseapath = [os.path.abspath(sys.path[0])] # where Python is installed + binseapath += [os.path.abspath(os.path.dirname(__file__))] # directory where this file is found + binseapath += [os.path.dirname(binseapath[-1])] # parent of above directory + binseapath += [os.path.expanduser('~/.GSASII')] # directory in user's home + searched = [] + for loc in binseapath: + if loc in searched: continue + if not os.path.exists(loc): continue + searched.append(loc) + # Look at bin directory (created by a local compile) before looking for standard dist files + searchpathlist = [] + appendIfExists(searchpathlist,loc,'bin') + appendIfExists(searchpathlist,loc,'bindist') + appendIfExists(searchpathlist,loc,'GSASII-bin') + # also look for directories named by platform etc in loc/AllBinaries or loc + versions = {} + namedpath = glob.glob(os.path.join(loc,'AllBinaries',binprfx+'*')) + namedpath += glob.glob(os.path.join(loc,'GSASII-bin',binprfx+'*')) + for d in namedpath: + d = os.path.realpath(d) + v = GSASIIpath.intver(d.rstrip('/').split('_')[-1].lstrip('n')) + versions[v] = d + vmin = None + vmax = None + # try to order the search in a way that makes sense + for v in sorted(versions.keys()): + if v <= inpver: + vmin = v + elif v > inpver: + vmax = v + break + if vmin in versions and versions[vmin] not in searchpathlist: + searchpathlist.append(versions[vmin]) + if vmax in versions and versions[vmax] not in searchpathlist: + searchpathlist.append(versions[vmax]) + for fpth in searchpathlist: + if not glob.glob(os.path.join(fpth,'pyspg.*')): continue + if GSASIIpath.pathhack_TestSPG(fpth): + binpath = fpth # got one that works, look no farther! + break + else: + continue + break + if binpath: # were GSAS-II binaries found? + GSASIIpath.binaryPath = binpath + GSASIIpath.BinaryPathLoaded = True + else: + print('*** ERROR: Unable to find GSAS-II binaries. Much of GSAS-II cannot function') + if GSASIIpath.GetConfigValue('debug'): + print('Searched directories:') + for i in searched: print(f'\t{i}') + print(f'''for subdirectories named .../bin, .../bindist, .../GSASII-bin, + .../AllBinaries/{binprfx}* and .../GSASII-bin/{binprfx}*''') + return True + + # add the data import and export directory to the search path + if binpath not in sys.path: sys.path.insert(0,binpath) + if printInfo: print(f'GSAS-II binary directory: {binpath}') + + # *** Thanks to work by Tom, imports and exports are now found directly + # *** and the code below is no longer needed. + # *** + #newpath = os.path.join(path2GSAS2,'imports') + #if newpath not in sys.path: sys.path.append(newpath) + #newpath = os.path.join(path2GSAS2,'exports') + #if newpath not in sys.path: sys.path.append(newpath) diff --git a/GSASII/pybaselines/LICENSE.txt b/GSASII/pybaselines/LICENSE.txt deleted file mode 100644 index 41af977d0..000000000 --- a/GSASII/pybaselines/LICENSE.txt +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021-2023, Donald Erb -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/GSASII/pybaselines/LICENSES_bundled.txt b/GSASII/pybaselines/LICENSES_bundled.txt deleted file mode 100644 index 7d396b4c9..000000000 --- a/GSASII/pybaselines/LICENSES_bundled.txt +++ /dev/null @@ -1,198 +0,0 @@ -The pybaselines repository and source distributions contain code adapted from compatibly -licensed sources, which are listed below. - - -Source: ported MATLAB code from https://www.mathworks.com/matlabcentral/fileexchange/27429-background-correction -(last accessed March 18, 2021) -Function: pybaselines.polynomial.penalized_poly -License: 2-clause BSD - -Copyright (c) 2012, Vincent Mazet -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - -Source: https://gist.github.com/agramfort/850437 (last accessed March 25, 2021) -Functions: pybaselines.polynomial._loess_low_memory and pybaselines.polynomial._loess_first_loop -License: 3-clause BSD - -# Authors: Alexandre Gramfort -# -# License: BSD (3-clause) -Copyright (c) 2015, Alexandre Gramfort -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -Source: ported MATLAB code from -https://www.mathworks.com/matlabcentral/fileexchange/49974-beads-baseline-estimation-and-denoising-with-sparsity -(last accessed June 28, 2021) -Function: pybaselines.misc.beads and all related functions -License: 3-clause BSD - -Copyright (c) 2018, Laurent Duval -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution -* Neither the name of IFP Energies nouvelles nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -Source: bandmat (https://github.com/MattShannon/bandmat, last accessed July 10, 2021) -Function: pybaselines.misc._banded_dot_banded -License: 3-clause BSD - -Copyright 2013, 2014, 2015, 2016, 2017, 2018 Matt Shannon - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. - - -Source: sphinx-autoapi (https://github.com/readthedocs/sphinx-autoapi) -Folder: All files within docs/_templates/autoapi were adapted from templates - provided by sphinx-autoapi. -License: MIT - -The MIT License (MIT) -===================== - -Copyright (c) 2015 Read the Docs, Inc - -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 -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -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, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - - -Source: SciPy (https://github.com/scipy/scipy, last accessed November 6, 2021) -File: pybaselines._spline_utils.py -License: 3-clause BSD - -Copyright (c) 2001-2002 Enthought, Inc. 2003-2019, SciPy Developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/GSASII/pybaselines/__init__.py b/GSASII/pybaselines/__init__.py deleted file mode 100644 index 351f1e627..000000000 --- a/GSASII/pybaselines/__init__.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -""" -pybaselines - A library of algorithms for the baseline correction of experimental data. -======================================================================================= - -pybaselines provides different techniques for fitting baselines to experimental data. - -* Polynomial methods (:mod:`pybaselines.polynomial`) - - * poly (Regular Polynomial) - * modpoly (Modified Polynomial) - * imodpoly (Improved Modified Polynomial) - * penalized_poly (Penalized Polynomial) - * loess (Locally Estimated Scatterplot Smoothing) - * quant_reg (Quantile Regression) - * goldindec (Goldindec Method) - -* Whittaker-smoothing-based methods (:mod:`pybaselines.whittaker`) - - * asls (Asymmetric Least Squares) - * iasls (Improved Asymmetric Least Squares) - * airpls (Adaptive Iteratively Reweighted Penalized Least Squares) - * arpls (Asymmetrically Reweighted Penalized Least Squares) - * drpls (Doubly Reweighted Penalized Least Squares) - * iarpls (Improved Asymmetrically Reweighted Penalized Least Squares) - * aspls (Adaptive Smoothness Penalized Least Squares) - * psalsa (Peaked Signal's Asymmetric Least Squares Algorithm) - * derpsalsa (Derivative Peak-Screening Asymmetric Least Squares Algorithm) - -* Morphological methods (:mod:`pybaselines.morphological`) - - * mpls (Morphological Penalized Least Squares) - * mor (Morphological) - * imor (Improved Morphological) - * mormol (Morphological and Mollified Baseline) - * amormol (Averaging Morphological and Mollified Baseline) - * rolling_ball (Rolling Ball Baseline) - * mwmv (Moving Window Minimum Value) - * tophat (Top-hat Transformation) - * mpspline (Morphology-Based Penalized Spline) - * jbcd (Joint Baseline Correction and Denoising) - -* Spline methods (:mod:`pybaselines.spline`) - - * mixture_model (Mixture Model) - * irsqr (Iterative Reweighted Spline Quantile Regression) - * corner_cutting (Corner-Cutting Method) - * pspline_asls (Penalized Spline Version of asls) - * pspline_iasls (Penalized Spline Version of iasls) - * pspline_airpls (Penalized Spline Version of airpls) - * pspline_arpls (Penalized Spline Version of arpls) - * pspline_drpls (Penalized Spline Version of drpls) - * pspline_iarpls (Penalized Spline Version of iarpls) - * pspline_aspls (Penalized Spline Version of aspls) - * pspline_psalsa (Penalized Spline Version of psalsa) - * pspline_derpsalsa (Penalized Spline Version of derpsalsa) - -* Smoothing-based methods (:mod:`pybaselines.smooth`) - - * noise_median (Noise Median method) - * snip (Statistics-sensitive Non-linear Iterative Peak-clipping) - * swima (Small-Window Moving Average) - * ipsa (Iterative Polynomial Smoothing Algorithm) - * ria (Range Independent Algorithm) - -* Baseline/Peak Classification methods (:mod:`pybaselines.classification`) - - * dietrich (Dietrich's Classification Method) - * golotvin (Golotvin's Classification Method) - * std_distribution (Standard Deviation Distribution) - * fastchrom (FastChrom's Baseline Method) - * cwt_br (Continuous Wavelet Transform Baseline Recognition) - * fabc (Fully Automatic Baseline Correction) - -* Optimizers (:mod:`pybaselines.optimizers`) - - * collab_pls (Collaborative Penalized Least Squares) - * optimize_extended_range - * adaptive_minmax (Adaptive MinMax) - -* Miscellaneous methods (:mod:`pybaselines.misc`) - - * interp_pts (Interpolation between points) - * beads (Baseline Estimation And Denoising with Sparsity) - - -@author: Donald Erb -Created on March 5, 2021 - -""" - -__version__ = '1.0.0' - - -# import utils first since it is imported by other modules; likewise, import -# optimizers and api last since they import the other modules -from . import ( - utils, classification, misc, morphological, polynomial, spline, whittaker, smooth, - optimizers, api -) - -from .api import Baseline diff --git a/GSASII/pybaselines/_algorithm_setup.py b/GSASII/pybaselines/_algorithm_setup.py deleted file mode 100644 index e5cd22e30..000000000 --- a/GSASII/pybaselines/_algorithm_setup.py +++ /dev/null @@ -1,855 +0,0 @@ -# -*- coding: utf-8 -*- -"""Setup code for the various algorithm types in pybaselines. - -Created on March 31, 2021 -@author: Donald Erb - -TODO: non-finite values (nan or inf) could be replaced for algorithms that use weighting -by setting their values to arbitrary value (eg. 0) within the output y, set their weights -to 0, and then back-fill after the calculation; something to consider, rather than just -raising an exception when encountering a non-finite value; could also interpolate rather -than just filling back in the nan or inf value. Could accomplish by setting check_finite -to something like 'mask'. - -""" - -from contextlib import contextmanager -from functools import partial, wraps -from inspect import signature -import warnings - -import numpy as np - -from ._banded_utils import PenalizedSystem -from ._spline_utils import PSpline -from ._validation import ( - _check_array, _check_half_window, _check_optional_array, _check_sized_array, _yx_arrays -) -from .utils import ParameterWarning, _inverted_sort, optimize_window, pad_edges - - -class _Algorithm: - """ - A base class for all algorithm types. - - Contains setup methods for all algorithm types to make more complex algorithms - easier to set up. - - Attributes - ---------- - poly_order : int - The last polynomial order used for a polynomial algorithm. Initially is -1, denoting - that no polynomial fitting has been performed. - pspline : PSpline or None - The PSpline object for setting up and solving penalized spline algorithms. Is None - if no penalized spline setup has been performed (typically done in :meth:`._setup_spline`). - vandermonde : numpy.ndarray or None - The Vandermonde matrix for solving polynomial equations. Is None if no polynomial - setup has been performed (typically done in :meth:`._setup_polynomial`). - whittaker_system : PenalizedSystem or None - The PenalizedSystem object for setting up and solving Whittaker-smoothing-based - algorithms. Is None if no Whittaker setup has been performed (typically done in - :meth:`_setup_whittaker`). - x : numpy.ndarray or None - The x-values for the object. If initialized with None, then `x` is initialized the - first function call to have the same length as the input `data` and has min and max - values of -1 and 1, respectively. - x_domain : numpy.ndarray - The minimum and maximum values of `x`. If `x_data` is None during initialization, then - set to numpy.ndarray([-1, 1]). - - """ - - def __init__(self, x_data=None, check_finite=True, assume_sorted=False, - output_dtype=None): - """ - Initializes the algorithm object. - - Parameters - ---------- - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 during the first function call with length equal to the - input data length. - check_finite : bool, optional - If True (default), will raise an error if any values in input data are not finite. - Setting to False will skip the check. Note that errors may occur if - `check_finite` is False and the input data contains non-finite values. - assume_sorted : bool, optional - If False (default), will sort the input `x_data` values. Otherwise, the - input is assumed to be sorted. Note that some functions may raise an error - if `x_data` is not sorted. - output_dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing - of the input data. - - """ - if x_data is None: - self.x = None - self.x_domain = np.array([-1., 1.]) - self._len = None - else: - self.x = _check_array(x_data, check_finite=check_finite) - self._len = len(self.x) - self.x_domain = np.polynomial.polyutils.getdomain(self.x) - - if x_data is None or assume_sorted: - self._sort_order = None - self._inverted_order = None - else: - self._sort_order = self.x.argsort(kind='mergesort') - self.x = self.x[self._sort_order] - self._inverted_order = _inverted_sort(self._sort_order) - - self.whittaker_system = None - self.vandermonde = None - self.poly_order = -1 - self.pspline = None - self._check_finite = check_finite - self._dtype = output_dtype - - def _return_results(self, baseline, params, dtype, sort_keys=(), axis=-1): - """ - Re-orders the input baseline and parameters based on the x ordering. - - If `self._sort_order` is None, then no reordering is performed. - - Parameters - ---------- - baseline : numpy.ndarray, shape (N,) - The baseline output by the baseline function. - params : dict - The parameter dictionary output by the baseline function. - dtype : [type] - The desired output dtype for the baseline. - sort_keys : Iterable, optional - An iterable of keys corresponding to the values in `params` that need - re-ordering. Default is (). - axis : int, optional - The axis of the input which defines each unique set of data. Default is -1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The input `baseline` after re-ordering and setting to the desired dtype. - params : dict - The input `params` after re-ordering the values for `sort_keys`. - - """ - if self._sort_order is not None: - for key in sort_keys: - if key in params: # some parameters are conditionally output - # assumes params all all just one dimensional arrays - params[key] = params[key][self._inverted_order] - baseline = _sort_array(baseline, sort_order=self._inverted_order, axis=axis) - - baseline = baseline.astype(dtype, copy=False) - - return baseline, params - - @classmethod - def _register(cls, func=None, *, sort_keys=(), dtype=None, order=None, ensure_1d=True, - axis=-1): - """ - Wraps a baseline function to validate inputs and correct outputs. - - The input data is converted to a numpy array, validated to ensure the length is - consistent, and ordered to match the input x ordering. The outputs are corrected - to ensure proper inverted sort ordering and dtype. - - Parameters - ---------- - func : Callable, optional - The function that is being decorated. Default is None, which returns a partial function. - sort_keys : tuple, optional - The keys within the output parameter dictionary that will need sorting to match the - sort order of :attr:`.x`. Default is (). - dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing of `array`. - order : {None, 'C', 'F'}, optional - The order for the output array. Default is None, which will use the default array - ordering. Other valid options are 'C' for C ordering or 'F' for Fortran ordering. - ensure_1d : bool, optional - If True (default), will raise an error if the shape of `array` is not a one dimensional - array with shape (N,) or a two dimensional array with shape (N, 1) or (1, N). - axis : int, optional - The axis of the input on which to check its length. Default is -1. - - Returns - ------- - numpy.ndarray - The calculated baseline. - dict - A dictionary of parameters output by the baseline function. - - """ - if func is None: - return partial( - cls._register, sort_keys=sort_keys, dtype=dtype, order=order, - ensure_1d=ensure_1d, axis=axis - ) - - @wraps(func) - def inner(self, data=None, *args, **kwargs): - if self.x is None: - if data is None: - raise TypeError('"data" and "x_data" cannot both be None') - reset_x = False - input_y = True - y, self.x = _yx_arrays( - data, check_finite=self._check_finite, dtype=dtype, order=order, - ensure_1d=ensure_1d, axis=axis - ) - self._len = y.shape[axis] - else: - reset_x = True - if data is not None: - input_y = True - y = _check_sized_array( - data, self._len, check_finite=self._check_finite, dtype=dtype, order=order, - ensure_1d=ensure_1d, axis=axis, name='data' - ) - else: - y = data - input_y = False - # update self.x just to ensure dtype and order are correct - x_dtype = self.x.dtype - self.x = _check_array( - self.x, dtype=dtype, order=order, check_finite=False, ensure_1d=False - ) - - if input_y: - y = _sort_array(y, sort_order=self._sort_order, axis=axis) - - if input_y and self._dtype is None: - output_dtype = y.dtype - else: - output_dtype = self._dtype - - baseline, params = func(self, y, *args, **kwargs) - if reset_x: - self.x = np.array(self.x, dtype=x_dtype, copy=False) - - return self._return_results( - baseline, params, output_dtype, sort_keys, axis - ) - - return inner - - @contextmanager - def _override_x(self, new_x, new_sort_order=None): - """ - Temporarily sets the x-values for the object to a different array. - - Useful when fitting extensions of the x attribute. - - Parameters - ---------- - new_x : numpy.ndarray - The x values to temporarily use. - new_sort_order : [type], optional - The sort order for the new x values. Default is None, which will not sort. - - Yields - ------ - pybaselines._algorithm_setup._Algorithm - The _Algorithm object with the new x attribute. - - """ - old_x = self.x - old_len = self._len - old_x_domain = self.x_domain - old_sort_order = self._sort_order - old_inverted_order = self._inverted_order - # also have to reset any sized attributes to force recalculation for new x - old_poly_order = self.poly_order - old_vandermonde = self.vandermonde - old_whittaker_system = self.whittaker_system - old_pspline = self.pspline - - try: - self.x = _check_array(new_x, check_finite=self._check_finite) - self._len = len(self.x) - self.x_domain = np.polynomial.polyutils.getdomain(self.x) - self._sort_order = new_sort_order - if self._sort_order is not None: - self._inverted_order = _inverted_sort(self._sort_order) - else: - self._inverted_order = None - - self.vandermonde = None - self.poly_order = -1 - self.whittaker_system = None - self.pspline = None - - yield self - - finally: - self.x = old_x - self._len = old_len - self.x_domain = old_x_domain - self._sort_order = old_sort_order - self._inverted_order = old_inverted_order - self.vandermonde = old_vandermonde - self.poly_order = old_poly_order - self.whittaker_system = old_whittaker_system - self.pspline = old_pspline - - def _setup_whittaker(self, y, lam=1, diff_order=2, weights=None, copy_weights=False, - allow_lower=True, reverse_diags=None): - """ - Sets the starting parameters for doing penalized least squares. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - lam : float, optional - The smoothing parameter, lambda. Typical values are between 10 and - 1e8, but it strongly depends on the penalized least square method - and the differential order. Default is 1. - diff_order : int, optional - The integer differential order; must be greater than 0. Default is 2. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - shape (N,) and all values set to 1. - copy_weights : boolean, optional - If True, will copy the array of input weights. Only needed if the - algorithm changes the weights in-place. Default is False. - allow_lower : boolean, optional - If True (default), will allow using only the lower non-zero diagonals of - the squared difference matrix. If False, will include all non-zero diagonals. - reverse_diags : {None, False, True}, optional - If True, will reverse the order of the diagonals of the squared difference - matrix. If False, will never reverse the diagonals. If None (default), will - only reverse the diagonals if using pentapy's solver. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - weight_array : numpy.ndarray, shape (N,), optional - The weighting array. - - Raises - ------ - ValueError - Raised is `diff_order` is less than 1. - - Warns - ----- - ParameterWarning - Raised if `diff_order` is greater than 3. - - """ - if diff_order < 1: - raise ValueError( - 'the difference order must be > 0 for Whittaker-smoothing-based methods' - ) - elif diff_order > 3: - warnings.warn( - ('difference orders greater than 3 can have numerical issues;' - ' consider using a difference order of 2 or 1 instead'), - ParameterWarning, stacklevel=2 - ) - weight_array = _check_optional_array( - self._len, weights, copy_input=copy_weights, check_finite=self._check_finite - ) - if self._sort_order is not None and weights is not None: - weight_array = weight_array[self._sort_order] - - if self.whittaker_system is not None: - self.whittaker_system.reset_diagonals(lam, diff_order, allow_lower, reverse_diags) - else: - self.whittaker_system = PenalizedSystem( - self._len, lam, diff_order, allow_lower, reverse_diags - ) - - return y, weight_array - - def _setup_polynomial(self, y, weights=None, poly_order=2, calc_vander=False, - calc_pinv=False, copy_weights=False): - """ - Sets the starting parameters for doing polynomial fitting. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - poly_order : int, optional - The polynomial order. Default is 2. - calc_vander : bool, optional - If True, will calculate and the Vandermonde matrix. Default is False. - calc_pinv : bool, optional - If True, and if `return_vander` is True, will calculate and return the - pseudo-inverse of the Vandermonde matrix. Default is False. - copy_weights : boolean, optional - If True, will copy the array of input weights. Only needed if the - algorithm changes the weights in-place. Default is False. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - weight_array : numpy.ndarray, shape (N,) - The weight array for fitting a polynomial to the data. - pseudo_inverse : numpy.ndarray - Only returned if `calc_pinv` is True. The pseudo-inverse of the - Vandermonde matrix, calculated with singular value decomposition (SVD). - - Raises - ------ - ValueError - Raised if `calc_pinv` is True and `calc_vander` is False. - - Notes - ----- - If x_data is given, its domain is reduced from ``[min(x_data), max(x_data)]`` - to [-1., 1.] to improve the numerical stability of calculations; since the - Vandermonde matrix goes from ``x**0`` to ``x^**poly_order``, large values of - x would otherwise cause difficulty when doing least squares minimization. - - """ - weight_array = _check_optional_array( - self._len, weights, copy_input=copy_weights, check_finite=self._check_finite - ) - if self._sort_order is not None and weights is not None: - weight_array = weight_array[self._sort_order] - - if calc_vander: - if self.vandermonde is None or poly_order > self.poly_order: - mapped_x = np.polynomial.polyutils.mapdomain( - self.x, self.x_domain, np.array([-1., 1.]) - ) - self.vandermonde = np.polynomial.polynomial.polyvander(mapped_x, poly_order) - elif poly_order < self.poly_order: - self.vandermonde = self.vandermonde[:, :poly_order + 1] - self.poly_order = poly_order - - if not calc_pinv: - return y, weight_array - elif not calc_vander: - raise ValueError('if calc_pinv is True, then calc_vander must also be True') - - if weights is None: - pseudo_inverse = np.linalg.pinv(self.vandermonde) - else: - pseudo_inverse = np.linalg.pinv(np.sqrt(weight_array)[:, None] * self.vandermonde) - - return y, weight_array, pseudo_inverse - - def _setup_spline(self, y, weights=None, spline_degree=3, num_knots=10, - penalized=True, diff_order=3, lam=1, make_basis=True, allow_lower=True, - reverse_diags=None, copy_weights=False): - """ - Sets the starting parameters for doing spline fitting. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - num_knots : int, optional - The number of interior knots for the splines. Default is 10. - penalized : bool, optional - Whether the basis matrix should be for a penalized spline or a regular - B-spline. Default is True, which creates the basis for a penalized spline. - diff_order : int, optional - The integer differential order for the spline penalty; must be greater than 0. - Default is 3. Only used if `penalized` is True. - lam : float, optional - The smoothing parameter, lambda. Typical values are between 10 and - 1e8, but it strongly depends on the number of knots and the difference order. - Default is 1. - make_basis : bool, optional - If True (default), will create the matrix containing the spline basis functions. - allow_lower : boolean, optional - If True (default), will include only the lower non-zero diagonals of - the squared difference matrix. If False, will include all non-zero diagonals. - reverse_diags : boolean, optional - If True, will reverse the order of the diagonals of the penalty matrix. - Default is False. - copy_weights : boolean, optional - If True, will copy the array of input weights. Only needed if the - algorithm changes the weights in-place. Default is False. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - weight_array : numpy.ndarray, shape (N,) - The weight array for fitting the spline to the data. - - Warns - ----- - ParameterWarning - Raised if `diff_order` is greater than 4. - - Notes - ----- - `degree` is used instead of `order` like for polynomials since the order of a spline - is defined by convention as ``degree + 1``. - - """ - weight_array = _check_optional_array( - self._len, weights, dtype=float, order='C', copy_input=copy_weights, - check_finite=self._check_finite - ) - if self._sort_order is not None and weights is not None: - weight_array = weight_array[self._sort_order] - - if make_basis: - if diff_order > 4: - warnings.warn( - ('differential orders greater than 4 can have numerical issues;' - ' consider using a differential order of 2 or 3 instead'), - ParameterWarning, stacklevel=2 - ) - - if self.pspline is None or not self.pspline.same_basis(num_knots, spline_degree): - self.pspline = PSpline( - self.x, num_knots, spline_degree, self._check_finite, lam, diff_order, - allow_lower, reverse_diags - ) - else: - self.pspline.reset_penalty_diagonals( - lam, diff_order, allow_lower, reverse_diags - ) - - return y, weight_array - - def _setup_morphology(self, y, half_window=None, **window_kwargs): - """ - Sets the starting parameters for morphology-based methods. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using pybaselines.morphological.optimize_window. - **window_kwargs - Keyword arguments to pass to :func:`.optimize_window`. - Possible items are: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 3. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable half-window size. If None (default), will be - set to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - output_half_window : int - The accepted half window size. - - Notes - ----- - Ensures that window size is odd since morphological operations operate in - the range [-output_half_window, ..., output_half_window]. - - Half windows are dealt with rather than full window sizes to clarify their - usage. SciPy morphology operations deal with full window sizes. - - """ - if half_window is not None: - output_half_window = _check_half_window(half_window) - else: - output_half_window = optimize_window(y, **window_kwargs) - - return y, output_half_window - - def _setup_smooth(self, y, half_window=0, allow_zero=True, **pad_kwargs): - """ - Sets the starting parameters for doing smoothing-based algorithms. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - half_window : int, optional - The half-window used for the smoothing functions. Used - to pad the left and right edges of the data to reduce edge - effects. Default is 0, which provides no padding. - allow_zero : bool, optional - If True (default), allows `half_window` to be 0; otherwise, `half_window` - must be at least 1. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - numpy.ndarray, shape (``N + 2 * half_window``,) - The padded array of data. - - """ - hw = _check_half_window(half_window, allow_zero) - return pad_edges(y, hw, **pad_kwargs) - - def _setup_classification(self, y, weights=None): - """ - Sets the starting parameters for doing classification algorithms. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - weight_array : numpy.ndarray, shape (N,) - The weight array for the data, with boolean dtype. - - """ - weight_array = _check_optional_array( - self._len, weights, dtype=bool, check_finite=self._check_finite - ) - if self._sort_order is not None and weights is not None: - weight_array = weight_array[self._sort_order] - - return y, weight_array - - def _get_function(self, method, modules): - """ - Tries to retrieve the indicated function from a list of modules. - - Parameters - ---------- - method : str - The string name of the desired function. Case does not matter. - modules : Sequence - A sequence of modules in which to look for the method. - - Returns - ------- - func : Callable - The corresponding function. - func_module : str - The module that `func` belongs to. - class_object : pybaselines._algorithm_setup._Algorithm - The `_Algorithm` object which will be used for fitting. - - Raises - ------ - AttributeError - Raised if no matching function is found within the modules. - - """ - function_string = method.lower() - for module in modules: - if hasattr(module, function_string): - func_module = module.__name__.split('.')[-1] - # if self is a Baseline class, can just use its method - if hasattr(self, function_string): - func = getattr(self, function_string) - class_object = self - else: - # have to reset x ordering so that all outputs and parameters are - # correctly sorted - if self._sort_order is not None: - x = self.x[self._inverted_order] - assume_sorted = False - else: - x = self.x - assume_sorted = True - class_object = getattr(module, '_' + func_module.capitalize())( - x, check_finite=self._check_finite, assume_sorted=assume_sorted, - output_dtype=self._dtype - ) - func = getattr(class_object, function_string) - break - else: # in case no break - mod_names = [module.__name__ for module in modules] - raise AttributeError(( - f'unknown method "{method}" or method is not within the allowed ' - f'modules: {mod_names}' - )) - - return func, func_module, class_object - - def _setup_optimizer(self, y, method, modules, method_kwargs=None, copy_kwargs=True, **kwargs): - """ - Sets the starting parameters for doing optimizer algorithms. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - method : str - The string name of the desired function, like 'asls'. Case does not matter. - modules : Sequence(module, ...) - The modules to search for the indicated `method` function. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the fitting function. Default - is None, which uses an emtpy dictionary. - copy_kwargs : bool, optional - If True (default), will copy the input `method_kwargs` so that the input - dictionary is not modified within the function. - **kwargs - Deprecated in version 0.8.0 and will be removed in version 0.10 or 1.0. Pass any - keyword arguments for the fitting function in the `method_kwargs` dictionary. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - baseline_func : Callable - The function for fitting the baseline. - func_module : str - The string name of the module that contained `fit_func`. - method_kws : dict - A dictionary of keyword arguments to pass to `fit_func`. - class_object : pybaselines._algorithm_setup._Algorithm - The `_Algorithm` object which will be used for fitting. - - Warns - ----- - DeprecationWarning - Passed if `kwargs` is not empty. - - """ - baseline_func, func_module, class_object = self._get_function(method, modules) - if method_kwargs is None: - method_kws = {} - elif copy_kwargs: - method_kws = method_kwargs.copy() - else: - method_kws = method_kwargs - - if 'x_data' in method_kws: - raise KeyError('"x_data" should not be within the method keyword arguments') - - if kwargs: # TODO remove in version 0.10 or 1.0 - warnings.warn( - ('Passing additional keyword arguments directly to optimizer functions is ' - 'deprecated and will be removed in version 0.10.0 or version 1.0. Place all ' - 'keyword arguments into the method_kwargs dictionary instead.'), - DeprecationWarning, stacklevel=2 - ) - method_kws.update(kwargs) - - return ( - _sort_array(y, self._inverted_order), baseline_func, func_module, method_kws, - class_object - ) - - def _setup_misc(self, y): - """ - Sets the starting parameters for doing miscellaneous algorithms. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, already converted to a numpy - array by :meth:`._register`. - - Returns - ------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, converted to a numpy array. - - Notes - ----- - Since the miscellaneous functions are not related, the only use of this - function is for aliasing the input `data` to `y`. - - """ - return y - - -def _sort_array(array, sort_order=None, axis=-1): - """ - Sorts the input array only if given a non-None sorting order. - - Parameters - ---------- - array : numpy.ndarray - The array to sort. - sort_order : numpy.ndarray, optional - The array defining the sort order for the input array. Default is None, which - will not sort the input. - axis : int, optional - The axis of the input which defines each unique set of data. Default is -1. - - Returns - ------- - output : numpy.ndarray - The input array after optionally sorting. - - Raises - ------ - ValueError - Raised if the input array has more than two dimensions. - - """ - if sort_order is None: - output = array - else: - n_dims = array.ndim - if n_dims == 1: - output = array[sort_order] - elif n_dims == 2: - axes = [..., ...] - axes[axis] = sort_order - output = array[tuple(axes)] - else: - raise ValueError('too many dimensions to sort the data') - - return output - - -def _class_wrapper(klass): - """ - Wraps a function to call the corresponding class method instead. - - Parameters - ---------- - klass : _Algorithm - The class being wrapped. - - """ - def outer(func): - func_signature = signature(func) - method = func.__name__ - - @wraps(func) - def inner(*args, **kwargs): - total_inputs = func_signature.bind(*args, **kwargs) - x = total_inputs.arguments.pop('x_data', None) - return getattr(klass(x_data=x), method)(*total_inputs.args, **total_inputs.kwargs) - return inner - - return outer diff --git a/GSASII/pybaselines/_banded_utils.py b/GSASII/pybaselines/_banded_utils.py deleted file mode 100644 index 168831bb3..000000000 --- a/GSASII/pybaselines/_banded_utils.py +++ /dev/null @@ -1,757 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper functions for working with banded linear systems. - -Created on December 8, 2021 -@author: Donald Erb - -""" - -import numpy as np -from scipy.linalg import solve_banded, solveh_banded -from scipy.sparse import identity, diags - -#from . import config # removed as causes problems w/GSAS-II with no clear use -from ._compat import _HAS_PENTAPY, _pentapy_solve -from ._validation import _check_lam - - -def _shift_rows(matrix, upper_diagonals=2, lower_diagonals=None): - """ - Shifts the upper and lower diagonals of a banded matrix in compressed form. - - Parameters - ---------- - matrix : numpy.ndarray - The matrix to be shifted. Note that all modifications are done in-place. - upper_diagonals : int, optional - The number of upper diagonals to shift. Default is 2. - lower_diagonals : int, optional - The number of lower diagonals to shift. Default is None, which uses the - same value as `upper_diagonals`. - - Returns - ------- - matrix : numpy.ndarray - The shifted matrix. - - Notes - ----- - Necessary to match the diagonal matrix format required by SciPy's solve_banded - function. - - Performs the following transformation with ``upper_diagonals=2`` and ``lower_diagonals=2`` - (left is input, right is output): - - [[a b c ... d 0 0] [[0 0 a ... b c d] - [e f g ... h i 0] [0 e f ... g h i] - [j k l ... m n o] --> [j k l ... m n o] - [0 p q ... r s t] [p q r ... s t 0] - [0 0 u ... v w x]] [u v w ... x 0 0]] - - The right matrix would be directly obtained when using SciPy's sparse diagonal - matrices, but when using multiplication with NumPy arrays, the result is the - left matrix, which has to be shifted to match the desired format. - - """ - for row, shift in enumerate(range(-upper_diagonals, 0)): - matrix[row, -shift:] = matrix[row, :shift] - matrix[row, :-shift] = 0 - - if lower_diagonals is None: - lower_diagonals = upper_diagonals - for pos_row, shift in enumerate(range(lower_diagonals, 0, -1), 1): - row = -pos_row - matrix[row, :-shift] = matrix[row, shift:] - matrix[row, -shift:] = 0 - - return matrix - - -def _lower_to_full(ab): - """ - Converts a lower banded array to a symmetric banded array. - - The lower bands are flipped and then shifted to make the upper bands. - - Parameters - ---------- - ab : numpy.ndarray, shape (M, N) - The lower banded array. - - Returns - ------- - ab_full : numpy.ndarray, shape (``2 * M - 1``, N) - The full, symmetric banded array. - - """ - ab_rows, ab_columns = ab.shape - ab_full = np.concatenate((np.zeros((ab_rows - 1, ab_columns)), ab)) - ab_full[:ab_rows - 1] = ab[1:][::-1] - _shift_rows(ab_full, upper_diagonals=ab_rows - 1, lower_diagonals=0) - - return ab_full - - -def _pad_diagonals(diagonals, padding, lower_only=True): - """ - Pads the diagonals of a banded matrix with zeros. - - Parameters - ---------- - diagonals : numpy.ndarray, shape (A, N) - The diagonals of the banded matrix with `A` bands. - padding : int - The number of rows of zeros to pad the input with. If `padding` is less than - or equal to 0, then no padding is applied. - lower_only : bool, optional - If True (default), will only add the padding rows to the bottom on the - input array. If False, will add the padding to both the top and bottom - of the input. - - Returns - ------- - output : numpy.ndarray - The padded diagonals. Has a shape of (``A + padding``, N) if `lower_only` is - True, otherwise (``A + 2 * padding``, N). - - """ - if padding > 0: - pad_layers = np.zeros((padding, diagonals.shape[1])) - if lower_only: - output = np.concatenate((diagonals, pad_layers)) - else: - output = np.concatenate((pad_layers, diagonals, pad_layers)) - else: - output = diagonals - - return output - - -def _add_diagonals(array_1, array_2, lower_only=True): - """ - Adds two arrays containing the diagonals of banded matrices. - - The array with the least rows is padded with zeros to allow the sum of the two arrays. - - Parameters - ---------- - array_1 : numpy.ndarray, shape (A, N) - An array to add. - array_2 : numpy.ndarray, shape (B, N) - An array to add. - lower_only : bool, optional - If True (default), will only add zero padding to the bottom of the smaller - array. If False, will add half of the zero padding to both the top and bottom - of the smaller array. - - Returns - ------- - summed_diagonals : numpy.ndarray, shape (`max(A, B)`, N) - The addition of `a` and `b` after adding the correct zero padding. - - Raises - ------ - ValueError - Raised if `a.shape[1]` and `b.shape[1]` are not equal or if `lower` is False - and `abs(a.shape[0] - b.shape[0])` is not even. - - """ - a, b = np.atleast_2d(array_1, array_2) - a_shape = a.shape - b_shape = b.shape - if a_shape[1] != b_shape[1]: - raise ValueError(( - f'the diagonal arrays have a dimension mismatch; {a_shape[1]} and {b_shape[1]}' - ' should be equal' - )) - row_mismatch = a_shape[0] - b_shape[0] - if row_mismatch == 0: - summed_diagonals = a + b - else: - abs_mismatch = abs(row_mismatch) - if lower_only: - padding = np.zeros((abs_mismatch, a_shape[1])) - if row_mismatch > 0: - summed_diagonals = a + np.concatenate((b, padding)) - else: - summed_diagonals = np.concatenate((a, padding)) + b - else: - if abs_mismatch % 2: - raise ValueError( - 'row mismatch between the arrays must be even if lower_only=False, ' - f'but instead was {abs_mismatch}' - ) - padding = np.zeros((abs_mismatch // 2, a_shape[1])) - if row_mismatch > 0: - summed_diagonals = a + np.concatenate((padding, b, padding)) - else: - summed_diagonals = np.concatenate((padding, a, padding)) + b - - return summed_diagonals - - -def difference_matrix(data_size, diff_order=2, diff_format=None): - """ - Creates an n-order finite-difference matrix. - - Parameters - ---------- - data_size : int - The number of data points. - diff_order : int, optional - The integer differential order; must be >= 0. Default is 2. - diff_format : str or None, optional - The sparse format to use for the difference matrix. Default is None, - which will use the default specified in :func:`scipy.sparse.diags`. - - Returns - ------- - diff_matrix : scipy.sparse.base.spmatrix - The sparse difference matrix. - - Raises - ------ - ValueError - Raised if `diff_order` or `data_size` is negative. - - Notes - ----- - The resulting matrices are sparse versions of:: - - import numpy as np - np.diff(np.eye(data_size), diff_order, axis=0) - - This implementation allows using the differential matrices are they - are written in various publications, ie. ``D.T @ D``. - - Most baseline algorithms use 2nd order differential matrices when - doing penalized least squared fitting or Whittaker-smoothing-based fitting. - - """ - if diff_order < 0: - raise ValueError('the differential order must be >= 0') - elif data_size < 0: - raise ValueError('data size must be >= 0') - elif diff_order > data_size: - # do not issue warning or exception to maintain parity with np.diff - diff_order = data_size - - if diff_order == 0: - # faster to directly create identity matrix - diff_matrix = identity(data_size, format=diff_format) - else: - diagonals = np.zeros(2 * diff_order + 1) - diagonals[diff_order] = 1 - for _ in range(diff_order): - diagonals = diagonals[:-1] - diagonals[1:] - - diff_matrix = diags( - diagonals, np.arange(diff_order + 1), - shape=(data_size - diff_order, data_size), format=diff_format - ) - - return diff_matrix - - -def _diff_1_diags(data_size, lower_only=True): - """ - Creates the the diagonals of the square of a first-order finite-difference matrix. - - Parameters - ---------- - data_size : int - The number of data points. - lower_only : bool, optional - If True (default), will return only the lower diagonals of the - matrix. If False, will include all diagonals of the matrix. - - Returns - ------- - output : numpy.ndarray - The array containing the diagonal data. Has a shape of (2, `data_size`) - if `lower_only` is True, otherwise (3, `data_size`). - - Notes - ----- - Equivalent to calling:: - - from pybaselines.utils import difference_matrix - diff_matrix = difference_matrix(data_size, 1) - output = (diff_matrix.T @ diff_matrix).todia().data[::-1] - if lower_only: - output = output[1:] - - but is several orders of magnitude times faster. - - The data is output in the banded format required by SciPy's solve_banded - and solveh_banded. - - """ - output = np.full((2 if lower_only else 3, data_size), -1.) - - output[-1, -1] = 0 - output[-2, 0] = output[-2, -1] = 1 - output[-2, 1:-1] = 2 - - if not lower_only: - output[0, 0] = 0 - - return output - - -def _diff_2_diags(data_size, lower_only=True): - """ - Creates the the diagonals of the square of a second-order finite-difference matrix. - - Parameters - ---------- - data_size : int - The number of data points. - lower_only : bool, optional - If True (default), will return only the lower diagonals of the - matrix. If False, will include all diagonals of the matrix. - - Returns - ------- - output : numpy.ndarray - The array containing the diagonal data. Has a shape of (3, `data_size`) - if `lower_only` is True, otherwise (5, `data_size`). - - Notes - ----- - Equivalent to calling:: - - from pybaselines.utils import difference_matrix - diff_matrix = difference_matrix(data_size, 2) - output = (diff_matrix.T @ diff_matrix).todia().data[::-1] - if lower_only: - output = output[2:] - - but is several orders of magnitude times faster. - - The data is output in the banded format required by SciPy's solve_banded - and solveh_banded. - - """ - output = np.ones((3 if lower_only else 5, data_size)) - - output[-1, -1] = output[-1, -2] = output[-2, -1] = 0 - output[-2, 0] = output[-2, -2] = -2 - output[-2, 1:-2] = -4 - output[-3, 1] = output[-3, -2] = 5 - output[-3, 2:-2] = 6 - - if not lower_only: - output[0, 0] = output[1, 0] = output[0, 1] = 0 - output[1, 1] = output[1, -1] = -2 - output[1, 2:-1] = -4 - - return output - - -def _diff_3_diags(data_size, lower_only=True): - """ - Creates the the diagonals of the square of a third-order finite-difference matrix. - - Parameters - ---------- - data_size : int - The number of data points. - lower_only : bool, optional - If True (default), will return only the lower diagonals of the - matrix. If False, will include all diagonals of the matrix. - - Returns - ------- - output : numpy.ndarray - The array containing the diagonal data. Has a shape of (4, `data_size`) - if `lower_only` is True, otherwise (7, `data_size`). - - Notes - ----- - Equivalent to calling:: - - from pybaselines.utils import difference_matrix - diff_matrix = difference_matrix(data_size, 3) - output = (diff_matrix.T @ diff_matrix).todia().data[::-1] - if lower_only: - output = output[3:] - - but is several orders of magnitude times faster. - - The data is output in the banded format required by SciPy's solve_banded - and solveh_banded. - - """ - output = np.full((4 if lower_only else 7, data_size), -1.) - - for row in range(-1, -4, -1): - output[row, -4 - row:] = 0 - - output[-2, 0] = output[-2, -3] = 3 - output[-2, 1:-3] = 6 - output[-3, 0] = output[-3, -2] = -3 - output[-3, 1] = output[-3, -3] = -12 - output[-3, 2:-3] = -15 - output[-4, 0] = output[-4, -1] = 1 - output[-4, 1] = output[-4, -2] = 10 - output[-4, 2] = output[-4, -3] = 19 - output[-4, 3:-3] = 20 - - if not lower_only: - for row in range(3): - output[row, :3 - row] = 0 - - output[1, 2] = output[1, -1] = 3 - output[1, 3:-1] = 6 - output[2, 1] = output[2, -1] = -3 - output[2, 2] = output[2, -2] = -12 - output[2, 3:-2] = -15 - - return output - - -def diff_penalty_diagonals(data_size, diff_order=2, lower_only=True, padding=0): - """ - Creates the diagonals of the finite difference penalty matrix. - - If `D` is the finite difference matrix, then the finite difference penalty - matrix is defined as ``D.T @ D``. The penalty matrix is banded and symmetric, so - the non-zero diagonal bands can be computed efficiently. - - Parameters - ---------- - data_size : int - The number of data points. - diff_order : int, optional - The integer differential order; must be >= 0. Default is 2. - lower_only : bool, optional - If True (default), will return only the lower diagonals of the - matrix. If False, will include all diagonals of the matrix. - padding : int, optional - The number of extra layers of zeros to add to the bottom and top, if - `lower_only` is True. Useful if working with other diagonal arrays with - a different number of rows. Default is 0, which adds no extra layers. - Negative `padding` is treated as equivalent to 0. - - Returns - ------- - diagonals : numpy.ndarray - The diagonals of the finite difference penalty matrix. - - Raises - ------ - ValueError - Raised if `diff_order` is negative or if `data_size` less than 1. - - Notes - ----- - Equivalent to calling:: - - from pybaselines.utils import difference_matrix - diff_matrix = difference_matrix(data_size, diff_order) - output = (diff_matrix.T @ diff_matrix).todia().data[::-1] - if lower_only: - output = output[diff_order:] - - but is several orders of magnitude times faster. - - The data is output in the banded format required by SciPy's solve_banded - and solveh_banded functions. - - """ - if diff_order < 0: - raise ValueError('the difference order must be >= 0') - elif data_size <= 0: - raise ValueError('data size must be > 0') - - # the fast, hard-coded values require that data_size > 2 * diff_order + 1, - # otherwise, the band structure has to actually be calculated - if diff_order == 0: - diagonals = np.ones((1, data_size)) - elif data_size < 2 * diff_order + 1 or diff_order > 3: - diff_matrix = difference_matrix(data_size, diff_order, 'csc') - # scipy's diag_matrix stores the diagonals in opposite order of - # the typical LAPACK banded structure - diagonals = (diff_matrix.T @ diff_matrix).todia().data[::-1] - if lower_only: - diagonals = diagonals[diff_order:] - else: - diag_func = {1: _diff_1_diags, 2: _diff_2_diags, 3: _diff_3_diags}[diff_order] - diagonals = diag_func(data_size, lower_only) - - diagonals = _pad_diagonals(diagonals, padding, lower_only=lower_only) - - return diagonals - - -def _pentapy_solver(ab, y, check_output=False): - """ - Convenience function for calling pentapy's solver with defaults already set. - - Solves the linear system :math:`A @ x = y` for `x`, given the matrix `A` in - banded format, `ab`. The default settings of :func`:pentapy.solve` are - already set for the fastest configuration. - - Parameters - ---------- - ab : array-like, shape (M, N) - The matrix `A` in row-wise banded format (see :func:`pentapy.solve`). - y : array-like, shape (N,) - The right-hand side of the equation. - check_output : bool, optional - If True, will check the output solution for non-finite values, which can often - occur without warning from the solver when all values are close to zero. - Default is False. - - Returns - ------- - numpy.ndarray, shape (N,) - The solution to the linear system. - - """ - output = _pentapy_solve(ab, y, is_flat=True, index_row_wise=True, solver=config.PENTAPY_SOLVER) - if check_output and not np.isfinite(output.dot(output)): - raise np.linalg.LinAlgError('non-finite value encountered in pentapy solver output') - - return output - - -class PenalizedSystem: - """ - An object for setting up and solving banded penalized least squares linear systems. - - Attributes - ---------- - diff_order : int - The difference order of the penalty. - lower : bool - If True, the penalty uses only the lower bands of the symmetric banded penalty. Will - use :func:`scipy.linalg.solveh_banded` for solving. If False, contains both the upper - and lower bands of the penalty and will use either :func:`scipy.linalg.solve_banded` - (if `using_pentapy` is False) or :func:`._pentapy_solver` when solving. - main_diagonal_index : int - The index of the main diagonal for `penalty`. Is updated when adding additional matrices - to the penalty, and takes into account whether the penalty is only the lower bands or - the total bands. - num_bands : int - The number of bands in the penalty. The number of bands is assumbed to be symmetric, - so the number of upper and lower bands should both be equal to `num_bands`. - original_diagonals : numpy.ndarray - The original penalty diagonals before multiplying by `lam` or adding any padding. - Maintained so that repeated computations with different `lam` values can be quickly - set up. `original_diagonals` can be either the full or lower bands of the penalty, - and may be reveresed, it depends on the set up. Reset by calling - :meth:`.reset_diagonals`. - penalty : numpy.ndarray - The current penalty. Originally is `original_diagonals` after multiplying by `lam` - and applying padding, but can also be changed by calling :meth:`.add_penalty`. - Reset by calling :meth:`.reset_diagonals`. - reversed : bool - If True, the penalty is reversed of the typical LAPACK banded format. Useful if - multiplying the penalty with an array since the rows get shifted, or if using pentapy's - solver. - using_pentapy : bool - If True, will use pentapy's solver when solving. - - """ - - def __init__(self, data_size, lam=1, diff_order=2, allow_lower=True, - reverse_diags=None, allow_pentapy=True, padding=0): - """ - Initializes the banded system. - - Parameters - ---------- - data_size : int - The number of data points for the system. - lam : float, optional - The penalty factor applied to the difference matrix. Larger values produce - smoother results. Must be greater than 0. Default is 1. - diff_order : int, optional - The difference order of the penalty. Default is 2 (second order difference). - allow_lower : bool, optional - If True (default), will allow only using the lower bands of the penalty matrix, - which allows using :func:`scipy.linalg.solveh_banded` instead of the slightly - slower :func:`scipy.linalg.solve_banded`. - reverse_diags : {None, False, True}, optional - If True, will reverse the order of the diagonals of the squared difference - matrix. If False, will never reverse the diagonals. If None (default), will - only reverse the diagonals if using pentapy's solver. - allow_pentapy : bool, optional - If True (default), will allow using pentapy's solver if `diff_order` is 2 - and pentapy is installed. pentapy's solver is faster than scipy's banded solvers. - padding : int, optional - The number of extra layers of zeros to add to the bottom and potentially - the top if the full bands are used. Default is 0, which adds no extra - layers. Negative `padding` is treated as equivalent to 0. - - """ - self._len = data_size - self.original_diagonals = None - - self.reset_diagonals( - lam, diff_order, allow_lower, reverse_diags, allow_pentapy, padding=padding - ) - - def add_penalty(self, penalty): - """ - Updates `self.penalty` with an additional penalty and updates the bands. - - Parameters - ---------- - penalty : array-like - The additional penalty to add to `self.penalty`. - - Returns - ------- - numpy.ndarray - The updated `self.penalty`. - - """ - self.penalty = _add_diagonals(self.penalty, penalty, lower_only=self.lower) - self._update_bands() - return self.penalty - - def _update_bands(self): - """Updates the number of bands and the index of the main diagonal in `self.penalty`.""" - if self.lower: - self.num_bands = len(self.penalty) - 1 - else: - self.num_bands = len(self.penalty) // 2 - self.main_diagonal_index = 0 if self.lower else self.num_bands - - def reset_diagonals(self, lam=1, diff_order=2, allow_lower=True, reverse_diags=None, - allow_pentapy=True, padding=0): - """ - Resets the diagonals of the system and all of the attributes. - - Useful for reusing the penalized system for a different `lam` value. - - Parameters - ---------- - lam : float, optional - The penalty factor applied to the difference matrix. Larger values produce - smoother results. Must be greater than 0. Default is 1. - diff_order : int, optional - The difference order of the penalty. Default is 2 (second order difference). - allow_lower : bool, optional - If True (default), will allow only using the lower bands of the penalty matrix, - which allows using :func:`scipy.linalg.solveh_banded` instead of the slightly - slower :func:`scipy.linalg.solve_banded`. - reverse_diags : {None, False, True}, optional - If True, will reverse the order of the diagonals of the squared difference - matrix. If False, will never reverse the diagonals. If None (default), will - only reverse the diagonals if using pentapy's solver. - allow_pentapy : bool, optional - If True (default), will allow using pentapy's solver if `diff_order` is 2 - and pentapy is installed. pentapy's solver is faster than scipy's banded solvers. - padding : int, optional - The number of extra layers of zeros to add to the bottom and potentially - the top if the full bands are used. Default is 0, which adds no extra - layers. Negative `padding` is treated as equivalent to 0. - - """ - using_pentapy = allow_pentapy and _HAS_PENTAPY and diff_order == 2 - if allow_lower and not using_pentapy: - lower_only = True - else: - lower_only = False - if reverse_diags or (using_pentapy and reverse_diags is None): - needs_reversed = True - else: - needs_reversed = False - - if self.original_diagonals is None or self.diff_order != diff_order: - diagonal_data = diff_penalty_diagonals(self._len, diff_order, lower_only) - if needs_reversed: - diagonal_data = diagonal_data[::-1] - self.original_diagonals = diagonal_data - else: - if self.lower and not lower_only: - self.original_diagonals = _lower_to_full(self.original_diagonals) - if (self.reversed and not needs_reversed) or (not self.reversed and needs_reversed): - self.original_diagonals = self.original_diagonals[::-1] - if not self.lower and lower_only: - self.original_diagonals = self.original_diagonals[self.diff_order:] - - self.diff_order = diff_order - self.lower = lower_only - self.using_pentapy = using_pentapy - self.reversed = needs_reversed - - self.lam = _check_lam(lam, allow_zero=False) - self.penalty = self.lam * _pad_diagonals(self.original_diagonals, padding, self.lower) - self._update_bands() - - def solve(self, lhs, rhs, overwrite_ab=False, overwrite_b=False, - check_finite=False, l_and_u=None, check_output=False): - """ - Solves the equation ``A @ x = rhs``, given `A` in banded format as `lhs`. - - Parameters - ---------- - lhs : array-like, shape (M, N) - The left-hand side of the equation, in banded format. `lhs` is assumed to be - some slight modification of `self.penalty` in the same format (reversed, lower, - number of bands, etc. are all the same). - rhs : array-like, shape (N,) - The right-hand side of the equation. - overwrite_ab : bool, optional - Whether to overwrite `lhs` when using :func:`scipy.linalg.solveh_banded` or - :func:`scipy.linalg.solve_banded`. Default is False. - overwrite_b : bool, optional - Whether to overwrite `rhs` when using :func:`scipy.linalg.solveh_banded` or - :func:`scipy.linalg.solve_banded`. Default is False. - check_finite : bool, optional - Whether to check if the inputs are finite when using - :func:`scipy.linalg.solveh_banded` or :func:`scipy.linalg.solve_banded`. - Default is False. - l_and_u : Container(int, int), optional - The number of lower and upper bands in `lhs` when using - :func:`scipy.linalg.solve_banded`. Default is None, which uses - (``len(lhs) // 2``, ``len(lhs) // 2``). - check_output : bool, optional - If True, will check the output for non-finite values when using - :func:`._pentapy_solver`. Default is False. - - Returns - ------- - output : numpy.ndarray, shape (N,) - The solution to the linear system, `x`. - - """ - if self.using_pentapy: - output = _pentapy_solver(lhs, rhs, check_output=check_output) - elif self.lower: - output = solveh_banded( - lhs, rhs, overwrite_ab=overwrite_ab, - overwrite_b=overwrite_b, lower=True, check_finite=check_finite - ) - else: - if l_and_u is None: - num_bands = len(lhs) // 2 - l_and_u = (num_bands, num_bands) - output = solve_banded( - l_and_u, lhs, rhs, overwrite_ab=overwrite_ab, - overwrite_b=overwrite_b, check_finite=check_finite - ) - - return output - - def reverse_penalty(self): - """ - Reverses the penalty and original diagonals for the system. - - Raises - ------ - ValueError - Raised if `self.lower` is True, since reversing the half diagonals does - not make physical sense. - - """ - if self.lower: - raise ValueError('cannot reverse diagonals when self.lower is True') - self.penalty = self.penalty[::-1] - self.original_diagonals = self.original_diagonals[::-1] - self.reversed = not self.reversed diff --git a/GSASII/pybaselines/_compat.py b/GSASII/pybaselines/_compat.py deleted file mode 100644 index 7198ed569..000000000 --- a/GSASII/pybaselines/_compat.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -"""Code to help use optional dependencies and handle changes within dependency versions. - -Created on June 24, 2021 -@author: Donald Erb - -""" - -from functools import wraps - - -try: - from pentapy import solve as _pentapy_solve - _HAS_PENTAPY = True -except ImportError: - _HAS_PENTAPY = False - - def _pentapy_solve(*args, **kwargs): - """Dummy function in case pentapy is not installed.""" - raise NotImplementedError('must have pentapy installed to use its solver') - -try: - from numba import jit, prange - _HAS_NUMBA = True -except ImportError: - _HAS_NUMBA = False - - def prange(*args): - """Dummy function that acts exactly like `range` if numba is not installed.""" - return range(*args) - - def jit(func=None, *jit_args, **jit_kwargs): - """Dummy decorator that does nothing if numba is not installed.""" - # First argument in jit would be the function signature. - # Signatures can be given as strings: "float64(float64, float64)" - # or literals or sequences of literals/strings: float64(float64, float64) - # or (float64,) if no return or [int64(int64, int64), float64(float64, float64)]; - # none of which are callable. - if func is None or not callable(func): - # ignore jit_args and jit_kwargs since they are not used by this dummy decorator - return jit - - @wraps(func) - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - return wrapper diff --git a/GSASII/pybaselines/_spline_utils.py b/GSASII/pybaselines/_spline_utils.py deleted file mode 100644 index 0c5bf7426..000000000 --- a/GSASII/pybaselines/_spline_utils.py +++ /dev/null @@ -1,892 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper functions for using splines. - -Created on November 3, 2021 -@author: Donald Erb - - -Several functions were adapted from Cython, Python, and C files from SciPy -(https://github.com/scipy/scipy, accessed November 2, 2021), which was -licensed under the BSD-3-Clause below. - -Copyright (c) 2001-2002 Enthought, Inc. 2003-2019, SciPy Developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" - -import numpy as np -from scipy.interpolate import BSpline, splev -from scipy.linalg import solve_banded, solveh_banded -from scipy.sparse import csc_matrix, csr_matrix, spdiags - -from ._banded_utils import _add_diagonals, _lower_to_full, PenalizedSystem -from ._compat import _HAS_NUMBA, jit -from ._validation import _check_array - - -try: - from scipy.interpolate import _bspl - _scipy_btb_bty = _bspl._norm_eq_lsq -except (AttributeError, ImportError): - # in case scipy ever changes - _scipy_btb_bty = None - - -# adapted from scipy (scipy/interpolate/_bspl.pyx/find_interval); see license above -@jit(nopython=True, cache=True) -def _find_interval(knots, spline_degree, x_val, last_left, num_bases): - """ - Finds the knot interval containing the x-value. - - Parameters - ---------- - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int - The spline degree. - x_val : float - The x-value to find the interval for. - last_left : int - The previous output of this function. For the first call, use any value - less than `spline_degree` to start. - num_bases : int - The total number of basis functions. Equals ``len(knots) - spline_degree - 1``, - but is precomputed rather than having to recompute each function call. - - Returns - ------- - int - The index in `knots` such that ``knots[index] <= x_val < knots[index + 1]``. - - """ - left = last_left if spline_degree < last_left < num_bases else spline_degree - - # x_val less than expected so shift knot interval left - while x_val < knots[left] and left != spline_degree: - left -= 1 - - left += 1 - while x_val >= knots[left] and left != num_bases: - left += 1 - - return left - 1 - - -# adapted from scipy (scipy/interpolate/src/__fitpack.h/_deBoor_D); see license above -@jit(nopython=True, cache=True) -def _de_boor(knots, x_val, spline_degree, left_knot_idx, work): - """ - Computes the non-zero values of the spline bases for the given x-value. - - Parameters - ---------- - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - x_val : float - The x-value at which the spline basis is being computed. - spline_degree : int - The degree of the spline. - left_knot_idx : int - The index in `knots` that defines the interval such that - ``knots[left_knot_idx] <= x_val < knots[left_knot_idx + 1]``. - work : numpy.ndarray, shape (``2 * (spline_degree + 1)``,) - The working array. Modified inplace to store the non-zero values of the spline - bases for `x_val`. - - Notes - ----- - Computes the non-zero values for knots from ``knots[left_knot_idx]`` to - ``knots[left_knot_idx - spline_degree]`` for the x-value using de Boor's recursive - algorithm. - - """ - temp = work + spline_degree + 1 - work[0] = 1.0 - for i in range(1, spline_degree + 1): - temp[:i] = work[:i] - work[0] = 0.0 - for j in range(1, i + 1): - idx = left_knot_idx + j - right_knot = knots[idx] - left_knot = knots[idx - i] - if left_knot == right_knot: - work[j] = 0.0 - continue - - factor = temp[j - 1] / (right_knot - left_knot) - work[j - 1] += factor * (right_knot - x_val) - work[j] = factor * (x_val - left_knot) - - -# adapted from scipy (scipy/interpolate/_bspl.pyx/_make_design_matrix); see license above -@jit(nopython=True, cache=True) -def __make_design_matrix(x, knots, spline_degree): - """ - Calculates the data needed to create the sparse matrix of basis functions for the spline. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int - The degree of the spline. - - Returns - ------- - basis_data : numpy.ndarray, shape (``N * (spline_degree + 1)``,) - The data for all of the basis functions. The basis for each `x[i]` value is represented - by ``basis_data[i * (spline_degree + 1):(i + 1) * (spline_degree + 1)]``. - row_ind : numpy.ndarray, shape (``N * (spline_degree + 1)``,) - The row indices of the data; used for converting `data` into a CSR matrix. - col_ind : numpy.ndarray, shape (``N * (spline_degree + 1)``,) - The column indices of the data; used for converting `data` into a CSR matrix. - - """ - len_x = len(x) - spline_order = spline_degree + 1 - data_length = len_x * spline_order - num_bases = len(knots) - spline_order - work = np.zeros(2 * spline_order) - basis_data = np.zeros(data_length) - row_ind = np.zeros(data_length, dtype=np.intp) - col_ind = np.zeros(data_length, dtype=np.intp) - - idx = 0 - left_knot_idx = spline_degree - for i in range(len_x): - x_val = x[i] - left_knot_idx = _find_interval(knots, spline_degree, x_val, left_knot_idx, num_bases) - _de_boor(knots, x_val, spline_degree, left_knot_idx, work) - - next_idx = idx + spline_order - basis_data[idx:next_idx] = work[:spline_order] - row_ind[idx:next_idx] = i - col_ind[idx:next_idx] = np.arange( - left_knot_idx - spline_degree, min(left_knot_idx + 1, num_bases) - ) - idx = next_idx - - return basis_data, row_ind, col_ind - - -# adapted from scipy (scipy/interpolate/_bspl.pyx/_make_design_matrix); see license above -def _make_design_matrix(x, knots, spline_degree): - """ - Creates the sparse matrix of basis functions for a B-spline. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int - The degree of the spline. - - Returns - ------- - scipy.sparse.csr.csr_matrix, shape (N, K - `spline_degree` - 1) - The sparse matrix containing all the spline basis functions. - - """ - data, row_ind, col_ind = __make_design_matrix(x, knots, spline_degree) - return csr_matrix((data, (row_ind, col_ind)), (len(x), len(knots) - spline_degree - 1)) - - -def _slow_design_matrix(x, knots, spline_degree): - """ - A nieve way of constructing the B-spline basis matrix by evaluating each basis individually. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int - The degree of the spline. - - Returns - ------- - scipy.sparse.csr.csr_matrix, shape (N, K - `spline_degree` - 1) - The sparse matrix containing all the spline basis functions. - - """ - num_bases = len(knots) - spline_degree - 1 - basis = np.empty((num_bases, len(x))) - coeffs = np.zeros(num_bases) - # TODO this is still quite slow and memory intensive; could make something similar - # to __make_design_matrix that is still fast enough without numba; could make a cached - # version, but would need to be able to use knots and x without cacheing them since numpy - # arrays are not hashable -> use an inner function probably; also has the benefit that - # cache gets automatically deleted once basis is created; a cached version would probably - # also be faster than the current numba version of _deBoor (assuming numba allows inner - # functions and dictionaries/caches?) - - # evaluate each single basis - for i in range(num_bases): - coeffs[i] = 1 # evaluate the i-th basis within splev - basis[i] = splev(x, (knots, coeffs, spline_degree)) - coeffs[i] = 0 # reset back to zero - - # The last and first coefficients for the first and last bases, respectively, - # get values == 0 when doing the above calculation, which causes issues when - # using the resulting csr_matrix's data attribute; instead, explicitly set - # those values to a very small, non-zero value; if spline_degree==0, it's fine - if spline_degree > 0: - small_float = np.finfo(float).tiny - basis[spline_degree, 0] = small_float - basis[-(spline_degree + 1), -1] = small_float - - return csc_matrix(basis).T - - -def _spline_knots(x, num_knots=10, spline_degree=3, penalized=True): - """ - Creates the basis matrix for B-splines and P-splines. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The array of x-values - num_knots : int, optional - The number of interior knots for the spline. Default is 10. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - penalized : bool, optional - Whether the basis matrix should be for a penalized spline or a regular - B-spline. Default is True, which creates the basis for a penalized spline. - - Returns - ------- - knots : numpy.ndarray, shape (``num_knots + 2 * spline_degree``,) - The array of knots for the spline, properly padded on each side. - - Notes - ----- - If `penalized` is True, makes the knots uniformly spaced to create penalized - B-splines (P-splines). That way, can use a finite difference matrix to impose - penalties on the spline. - - The knots are padded on each end with `spline_degree` extra knots to provide proper - support for the outermost inner knots. - - Raises - ------ - ValueError - Raised if `num_knots` is less than 2. - - References - ---------- - Eilers, P., et al. Twenty years of P-splines. SORT: Statistics and Operations Research - Transactions, 2015, 39(2), 149-186. - - Hastie, T., et al. The Elements of Statistical Learning. Springer, 2017. Chapter 5. - - """ - if num_knots < 2: # num_knots == 2 means the only knots are the two endpoints - raise ValueError('the number of knots must be at least 2') - - if penalized: - x_min = x.min() - x_max = x.max() - # number of sections is num_knots - 1 since counting the first and last - # knots as inner knots - dx = (x_max - x_min) / (num_knots - 1) - # calculate inner knots separately to ensure x_min and x_max are correct; - # otherwise, they can be slighly off due to floating point errors - inner_knots = np.linspace(x_min, x_max, num_knots) - knots = np.concatenate(( - np.linspace(x_min - spline_degree * dx, x_min - dx, spline_degree), - inner_knots, - np.linspace(x_max + dx, x_max + spline_degree * dx, spline_degree), - )) - else: - # TODO maybe provide a better way to select knot positions for regular B-splines - inner_knots = np.percentile(x, np.linspace(0, 100, num_knots)) - knots = np.concatenate(( - np.repeat(inner_knots[0], spline_degree), inner_knots, - np.repeat(inner_knots[-1], spline_degree) - )) - - return knots - - -def _spline_basis(x, knots, spline_degree=3): - """ - Constructs the spline basis matrix. - - Chooses the fastest constuction route based on the available options. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - - Returns - ------- - scipy.sparse.csr.csr_matrix, shape (N, K - `spline_degree` - 1) - The matrix of basis functions for the spline. - - Notes - ----- - The numba version is ~70% faster than scipy's BSpline.design_matrix (tested - with python 3.9.7 and scipy 1.8.0.dev0+1981 and python 3.8.6 and scipy 1.8.0rc1), - so the numba version is preferred. - - Most checks on the inputs are skipped since this is an internal function and the - proper steps are assumed to be done. For more proper error handling in the inputs, - see :func:`scipy.interpolate.make_lsq_spline`. - - """ - if _HAS_NUMBA: - validate_inputs = True - basis_func = _make_design_matrix - elif hasattr(BSpline, 'design_matrix'): - validate_inputs = False - # BSpline.design_matrix introduced in scipy version 1.8.0 - basis_func = BSpline.design_matrix - else: - validate_inputs = True - basis_func = _slow_design_matrix - - # validate inputs only if not using scipy's version - if validate_inputs: - len_knots = len(knots) - if np.any(x < knots[spline_degree]) or np.any(x > knots[len_knots - spline_degree - 1]): - raise ValueError(( - f'x-values are either < {knots[spline_degree]} or ' - f'> {knots[len_knots - spline_degree - 1]}' - )) - - return basis_func(x, knots, spline_degree) - - -# adapted from scipy (scipy/interpolate/_bspl.pyx/_norm_eq_lsq); see license above -@jit(nopython=True, cache=True) -def _numba_btb_bty(x, knots, spline_degree, y, weights, ab, rhs, basis_data): - """ - Computes ``B.T @ W @ B`` and ``B.T @ W @ y`` for a spline. - - The result of ``B.T @ W @ B`` is stored in LAPACK's lower banded format (see - :func:`scipy.linalg.solveh_banded`). - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int - The degree of the spline. - y : numpy.ndarray, shape (N,) - The y-values for fitting the spline. - weights : numpy.ndarray, shape(N,) - The weights for each y-value. - ab : numpy.ndarray, shape (`spline_degree` + 1, N) - An array of zeros that will be modified inplace to contain ``B.T @ W @ B`` in - lower banded format. - rhs : numpy.ndarray, shape (N,) - An array of zeros that will be modified inplace to contain the right-hand - side of the normal equation, ``B.T @ W @ y``. - basis_data : numpy.ndarray, shape (``N * (spline_degree + 1)``,) - The data for all of the basis functions. The basis for each `x[i]` value is represented - by ``basis_data[i * (spline_degree + 1):(i + 1) * (spline_degree + 1)]``. If the basis, - `B` is a sparse matrix, then `basis_data` can be gotten using `B.tocsr().data`. - - Notes - ----- - This function is slightly different than SciPy's `_norm_eq_lst` function in - scipy.interpolate._bspl.pyx since this function uses the weights directly, rather - than squaring the weights, and directly uses the basis data (gotten by using the - `data` attribute of the basis in CSR sparse format) rather than computing the - basis using de Boor's algorithm. This makes it much faster when solving a spline - system using iteratively reweighted least squares since the basis only needs to be - created once. - - There is no significant time difference between calling _find_interval each time this - function is used compared to calculating all the intervals once and inputting them - into this function. - - """ - spline_order = spline_degree + 1 - num_bases = len(knots) - spline_order - work = np.zeros(2 * spline_order) - - left_knot_idx = spline_degree - idx = 0 - for i in range(len(x)): - x_val = x[i] - y_val = y[i] - weight_val = weights[i] - left_knot_idx = _find_interval(knots, spline_degree, x_val, left_knot_idx, num_bases) - - next_idx = idx + spline_order - work[:] = 0 - work[:spline_order] = basis_data[idx:next_idx] - idx = next_idx - for j in range(spline_order): - work_val = work[j] - # B.T @ W @ B - for k in range(j + 1): - column = left_knot_idx - spline_degree + k - ab[j - k, column] += work_val * work[k] * weight_val - - # B.T @ W @ y - row = left_knot_idx - spline_degree + j - rhs[row] += work_val * y_val * weight_val - - -# adapted from scipy (scipy/interpolate/_bsplines.py/make_lsq_spline); see license above -def _solve_pspline(x, y, weights, basis, penalty, knots, spline_degree, rhs_extra=None, - lower_only=True): - """ - Solves the coefficients for a weighted penalized spline. - - Solves the linear equation ``(B.T @ W @ B + P) c = B.T @ W @ y`` for the spline - coefficients, `c`, given the spline basis, `B`, the weights (diagonal of `W`), the - penalty `P`, and `y`. Attempts to calculate ``B.T @ W @ B`` and ``B.T @ W @ y`` as - a banded system to speed up the calculation. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - y : numpy.ndarray, shape (N,) - The y-values for fitting the spline. - weights : numpy.ndarray, shape (N,) - The weights for each y-value. - basis : scipy.sparse.base.spmatrix, shape (N, K - `spline_degree` - 1) - The sparse spline basis matrix. CSR format is preferred. - penalty : numpy.ndarray, shape (D, N) - The finite difference penalty matrix, in LAPACK's lower banded format (see - :func:`scipy.linalg.solveh_banded`) if `lower_only` is True or the full banded - format (see :func:`scipy.linalg.solve_banded`) if `lower_only` is False. - knots : numpy.ndarray, shape (K,) - The array of knots for the spline. Should be padded on each end with - `spline_degree` extra knots. - spline_degree : int - The degree of the spline. - rhs_extra : float or numpy.ndarray, shape (N,), optional - If supplied, `rhs_extra` will be added to the right hand side (``B.T @ W @ y``) - of the equation before solving. Default is None, which adds nothing. - lower_only : boolean, optional - If True (default), will include only the lower non-zero diagonals of - ``B.T @ W @ B`` and use :func:`scipy.linalg.solveh_banded` to solve the equation. - If False, will use all of the non-zero diagonals and use - :func:`scipy.linalg.solve_banded` for solving. `penalty` is not modified, so it - must be in the correct lower or full format before passing to this function. - - Returns - ------- - coeffs : numpy.ndarray, shape (K - `spline_degree` - 1,) - The coefficients for the spline. To calculate the spline, do ``basis @ coeffs``. - - Raises - ------ - ValueError - Raised if `penalty` and the calculated `basis.T @ W @ basis` have different number - of columns. - - Notes - ----- - Most checks on the inputs are skipped since this is an internal function and the - proper steps are assumed to be done. For more proper error handling in the inputs, - see :func:`scipy.interpolate.make_lsq_spline`. - - """ - use_backup = True - num_bases = basis.shape[1] - # prefer numba version since it directly uses the basis - if _HAS_NUMBA: - # the spline basis must explicitly be created such that the csr matrix's data - # attribute is not missing any zeros; it is correct for all the internal basis - # creation functions used, but need to ensure just in case something ever changes; - # could guess if missing_values==2 that the first and last basis functions are - # missing a near-zero value, but safer to just move to other options - basis_data = basis.tocsr().data - missing_values = len(y) * (spline_degree + 1) - len(basis_data) - if not missing_values: - # TODO if using the numba version, does fortran ordering speed up the calc? or - # can ab just be c ordered? - - # create ab and rhs arrays outside of numba function since numba's implementation - # of np.zeros is slower than numpy's (https://github.com/numba/numba/issues/7259) - ab = np.zeros((spline_degree + 1, num_bases), order='F') - rhs = np.zeros(num_bases) - _numba_btb_bty(x, knots, spline_degree, y, weights, ab, rhs, basis_data) - # TODO can probably make the full matrix directly within the numba - # btb calculation - if not lower_only: - ab = _lower_to_full(ab) - use_backup = False - - if use_backup and _scipy_btb_bty is not None: - ab = np.zeros((spline_degree + 1, num_bases), order='F') - rhs = np.zeros((num_bases, 1), order='F') - _scipy_btb_bty(x, knots, spline_degree, y.reshape(-1, 1), np.sqrt(weights), ab, rhs) - rhs = rhs.reshape(-1) - if not lower_only: - ab = _lower_to_full(ab) - use_backup = False - - if use_backup: - # worst case scenario; have to convert weights to a sparse diagonal matrix, - # do B.T @ W @ B, and convert back to lower banded - len_y = len(y) - full_matrix = basis.T @ spdiags(weights, 0, len_y, len_y, 'csr') @ basis - rhs = basis.T @ (weights * y) - ab = full_matrix.todia().data[::-1] - # take only the lower diagonals of the symmetric ab; cannot just do - # ab[spline_degree:] since some diagonals become fully 0 and are truncated from - # the data attribute, so have to calculate the number of bands first - if lower_only: - ab = ab[len(ab) // 2:] - - lhs = _add_diagonals(ab, penalty, lower_only) - if rhs_extra is not None: - rhs = rhs + rhs_extra - - if lower_only: - coeffs = solveh_banded( - lhs, rhs, overwrite_ab=True, overwrite_b=True, lower=True, - check_finite=False - ) - else: - bands = len(lhs) // 2 - coeffs = solve_banded( - (bands, bands), lhs, rhs, overwrite_ab=True, overwrite_b=True, - check_finite=False - ) - - return coeffs - - -def _basis_midpoints(knots, spline_degree): - """ - Calculates the midpoint x-values of spline basis functions assuming evenly spaced knots. - - Parameters - ---------- - knots : numpy.ndarray - The spline knots. - spline_degree : int - The degree of the spline. - - Returns - ------- - points : numpy.ndarray - The midpoints of the spline basis functions. - - """ - if spline_degree % 2: - points = knots[1 + spline_degree // 2:len(knots) - (spline_degree - spline_degree // 2)] - else: - midpoints = 0.5 * (knots[1:] + knots[:-1]) - points = midpoints[spline_degree // 2: len(midpoints) - spline_degree // 2] - - return points - - -class PSpline(PenalizedSystem): - """ - A Penalized Spline, which penalizes the difference of the spline coefficients. - - Penalized splines (P-Splines) are solved with the following equation - ``(B.T @ W @ B + P) c = B.T @ W @ y`` where `c` is the spline coefficients, `B` is the - spline basis, the weights are the diagonal of `W`, the penalty is `P`, and `y` is the - fit data. The penalty `P` is usually in the form ``lam * D.T @ D``, where `lam` is a - penalty factor and `D` is the matrix version of the finite difference operator. - - Attributes - ---------- - basis : scipy.sparse.csr.csr_matrix, shape (N, M) - The spline basis. Has a shape of (`N,` `M`), where `N` is the number of points - in `x`, and `M` is the number of basis functions (equal to ``K - spline_degree - 1`` - or equivalently ``num_knots + spline_degree - 1``). - coef : None or numpy.ndarray, shape (M,) - The spline coefficients. Is None if :meth:`.solve_pspline` has not been called - at least once. - knots : numpy.ndarray, shape (K,) - The knots for the spline. Has a shape of `K`, which is equal to - ``num_knots + 2 * spline_degree``. - num_knots : int - The number of internal knots (including the endpoints). The total number of knots - for the spline, `K`, is equal to ``num_knots + 2 * spline_degree``. - spline_degree : int - The degree of the spline (eg. a cubic spline would have a `spline_degree` of 3). - x : numpy.ndarray, shape (N,) - The x-values for the spline. - - References - ---------- - Eilers, P., et al. Twenty years of P-splines. SORT: Statistics and Operations Research - Transactions, 2015, 39(2), 149-186. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - def __init__(self, x, num_knots=100, spline_degree=3, check_finite=False, lam=1, - diff_order=2, allow_lower=True, reverse_diags=False): - """ - Initializes the penalized spline by calculating the basis and penalty. - - Parameters - ---------- - x : array-like, shape (N,) - The x-values for the spline. - num_knots : int, optional - The number of internal knots for the spline, including the endpoints. - Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - check_finite : bool, optional - If True, will raise an error if any values in `x` are not finite. Default - is False, which skips the check. - lam : float, optional - The penalty factor applied to the difference matrix. Larger values produce - smoother results. Must be greater than 0. Default is 1. - diff_order : int, optional - The difference order of the penalty. Default is 2 (second order difference). - allow_lower : bool, optional - If True (default), will allow only using the lower bands of the penalty matrix, - which allows using :func:`scipy.linalg.solveh_banded` instead of the slightly - slower :func:`scipy.linalg.solve_banded`. - reverse_diags : {False, True, None}, optional - If True, will reverse the order of the diagonals of the squared difference - matrix. If False (default), will never reverse the diagonals. If None, will - only reverse the diagonals if using pentapy's solver (which is set to False - for PSpline). - - Raises - ------ - ValueError - Raised if `spline_degree` is less than 0 or if `diff_order` is less than 1 - or greater than or equal to the number of spline basis functions - (``num_knots + spline_degree - 1``). - - """ - if spline_degree < 0: - raise ValueError('spline degree must be >= 0') - elif diff_order < 1: - raise ValueError( - 'the difference order must be > 0 for a penalized spline' - ) - - self.x = _check_array( - x, dtype=float, order='C', check_finite=check_finite, ensure_1d=True - ) - self._x_len = len(x) - self.knots = _spline_knots(self.x, num_knots, spline_degree, True) - self.spline_degree = spline_degree - self.num_knots = num_knots - self.basis = _spline_basis(self.x, self.knots, spline_degree) - self._num_bases = self.basis.shape[1] - self.coef = None - - if diff_order >= self._num_bases: - raise ValueError(( - 'the difference order must be less than the number of basis ' - 'functions, which is the number of knots + spline degree - 1' - )) - - super().__init__( - self._num_bases, lam, diff_order, allow_lower, reverse_diags, - allow_pentapy=False, padding=spline_degree - diff_order - ) - - # if using the numba B.T @ W @ B calculation, the spline basis must explicitly be - # created such that the csr matrix's data attribute is not missing any zeros; it is - # correct for all the internal basis creation functions used, but need to ensure - # just in case something ever changes - if _HAS_NUMBA and (self._x_len * (spline_degree + 1)) == len(self.basis.tocsr().data): - self._use_numba = True - else: - self._use_numba = False - - def same_basis(self, num_knots=100, spline_degree=3): - """ - Sees if the current basis is equivalent to the input number of knots of spline degree. - - Parameters - ---------- - num_knots : int, optional - The number of knots for the new spline. Default is 100. - spline_degree : int, optional - The degree of the new spline. Default is 3. - - Returns - ------- - bool - True if the input number of knots and spline degree are equivalent to the current - spline basis of the object. - - """ - return num_knots == self.num_knots and spline_degree == self.spline_degree - - def reset_penalty_diagonals(self, lam=1, diff_order=2, allow_lower=True, reverse_diags=None): - """ - Resets the penalty diagonals of the system and all of the attributes. - - Useful for reusing the penalty diagonals without having to recalculate the spline basis. - - Parameters - ---------- - lam : float, optional - The penalty factor applied to the difference matrix. Larger values produce - smoother results. Must be greater than 0. Default is 1. - diff_order : int, optional - The difference order of the penalty. Default is 2 (second order difference). - allow_lower : bool, optional - If True (default), will allow only using the lower bands of the penalty matrix, - which allows using :func:`scipy.linalg.solveh_banded` instead of the slightly - slower :func:`scipy.linalg.solve_banded`. - reverse_diags : {None, False, True}, optional - If True, will reverse the order of the diagonals of the squared difference - matrix. If False, will never reverse the diagonals. If None (default), will - only reverse the diagonals if using pentapy's solver. - - Notes - ----- - `allow_pentapy` is always set to False since the time needed to go from a lower to full - banded matrix and shifting the rows removes any speedup from using pentapy's solver. It - also reduces the complexity of setting up the equations. - - Adds padding to the penalty diagonals to accomodate the different shapes of the spline - basis and the penalty to speed up calculations when the two are added. - - """ - self.reset_diagonals( - lam=lam, diff_order=diff_order, allow_lower=allow_lower, reverse_diags=reverse_diags, - allow_pentapy=False, padding=self.spline_degree - diff_order - ) - - def solve_pspline(self, y, weights, penalty=None, rhs_extra=None): - """ - Solves the coefficients for a weighted penalized spline. - - Solves the linear equation ``(B.T @ W @ B + P) c = B.T @ W @ y`` for the spline - coefficients, `c`, given the spline basis, `B`, the weights (diagonal of `W`), the - penalty `P`, and `y`, and returns the resulting spline, ``B @ c``. Attempts to - calculate ``B.T @ W @ B`` and ``B.T @ W @ y`` as a banded system to speed up - the calculation. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values for fitting the spline. - weights : numpy.ndarray, shape (N,) - The weights for each y-value. - penalty : numpy.ndarray, shape (D, N) - The finite difference penalty matrix, in LAPACK's lower banded format (see - :func:`scipy.linalg.solveh_banded`) if `lower_only` is True or the full banded - format (see :func:`scipy.linalg.solve_banded`) if `lower_only` is False. - rhs_extra : float or numpy.ndarray, shape (N,), optional - If supplied, `rhs_extra` will be added to the right hand side (``B.T @ W @ y``) - of the equation before solving. Default is None, which adds nothing. - - Returns - ------- - numpy.ndarray, shape (N,) - The spline, corresponding to ``B @ c``, where `c` are the solved spline - coefficients and `B` is the spline basis. - - """ - use_backup = True - # prefer numba version since it directly uses the basis - if self._use_numba: - basis_data = self.basis.tocsr().data - # TODO if using the numba version, does fortran ordering speed up the calc? or - # can ab just be c ordered? - - # create ab and rhs arrays outside of numba function since numba's implementation - # of np.zeros is slower than numpy's (https://github.com/numba/numba/issues/7259) - ab = np.zeros((self.spline_degree + 1, self._num_bases), order='F') - rhs = np.zeros(self._num_bases) - _numba_btb_bty(self.x, self.knots, self.spline_degree, y, weights, ab, rhs, basis_data) - # TODO can probably make the full matrix directly within the numba - # btb calculation - if not self.lower: - ab = _lower_to_full(ab) - use_backup = False - - if use_backup and _scipy_btb_bty is not None: - ab = np.zeros((self.spline_degree + 1, self._num_bases), order='F') - rhs = np.zeros((self._num_bases, 1), order='F') - _scipy_btb_bty( - self.x, self.knots, self.spline_degree, y.reshape(-1, 1), np.sqrt(weights), ab, rhs - ) - rhs = rhs.reshape(-1) - if not self.lower: - ab = _lower_to_full(ab) - use_backup = False - - if use_backup: - # worst case scenario; have to convert weights to a sparse diagonal matrix, - # do B.T @ W @ B, and convert back to lower banded - full_matrix = ( - self.basis.T @ spdiags(weights, 0, self._x_len, self._x_len, 'csr') @ self.basis - ) - rhs = self.basis.T @ (weights * y) - ab = full_matrix.todia().data[::-1] - # take only the lower diagonals of the symmetric ab; cannot just do - # ab[spline_degree:] since some diagonals become fully 0 and are truncated from - # the data attribute, so have to calculate the number of bands first - if self.lower: - ab = ab[len(ab) // 2:] - - if penalty is None: - penalty = self.penalty - - lhs = _add_diagonals(ab, penalty, self.lower) - if rhs_extra is not None: - rhs = rhs + rhs_extra - - self.coef = self.solve( - lhs, rhs, overwrite_ab=True, overwrite_b=True, check_finite=False - ) - - return self.basis @ self.coef diff --git a/GSASII/pybaselines/_validation.py b/GSASII/pybaselines/_validation.py deleted file mode 100644 index 75cf968f1..000000000 --- a/GSASII/pybaselines/_validation.py +++ /dev/null @@ -1,388 +0,0 @@ -# -*- coding: utf-8 -*- -"""Code for validating inputs. - -Created on December 9, 2021 -@author: Donald Erb - -""" - -import numpy as np - - -def _check_scalar(data, desired_length, fill_scalar=False, **asarray_kwargs): - """ - Checks if the input is scalar and potentially coerces it to the desired length. - - Only intended for one dimensional data. - - Parameters - ---------- - data : array-like - Either a scalar value or an array. Array-like inputs with only 1 item will also - be considered scalar. - desired_length : int - If `data` is an array, `desired_length` is the length the array must have. If `data` - is a scalar and `fill_scalar` is True, then `desired_length` is the length of the output. - fill_scalar : bool, optional - If True and `data` is a scalar, then will output an array with a length of - `desired_length`. Default is False, which leaves scalar values unchanged. - **asarray_kwargs : dict - Additional keyword arguments to pass to :func:`numpy.asarray`. - - Returns - ------- - output : numpy.ndarray or numpy.number - The array of values or the single array scalar, depending on the input parameters. - is_scalar : bool - True if the input was a scalar value or had a length of 1; otherwise, is False. - - Raises - ------ - ValueError - Raised if `data` is not a scalar and its length is not equal to `desired_length`. - - """ - output = np.asarray(data, **asarray_kwargs) - ndim = output.ndim - if not ndim: - is_scalar = True - else: - if ndim > 1: # coerce to 1d shape - output = output.reshape(-1) - len_output = len(output) - if len_output == 1: - is_scalar = True - output = np.asarray(output[0], **asarray_kwargs) - else: - is_scalar = False - - if is_scalar: - if fill_scalar: - output = np.full(desired_length, output) - else: - # index with an empty tuple to get the single scalar while maintaining the numpy dtype - output = output[()] - elif len_output != desired_length: - raise ValueError(f'desired length was {desired_length} but instead got {len_output}') - - return output, is_scalar - - -def _check_scalar_variable(value, allow_zero=False, variable_name='lam', **asarray_kwargs): - """ - Ensures the input is a scalar value. - - Parameters - ---------- - value : float or array-like - The value to check. - allow_zero : bool, optional - If False (default), only allows `value` > 0. If True, allows `value` >= 0. - variable_name : str, optional - The name displayed if an error occurs. Default is 'lam'. - **asarray_kwargs : dict - Additional keyword arguments to pass to :func:`numpy.asarray`. - - Returns - ------- - output : float - The verified scalar value. - - Raises - ------ - ValueError - Raised if `value` is less than or equal to 0 if `allow_zero` is False or - less than 0 if `allow_zero` is True. - - """ - output = _check_scalar(value, 1, fill_scalar=False, **asarray_kwargs)[0] - if allow_zero: - operation = np.less - text = 'greater than or equal to' - else: - operation = np.less_equal - text = 'greater than' - if np.any(operation(output, 0)): - raise ValueError(f'{variable_name} must be {text} 0') - - # use an empty tuple to get the single scalar value - return output - - -def _check_array(array, dtype=None, order=None, check_finite=False, ensure_1d=True): - """ - Validates the shape and values of the input array and controls the output parameters. - - Parameters - ---------- - array : array-like - The input array to check. - dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing of `array`. - order : {None, 'C', 'F'}, optional - The order for the output array. Default is None, which will use the default array - ordering. Other valid options are 'C' for C ordering or 'F' for Fortran ordering. - check_finite : bool, optional - If True, will raise an error if any values in `array` are not finite. Default is False, - which skips the check. - ensure_1d : bool, optional - If True (default), will raise an error if the shape of `array` is not a one dimensional - array with shape (N,) or a two dimensional array with shape (N, 1) or (1, N). - - Returns - ------- - output : numpy.ndarray - The array after performing all validations. - - Raises - ------ - ValueError - Raised if `ensure_1d` is True and `array` does not have a shape of (N,) or - (N, 1) or (1, N). - - Notes - ----- - If `ensure_1d` is True and `array` has a shape of (N, 1) or (1, N), it is reshaped to - (N,) for better compatibility for all functions. - - """ - if check_finite: - array_func = np.asarray_chkfinite - else: - array_func = np.asarray - output = array_func(array, dtype=dtype, order=order) - if ensure_1d: - output = np.array(output, copy=False, ndmin=1) - dimensions = output.ndim - if dimensions == 2 and 1 in output.shape: - output = output.reshape(-1) - elif dimensions != 1: - raise ValueError('must be a one dimensional array') - - return output - - -def _check_sized_array(array, length, dtype=None, order=None, check_finite=False, - ensure_1d=True, axis=-1, name='weights'): - """ - Validates the input array and ensures its length is correct. - - Parameters - ---------- - array : array-like - The input array to check. - length : int - The length that the input should have on the specified `axis`. - dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing of `array`. - order : {None, 'C', 'F'}, optional - The order for the output array. Default is None, which will use the default array - ordering. Other valid options are 'C' for C ordering or 'F' for Fortran ordering. - check_finite : bool, optional - If True, will raise an error if any values if `array` are not finite. Default is False, - which skips the check. - ensure_1d : bool, optional - If True (default), will raise an error if the shape of `array` is not a one dimensional - array with shape (N,) or a two dimensional array with shape (N, 1) or (1, N). - axis : int, optional - The axis of the input on which to check its length. Default is -1. - name : str, optional - The name for the variable if an exception is raised. Default is 'weights'. - - Returns - ------- - output : numpy.ndarray - The array after performing all validations. - - Raises - ------ - ValueError - Raised if `array` does not match `length` on the given `axis`. - - """ - output = _check_array( - array, dtype=dtype, order=order, check_finite=check_finite, ensure_1d=ensure_1d - ) - if output.shape[axis] != length: - raise ValueError( - f'length mismatch for {name}; expected {length} but got {output.shape[axis]}' - ) - return output - - -def _yx_arrays(data, x_data=None, check_finite=False, dtype=None, order=None, ensure_1d=True, - axis=-1): - """ - Converts input data into numpy arrays and provides x data if none is given. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1. to 1. with N points. - check_finite : bool, optional - If True, will raise an error if any values if `array` are not finite. Default is False, - which skips the check. - dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing of `array`. - order : {None, 'C', 'F'}, optional - The order for the output array. Default is None, which will use the default array - ordering. Other valid options are 'C' for C ordering or 'F' for Fortran ordering. - ensure_1d : bool, optional - If True (default), will raise an error if the shape of `array` is not a one dimensional - array with shape (N,) or a two dimensional array with shape (N, 1) or (1, N). - axis : int, optional - The axis of the input on which to check its length. Default is -1. - - Returns - ------- - y : numpy.ndarray, shape (N,) - A numpy array of the y-values of the measured data. - x : numpy.ndarray, shape (N,) - A numpy array of the x-values of the measured data, or a created array. - - Notes - ----- - Does not change the scale/domain of the input `x_data` if it is given, only - converts it to an array. - - """ - y = _check_array( - data, dtype=dtype, order=order, check_finite=check_finite, ensure_1d=ensure_1d - ) - len_y = y.shape[axis] - if x_data is None: - x = np.linspace(-1, 1, len_y) - else: - x = _check_sized_array( - x_data, len_y, dtype=dtype, order=order, check_finite=check_finite, - ensure_1d=True, axis=0, name='x_data' - ) - - return y, x - - -def _check_lam(lam, allow_zero=False): - """ - Ensures the regularization parameter `lam` is a scalar greater than 0. - - Parameters - ---------- - lam : float or array-like - The regularization parameter, lambda, used in Whittaker smoothing and - penalized splines. - allow_zero : bool - If False (default), only allows `lam` values > 0. If True, allows `lam` >= 0. - - Returns - ------- - float - The scalar `lam` value. - - Raises - ------ - ValueError - Raised if `lam` is less than or equal to 0. - - Notes - ----- - Array-like `lam` values could be permitted, but they require using the full - banded penalty matrix. Many functions use only half of the penalty matrix due - to its symmetry; that symmetry is broken when using an array for `lam`, so allowing - an array `lam` would change how the system is solved. Further, array-like `lam` - values with large changes in scale cause some instability and/or discontinuities - when using Whittaker smoothing or penalized splines. Thus, it is easier and better - to only allow scalar `lam` values. - - TODO will maybe change this in the future to allow array-like `lam`, and the - solver will be determined based on that; however, until then, want to ensure users - don't unknowingly use an array-like `lam` when it doesn't work. - NOTE for future: if multiplying an array `lam` with the penalties in banded format, - do not reverse the order (ie. keep it like the output of sparse.dia.data), multiply - by the array, and then shift the rows based on the difference order (same procedure - as done for aspls). That will give the same output as - ``(diags(lam) @ D.T @ D).todia().data[::-1]``. - - """ - return _check_scalar_variable(lam, allow_zero) - - -def _check_half_window(half_window, allow_zero=False): - """ - Ensures the half-window is an integer and has an appropriate value. - - Parameters - ---------- - half_window : int, optional - The half-window used for the smoothing functions. Used - to pad the left and right edges of the data to reduce edge - effects. Default is 0, which provides no padding. - allow_zero : bool, optional - If True, allows `half_window` to be 0; otherwise, `half_window` - must be at least 1. Default is False. - - Returns - ------- - output_half_window : int - The verified half-window value. - - Raises - ------ - TypeError - Raised if the integer converted `half_window` is not equal to the input - `half_window`. - - """ - output_half_window = _check_scalar_variable( - half_window, allow_zero, 'half_window', dtype=np.intp - ) - if output_half_window != half_window: - raise TypeError('half_window must be an integer') - - return output_half_window - - -def _check_optional_array(data_size, array=None, dtype=None, order=None, check_finite=False, - copy_input=False, name='weights'): - """ - Validates the length of the input array or creates an array of ones if no input is given. - - Parameters - ---------- - data_size : int - The length that the input should have. - array : array-like, shape (`data_size`), optional - The array to validate. Default is None, which will create an array of ones with length - equal to `data_size`. - copy_input : bool, optional - If True, returns a copy of the input `array` if it is not None. Default is False. - dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing of `array`. - order : {None, 'C', 'F'}, optional - The order for the output array. Default is None, which will use the default array - ordering. Other valid options are 'C' for C ordering or 'F' for Fortran ordering. - check_finite : bool, optional - If True, will raise an error if any values if `array` are not finite. Default is False, - which skips the check. - name : str, optional - The name for the variable if an exception is raised. Default is 'weights'. - - Returns - ------- - output_array : numpy.ndarray, shape (`data_size`) - The validated array or the new ones array. - - """ - if array is None: - output_array = np.ones(data_size) - else: - output_array = _check_sized_array( - array, data_size, dtype=dtype, order=order, check_finite=check_finite, - ensure_1d=True, name=name - ) - if copy_input: - output_array = output_array.copy() - - return output_array diff --git a/GSASII/pybaselines/_weighting.py b/GSASII/pybaselines/_weighting.py deleted file mode 100644 index f8cb4cb43..000000000 --- a/GSASII/pybaselines/_weighting.py +++ /dev/null @@ -1,356 +0,0 @@ -# -*- coding: utf-8 -*- -"""Contains various weighting schemes used in pybaselines.""" - -import numpy as np -from scipy.special import expit - -from .utils import _MIN_FLOAT - - -def _asls(y, baseline, p): - """ - The weighting for the asymmetric least squares algorithm (asls). - - Also used by the improved asymmetric least squares algorithm (iasls). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - p : float - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - - References - ---------- - Eilers, P., et al. Baseline correction with asymmetric least squares smoothing. - Leiden University Medical Centre Report, 2005, 1(1). - - He, S., et al. Baseline correction for raman spectra using an improved - asymmetric least squares method, Analytical Methods, 2014, 6(12), 4402-4407. - - """ - mask = y > baseline - weights = p * mask + (1 - p) * (~mask) - return weights - - -def _safe_std(array, **kwargs): - """ - Calculates the standard deviation and protects against nan and 0. - - Used to prevent propogating nan or dividing by 0. - - Parameters - ---------- - array : numpy.ndarray - The array of values for calculating the standard deviation. - **kwargs - Additional keyword arguments to pass to :func:`numpy.std`. - - Returns - ------- - std : float - The standard deviation of the array, or `_MIN_FLOAT` if the - calculated standard deviation was 0 or if `array` was empty. - - Notes - ----- - Does not protect against the calculated standard deviation of a non-empty - array being nan because that would indicate that nan or inf was within the - array, which should not be protected. - - """ - # std would be 0 for an array with size of 1 and inf if size <= ddof; only - # internally use ddof=1, so the second condition is already covered - if array.size < 2: - std = _MIN_FLOAT - else: - std = array.std(**kwargs) - if std == 0: - std = _MIN_FLOAT - - return std - - -def _arpls(y, baseline): - """ - The weighting for asymmetrically reweighted penalized least squares smoothing (arpls). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - - References - ---------- - Baek, S.J., et al. Baseline correction using asymmetrically reweighted - penalized least squares smoothing. Analyst, 2015, 140, 250-257. - - """ - residual = y - baseline - neg_residual = residual[residual < 0] - std = _safe_std(neg_residual, ddof=1) # use dof=1 since sampling subset - # add a negative sign since expit performs 1/(1+exp(-input)) - weights = expit(-(2 / std) * (residual - (2 * std - np.mean(neg_residual)))) - return weights - - -def _drpls(y, baseline, iteration): - """ - The weighting for the doubly reweighted penalized least squares algorithm (drpls). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - iteration : int - The iteration number. Should be 1-based, such that the first iteration is 1 - instead of 0. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - - References - ---------- - Xu, D. et al. Baseline correction method based on doubly reweighted - penalized least squares, Applied Optics, 2019, 58, 3913-3920. - - """ - residual = y - baseline - neg_residual = residual[residual < 0] - std = _safe_std(neg_residual, ddof=1) # use dof=1 since only sampling a subset - inner = (np.exp(iteration) / std) * (residual - (2 * std - np.mean(neg_residual))) - weights = 0.5 * (1 - (inner / (1 + np.abs(inner)))) - return weights - - -def _iarpls(y, baseline, iteration): - """ - Weighting for improved asymmetrically reweighted penalized least squares smoothing (iarpls). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - iteration : int - The iteration number. Should be 1-based, such that the first iteration is 1 - instead of 0. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - - References - ---------- - Ye, J., et al. Baseline correction method based on improved asymmetrically - reweighted penalized least squares for Raman spectrum. Applied Optics, 2020, - 59, 10933-10943. - - """ - residual = y - baseline - std = _safe_std(residual[residual < 0], ddof=1) # dof=1 since sampling a subset - inner = (np.exp(iteration) / std) * (residual - 2 * std) - weights = 0.5 * (1 - (inner / np.sqrt(1 + inner**2))) - return weights - - -def _aspls(y, baseline): - """ - Weighting for the adaptive smoothness penalized least squares smoothing (aspls). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - residual : numpy.ndarray, shape (N,) - The residual, ``y - baseline``. - - Notes - ----- - The weighting uses an asymmetric coefficient (`k` in the asPLS paper) of 0.5 instead - of the 2 listed in the asPLS paper. pybaselines uses the factor of 0.5 since it - matches the results in Table 2 and Figure 5 of the asPLS paper closer than the - factor of 2 and fits noisy data much better. - - References - ---------- - Zhang, F., et al. Baseline correction for infrared spectra using adaptive smoothness - parameter penalized least squares method. Spectroscopy Letters, 2020, 53(3), 222-233. - - """ - residual = y - baseline - std = _safe_std(residual[residual < 0], ddof=1) # use dof=1 since sampling a subset - # add a negative sign since expit performs 1/(1+exp(-input)) - weights = expit(-(0.5 / std) * (residual - std)) - return weights, residual - - -def _psalsa(y, baseline, p, k, len_y): - """ - Weighting for the peaked signal's asymmetric least squares algorithm (psalsa). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - p : float - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. - k : float - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. - len_y : int - The length of `y`, `N`. Precomputed to avoid repeated calculations. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - - References - ---------- - Oller-Moreno, S., et al. Adaptive Asymmetric Least Squares baseline estimation - for analytical instruments. 2014 IEEE 11th International Multi-Conference on - Systems, Signals, and Devices, 2014, 1-5. - - """ - residual = y - baseline - # only use positive residual in exp to avoid exponential overflow warnings - # and accidently creating a weight of nan (inf * 0 = nan) - weights = np.full(len_y, 1 - p, dtype=float) - mask = residual > 0 - weights[mask] = p * np.exp(-residual[mask] / k) - return weights - - -def _derpsalsa(y, baseline, p, k, len_y, partial_weights): - """ - Weights for derivative peak-screening asymmetric least squares algorithm (derpsalsa). - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The measured data. - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - p : float - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. - k : float - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. - len_y : int - The length of `y`, `N`. Precomputed to avoid repeated calculations. - partial_weights : numpy.ndarray, shape (N,) - The weights associated with the first and second derivatives of the data. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The calculated weights. - - Notes - ----- - The reference is not clear as to how `p` and `1-p` are applied. An alternative could - be that `partial_weights` are multiplied only where the residual is greater than - 0 and that all other weights are `1-p`, but based on Figure 1c in the reference, the - total weights are never greater than `partial_weights`, so that must mean the non-peak - regions have a weight of `1-p` times `partial_weights` rather than just `1-p`; - both weighting systems give near identical results, so it is not a big deal. - - References - ---------- - Korepanov, V. Asymmetric least-squares baseline algorithm with peak screening for - automatic processing of the Raman spectra. Journal of Raman Spectroscopy. 2020, - 51(10), 2061-2065. - - """ - residual = y - baseline - # no need for caution since inner exponential is always negative, but still mask - # since it's faster than performing the square and exp on the full residual - weights = np.full(len_y, 1 - p, dtype=float) - mask = residual > 0 - weights[mask] = p * np.exp(-((residual[mask] / k)**2) / 2) - weights *= partial_weights - return weights - - -def _quantile(y, fit, quantile, eps=None): - r""" - An approximation of quantile loss. - - The loss is defined as :math:`\rho(r) / |r|`, where r is the residual, `y - fit`, - and the function :math:`\rho(r)` is `quantile` for `r` > 0 and 1 - `quantile` - for `r` < 0. Rather than using `|r|` as the denominator, which is non-differentiable - and causes issues when `r` = 0, the denominator is approximated as - :math:`\sqrt{r^2 + eps}` where `eps` is a small number. - - Parameters - ---------- - y : numpy.ndarray - The values of the raw data. - fit : numpy.ndarray - The fit values. - quantile : float - The quantile value. - eps : float, optional - A small value added to the square of `residual` to prevent dividing by 0. - Default is None, which uses `(1e-6 * max(abs(fit)))**2`. - - Returns - ------- - numpy.ndarray - The calculated loss, which can be used as weighting when performing iteratively - reweighted least squares (IRLS) - - References - ---------- - Schnabel, S., et al. Simultaneous estimation of quantile curves using quantile - sheets. AStA Advances in Statistical Analysis, 2013, 97, 77-87. - - """ - if eps is None: - # 1e-6 seems to work better than the 1e-4 in Schnabel, et al - eps = (np.abs(fit).max() * 1e-6)**2 - residual = y - fit - numerator = np.where(residual > 0, quantile, 1 - quantile) - # use max(eps, _MIN_FLOAT) to ensure that eps + 0 > 0 - denominator = np.sqrt(residual**2 + max(eps, _MIN_FLOAT)) # approximates abs(residual) - - return numerator / denominator diff --git a/GSASII/pybaselines/api.py b/GSASII/pybaselines/api.py deleted file mode 100644 index 025916f30..000000000 --- a/GSASII/pybaselines/api.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -"""The main entry point for using the object oriented api of pybaselines.""" - -from .classification import _Classification -from .misc import _Misc -from .morphological import _Morphological -from .optimizers import _Optimizers -from .polynomial import _Polynomial -from .smooth import _Smooth -from .spline import _Spline -from .whittaker import _Whittaker - - -class Baseline( - _Classification, _Misc, _Morphological, _Optimizers, _Polynomial, _Smooth, _Spline, _Whittaker -): - """ - A class for all baseline correction algorithms. - - Contains all available baseline correction algorithms in pybaselines as methods to - allow a single interface for easier usage. - - Parameters - ---------- - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 during the first function call with length equal to the - input data length. - check_finite : bool, optional - If True (default), will raise an error if any values in input data are not finite. - Setting to False will skip the check. Note that errors may occur if - `check_finite` is False and the input data contains non-finite values. - assume_sorted : bool, optional - If False (default), will sort the input `x_data` values. Otherwise, the - input is assumed to be sorted. Note that some functions may raise an error - if `x_data` is not sorted. - output_dtype : type or numpy.dtype, optional - The dtype to cast the output array. Default is None, which uses the typing - of the input data. - - Attributes - ---------- - poly_order : int - The last polynomial order used for a polynomial algorithm. Initially is -1, denoting - that no polynomial fitting has been performed. - pspline : pybaselines._spline_utils.PSpline or None - The PSpline object for setting up and solving penalized spline algorithms. Is None - if no penalized spline setup has been performed. - vandermonde : numpy.ndarray or None - The Vandermonde matrix for solving polynomial equations. Is None if no polynomial - setup has been performed. - whittaker_system : pybaselines._banded_utils.PenalizedSystem or None - The PenalizedSystem object for setting up and solving Whittaker-smoothing-based - algorithms. Is None if no Whittaker setup has been performed. - x : numpy.ndarray or None - The x-values for the object. If initialized with None, then `x` is initialized the - first function call to have the same length as the input `data` and has min and max - values of -1 and 1, respectively. - x_domain : numpy.ndarray - The minimum and maximum values of `x`. If `x_data` is None during initialization, then - set to numpy.ndarray([-1, 1]). - - """ diff --git a/GSASII/pybaselines/classification.py b/GSASII/pybaselines/classification.py deleted file mode 100644 index 0dc9257a8..000000000 --- a/GSASII/pybaselines/classification.py +++ /dev/null @@ -1,1575 +0,0 @@ -# -*- coding: utf-8 -*- -"""Techniques that rely on classifying peak and/or baseline segments for fitting baselines. - -Created on July 3, 2021 -@author: Donald Erb - -""" - -from math import ceil -import warnings - -import numpy as np -from scipy.ndimage import ( - binary_dilation, binary_erosion, binary_opening, grey_dilation, grey_erosion, uniform_filter1d -) -from scipy.optimize import curve_fit -from scipy.signal import cwt, ricker - -from ._algorithm_setup import _Algorithm, _class_wrapper -from ._compat import jit -from .utils import ( - _MIN_FLOAT, ParameterWarning, _convert_coef, _interp_inplace, gaussian, optimize_window, - pad_edges, relative_difference, whittaker_smooth -) - - -class _Classification(_Algorithm): - """A base class for all classification algorithms.""" - - @_Algorithm._register(sort_keys=('mask',)) - def golotvin(self, data, half_window=None, num_std=2.0, sections=32, smooth_half_window=None, - interp_half_window=5, weights=None, min_length=2, **pad_kwargs): - """ - Golotvin's method for identifying baseline regions. - - Divides the data into sections and takes the minimum standard deviation of all - sections as the noise standard deviation for the entire data. Then classifies any point - where the rolling max minus min is less than ``num_std * noise standard deviation`` - as belonging to the baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window to use for the rolling maximum and rolling minimum calculations. - Should be approximately equal to the full-width-at-half-maximum of the peaks or - features in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 3.0. - sections : int, optional - The number of sections to divide the input data into for finding the minimum - standard deviation. - smooth_half_window : int, optional - The half window to use for smoothing the interpolated baseline with a moving average. - Default is None, which will use `half_window`. Set to 0 to not smooth the baseline. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from the moving average smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - - References - ---------- - Golotvin, S., et al. Improved Baseline Recognition and Modeling of - FT NMR Spectra. Journal of Magnetic Resonance. 2000, 146, 122-125. - - """ - y, weight_array = self._setup_classification(data, weights) - if half_window is None: - # optimize_window(y) / 2 gives an "okay" estimate that at least scales - # with data size - half_window = ceil(optimize_window(y) / 2) - if smooth_half_window is None: - smooth_half_window = half_window - min_sigma = np.inf - for i in range(sections): - # use ddof=1 since sampling subsets of the data - min_sigma = min( - min_sigma, - np.std(y[i * self._len // sections:((i + 1) * self._len) // sections], ddof=1) - ) - - mask = ( - grey_dilation(y, 2 * half_window + 1) - grey_erosion(y, 2 * half_window + 1) - ) < num_std * min_sigma - mask = _refine_mask(mask, min_length) - np.logical_and(mask, weight_array, out=mask) - - rough_baseline = _averaged_interp(self.x, y, mask, interp_half_window) - baseline = uniform_filter1d( - pad_edges(rough_baseline, smooth_half_window, **pad_kwargs), - 2 * smooth_half_window + 1 - )[smooth_half_window:self._len + smooth_half_window] - - return baseline, {'mask': mask} - - @_Algorithm._register(sort_keys=('mask',)) - def dietrich(self, data, smooth_half_window=None, num_std=3.0, interp_half_window=5, - poly_order=5, max_iter=50, tol=1e-3, weights=None, return_coef=False, - min_length=2, **pad_kwargs): - """ - Dietrich's method for identifying baseline regions. - - Calculates the power spectrum of the data as the squared derivative of the data. - Then baseline points are identified by iteratively removing points where the mean - of the power spectrum is less than `num_std` times the standard deviation of the - power spectrum. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - smooth_half_window : int, optional - The half window to use for smoothing the input data with a moving average. - Default is None, which will use N / 256. Set to 0 to not smooth the data. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 3.0. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - poly_order : int, optional - The polynomial order for fitting the identified baseline. Default is 5. - max_iter : int, optional - The maximum number of iterations for fitting a polynomial to the identified - baseline. If `max_iter` is 0, the returned baseline will be just the linear - interpolation of the baseline segments. Default is 50. - tol : float, optional - The exit criteria for fitting a polynomial to the identified baseline points. - Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input `x_data` and return them in the params dictionary. - Default is False, since the conversion takes time. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - * 'coef': numpy.ndarray, shape (poly_order,) - Only if `return_coef` is True and `max_iter` is greater than 0. The array - of polynomial coefficients for the baseline, in increasing order. Can be - used to create a polynomial using numpy.polynomial.polynomial.Polynomial(). - * 'tol_history': numpy.ndarray - Only if `max_iter` is greater than 1. An array containing the calculated - tolerance values for each iteration. The length of the array is the number - of iterations completed. If the last value in the array is greater than - the input `tol` value, then the function did not converge. - - Notes - ----- - When choosing parameters, first choose a `smooth_half_window` that appropriately - smooths the data, and then reduce `num_std` until no peak regions are included in - the baseline. If no value of `num_std` works, change `smooth_half_window` and repeat. - - If `max_iter` is 0, the baseline is simply a linear interpolation of the identified - baseline points. Otherwise, a polynomial is iteratively fit through the baseline - points, and the interpolated sections are replaced each iteration with the polynomial - fit. - - References - ---------- - Dietrich, W., et al. Fast and Precise Automatic Baseline Correction of One- and - Two-Dimensional NMR Spectra. Journal of Magnetic Resonance. 1991, 91, 1-11. - - """ - y, weight_array = self._setup_classification(data, weights) - if smooth_half_window is None: - smooth_half_window = ceil(self._len / 256) - smooth_y = uniform_filter1d( - pad_edges(y, smooth_half_window, **pad_kwargs), - 2 * smooth_half_window + 1 - )[smooth_half_window:self._len + smooth_half_window] - power = np.gradient(smooth_y)**2 - mask = _refine_mask(_iter_threshold(power, num_std), min_length) - np.logical_and(mask, weight_array, out=mask) - rough_baseline = _averaged_interp(self.x, y, mask, interp_half_window) - - params = {'mask': mask} - baseline = rough_baseline - if max_iter > 0: - *_, pseudo_inverse = self._setup_polynomial( - y, poly_order=poly_order, calc_vander=True, calc_pinv=True - ) - old_coef = coef = pseudo_inverse @ rough_baseline - baseline = self.vandermonde @ coef - if max_iter > 1: - tol_history = np.empty(max_iter - 1) - for i in range(max_iter - 1): - rough_baseline[mask] = baseline[mask] - coef = pseudo_inverse @ rough_baseline - baseline = self.vandermonde @ coef - calc_difference = relative_difference(old_coef, coef) - tol_history[i] = calc_difference - if calc_difference < tol: - break - old_coef = coef - params['tol_history'] = tol_history[:i + 1] - - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('mask',)) - def std_distribution(self, data, half_window=None, interp_half_window=5, - fill_half_window=3, num_std=1.1, smooth_half_window=None, - weights=None, **pad_kwargs): - """ - Identifies baseline segments by analyzing the rolling standard deviation distribution. - - The rolling standard deviations are split into two distributions, with the smaller - distribution assigned to noise. Baseline points are then identified as any point - where the rolling standard deviation is less than a multiple of the median of the - noise's standard deviation distribution. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window to use for the rolling standard deviation calculation. Should - be approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - fill_half_window : int, optional - When a point is identified as a peak point, all points +- `fill_half_window` - are likewise set as peak points. Default is 3. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 1.1. - smooth_half_window : int, optional - The half window to use for smoothing the interpolated baseline with a moving average. - Default is None, which will use `half_window`. Set to 0 to not smooth the baseline. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from the moving average smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - - References - ---------- - Wang, K.C., et al. Distribution-Based Classification Method for Baseline - Correction of Metabolomic 1D Proton Nuclear Magnetic Resonance Spectra. - Analytical Chemistry. 2013, 85, 1231-1239. - - """ - y, weight_array = self._setup_classification(data, weights) - if half_window is None: - # optimize_window(y) / 2 gives an "okay" estimate that at least scales - # with data size - half_window = ceil(optimize_window(y) / 2) - if smooth_half_window is None: - smooth_half_window = half_window - - # use dof=1 since sampling a subset of the data - std = _padded_rolling_std(y, half_window, 1) - median = np.median(std) - median_2 = np.median(std[std < 2 * median]) # TODO make the 2 an input? - while median_2 / median < 0.999: # TODO make the 0.999 an input? - median = median_2 - median_2 = np.median(std[std < 2 * median]) - noise_std = median_2 - - # use ~ to convert from peak==1, baseline==0 to peak==0, baseline==1; if done before, - # would have to do ~binary_dilation(~mask) or binary_erosion(np.hstack((1, mask, 1))[1:-1] - mask = np.logical_and( - ~binary_dilation(std > num_std * noise_std, np.ones(2 * fill_half_window + 1)), - weight_array - ) - - rough_baseline = _averaged_interp(self.x, y, mask, interp_half_window) - - baseline = uniform_filter1d( - pad_edges(rough_baseline, smooth_half_window, **pad_kwargs), - 2 * smooth_half_window + 1 - )[smooth_half_window:self._len + smooth_half_window] - - return baseline, {'mask': mask} - - @_Algorithm._register(sort_keys=('mask',)) - def fastchrom(self, data, half_window=None, threshold=None, min_fwhm=None, - interp_half_window=5, smooth_half_window=None, weights=None, - max_iter=100, min_length=2, **pad_kwargs): - """ - Identifies baseline segments by thresholding the rolling standard deviation distribution. - - Baseline points are identified as any point where the rolling standard deviation - is less than the specified threshold. Peak regions are iteratively interpolated - until the baseline is below the data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window to use for the rolling standard deviation calculation. Should - be approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - threshold : float of Callable, optional - All points in the rolling standard deviation below `threshold` will be considered - as baseline. Higher values will assign more points as baseline. Default is None, - which will set the threshold as the 15th percentile of the rolling standard - deviation. If `threshold` is Callable, it should take the rolling standard deviation - as the only argument and output a float. - min_fwhm : int, optional - After creating the interpolated baseline, any region where the baseline - is greater than the data for `min_fwhm` consecutive points will have an additional - baseline point added and reinterpolated. Should be set to approximately the - index-based full-width-at-half-maximum of the smallest peak. Default is None, - which uses 2 * `half_window`. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - smooth_half_window : int, optional - The half window to use for smoothing the interpolated baseline with a moving average. - Default is None, which will use `half_window`. Set to 0 to not smooth the baseline. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - max_iter : int, optional - The maximum number of iterations to attempt to fill in regions where the baseline - is greater than the input data. Default is 100. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from the moving average smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - - Notes - ----- - Only covers the baseline correction from FastChrom, not its peak finding and peak - grouping capabilities. - - References - ---------- - Johnsen, L., et al. An automated method for baseline correction, peak finding - and peak grouping in chromatographic data. Analyst. 2013, 138, 3502-3511. - - """ - y, weight_array = self._setup_classification(data, weights) - if half_window is None: - # optimize_window(y) / 2 gives an "okay" estimate that at least scales - # with data size - half_window = ceil(optimize_window(y) / 2) - if smooth_half_window is None: - smooth_half_window = half_window - if min_fwhm is None: - min_fwhm = 2 * half_window - - # use dof=1 since sampling a subset of the data - std = _padded_rolling_std(y, half_window, 1) - if threshold is None: - # scales fairly well with y and gaurantees baseline segments are created; - # picked 15% since it seems to work better than 10% - threshold_val = np.percentile(std, 15) - elif callable(threshold): - threshold_val = threshold(std) - else: - threshold_val = threshold - - # reference did not mention removing single points, but do so anyway to - # be more thorough - mask = _refine_mask(std < threshold_val, min_length) - np.logical_and(mask, weight_array, out=mask) - rough_baseline = _averaged_interp(self.x, y, mask, interp_half_window) - - mask_sum = mask.sum() - # only try to fix peak regions if there actually are peak and baseline regions - if mask_sum and mask_sum != self._len: - peak_starts, peak_ends = _find_peak_segments(mask) - for _ in range(max_iter): - modified_baseline = False - for start, end in zip(peak_starts, peak_ends): - baseline_section = rough_baseline[start:end + 1] - data_section = y[start:end + 1] - # mask should be baseline_section > data_section, but use the - # inverse since _find_peak_segments looks for 0s, not 1s - section_mask = baseline_section < data_section - seg_starts, seg_ends = _find_peak_segments(section_mask) - if np.any(seg_ends - seg_starts > min_fwhm): - modified_baseline = True - # designate lowest point as baseline - # TODO should surrounding points also be classified as baseline? - mask[np.argmin(data_section - baseline_section) + start] = 1 - - if modified_baseline: - # TODO probably faster to just re-interpolate changed sections - rough_baseline = _averaged_interp(self.x, y, mask, interp_half_window) - else: - break - - # reference did not discuss smoothing, but include to be consistent with - # other classification functions - baseline = uniform_filter1d( - pad_edges(rough_baseline, smooth_half_window, **pad_kwargs), - 2 * smooth_half_window + 1 - )[smooth_half_window:self._len + smooth_half_window] - - return baseline, {'mask': mask} - - @_Algorithm._register(sort_keys=('mask',)) - def cwt_br(self, data, poly_order=5, scales=None, num_std=1.0, min_length=2, - max_iter=50, tol=1e-3, symmetric=False, weights=None, **pad_kwargs): - """ - Continuous wavelet transform baseline recognition (CWT-BR) algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 5. - scales : array-like, optional - The scales at which to perform the continuous wavelet transform. Default - is None, - num_std : float, optional - The number of standard deviations to include when thresholding. Default - is 1.0. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - max_iter : int, optional - The maximum number of iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - symmetric : bool, optional - When fitting the identified baseline points with a polynomial, if `symmetric` - is False (default), will add any point `i` as a baseline point where the fit - polynomial is greater than the input data for ``N/100`` consecutive points on both - sides of point `i`. If `symmetric` is True, then it means that both positive and - negative peaks exist and baseline points are not modified during the polynomial fitting. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution for the - continuous wavelet transform. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'best_scale' : scalar - The scale at which the Shannon entropy of the continuous wavelet transform - of the data is at a minimum. - - Notes - ----- - Uses the standard deviation for determining outliers during polynomial fitting rather - than the standard error as used in the reference since the number of standard errors - to include when thresholding varies with data size while the number of standard - deviations is independent of data size. - - References - ---------- - Bertinetto, C., et al. Automatic Baseline Recognition for the Correction of Large - Sets of Spectra Using Continuous Wavelet Transform and Iterative Fitting. Applied - Spectroscopy, 2014, 68(2), 155-164. - - """ - y, weight_array = self._setup_classification(data, weights) - self._setup_polynomial(y, weight_array, poly_order=poly_order, calc_vander=True) - # scale y between -1 and 1 so that the residual fit is more numerically stable - y_domain = np.polynomial.polyutils.getdomain(y) - y = np.polynomial.polyutils.mapdomain(y, y_domain, np.array([-1., 1.])) - if scales is None: - # avoid low scales since their cwt is fairly noisy - min_scale = max(2, self._len // 500) - max_scale = self._len // 4 - scales = range(min_scale, max_scale) - else: - scales = np.atleast_1d(scales).reshape(-1) - max_scale = scales.max() - - shannon_old = -np.inf - shannon_current = -np.inf - half_window = max_scale * 2 # TODO is x2 enough padding to prevent edge effects from cwt? - padded_y = pad_edges(y, half_window, **pad_kwargs) - for scale in scales: - wavelet_cwt = cwt(padded_y, ricker, [scale])[0, half_window:-half_window] - abs_wavelet = np.abs(wavelet_cwt) - inner = abs_wavelet / abs_wavelet.sum(axis=0) - # was not stated in the reference to use abs(wavelet) for the Shannon entropy, - # but otherwise the Shannon entropy vs wavelet scale curve does not look like - # Figure 2 in the reference; masking out non-positive values also gives an - # incorrect entropy curve - shannon_entropy = -np.sum(inner * np.log(inner + _MIN_FLOAT), 0) - if shannon_current < shannon_old and shannon_entropy > shannon_current: - break - shannon_old = shannon_current - shannon_current = shannon_entropy - - best_scale_ptp_multiple = 8 * abs(wavelet_cwt.max() - wavelet_cwt.min()) - num_bins = 200 - histogram, bin_edges = np.histogram(wavelet_cwt, num_bins) - bins = 0.5 * (bin_edges[1:] + bin_edges[:-1]) - fit_params = [histogram.max(), np.log10(0.2 * np.std(wavelet_cwt))] - # use 10**sigma so that sigma is not actually bounded - gaussian_fit = lambda x, height, sigma: gaussian(x, height, 0, 10**sigma) - # TODO should the number of iterations, the height cutoff for the histogram, - # and the exit tol be parameters? The number of iterations is never greater than - # 2 or 3, matching the reference. The height maybe should be since the masking - # depends on the histogram scale - dilation_structure = np.ones(5, bool) - for _ in range(10): - # dilate the mask to ensure at least five points are included for the fitting - fit_mask = binary_dilation(histogram > histogram.max() / 5, dilation_structure) - # histogram[~fit_mask] = 0 TODO use this instead? does it help fitting? - fit_params = curve_fit( - gaussian_fit, bins[fit_mask], histogram[fit_mask], fit_params, - check_finite=False - )[0] - sigma_opt = 10**fit_params[1] - - new_num_bins = ceil(best_scale_ptp_multiple / sigma_opt) - if relative_difference(num_bins, new_num_bins) < 0.05: - break - num_bins = new_num_bins - histogram, bin_edges = np.histogram(wavelet_cwt, num_bins) - bins = 0.5 * (bin_edges[1:] + bin_edges[:-1]) - - gaussian_mask = np.abs(bins) < 3 * sigma_opt - gaus_area = np.trapz(histogram[gaussian_mask], bins[gaussian_mask]) - num_sigma = 0.6 + 10 * ((np.trapz(histogram, bins) - gaus_area) / gaus_area) - - wavelet_mask = _refine_mask(abs_wavelet < num_sigma * sigma_opt, min_length) - np.logical_and(wavelet_mask, weight_array, out=wavelet_mask) - - check_window = np.ones(2 * (self._len // 200) + 1, bool) # TODO make window size a param? - baseline_old = y - mask = wavelet_mask.copy() - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - coef = np.linalg.lstsq(self.vandermonde[mask], y[mask], None)[0] - baseline = self.vandermonde @ coef - residual = y - baseline - mask[residual > num_std * np.std(residual)] = False - - # TODO is this necessary? It improves fits where the initial fit didn't - # include enough points, but ensures that negative peaks are not allowed; - # maybe make it a param called symmetric, like for mixture_model, and only - # do if not symmetric; also probably only need to do it the first iteration - # since after that the masking above will not remove negative residuals - coef = np.linalg.lstsq(self.vandermonde[mask], y[mask], None)[0] - baseline = self.vandermonde @ coef - - calc_difference = relative_difference(baseline_old, baseline) - tol_history[i] = calc_difference - if calc_difference < tol: - break - baseline_old = baseline - if not symmetric: - np.logical_or(mask, binary_erosion(y < baseline, check_window), out=mask) - - # TODO should include wavelet_mask in params; maybe called 'initial_mask'? - params = { - 'mask': mask, 'tol_history': tol_history[:i + 1], 'best_scale': scale - } - - baseline = np.polynomial.polyutils.mapdomain(baseline, np.array([-1., 1.]), y_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('mask', 'weights')) - def fabc(self, data, lam=1e6, scale=None, num_std=3.0, diff_order=2, min_length=2, - weights=None, weights_as_mask=False, **pad_kwargs): - """ - Fully automatic baseline correction (fabc). - - Similar to Dietrich's method, except that the derivative is estimated using a - continuous wavelet transform and the baseline is calculated using Whittaker - smoothing through the identified baseline points. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - scale : int, optional - The scale at which to calculate the continuous wavelet transform. Should be - approximately equal to the index-based full-width-at-half-maximum of the peaks - or features in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 3.0. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - weights_as_mask : bool, optional - If True, signifies that the input `weights` is the mask to use for fitting, - which skips the continuous wavelet calculation and just smooths the input data. - Default is False. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution for the - continuous wavelet transform. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - - Notes - ----- - The classification of baseline points is similar to :meth:`.dietrich`, except that - this method approximates the first derivative using a continous wavelet transform - with the Haar wavelet, which is more robust than the numerical derivative in - Dietrich's method. - - References - ---------- - Cobas, J., et al. A new general-purpose fully automatic baseline-correction - procedure for 1D and 2D NMR data. Journal of Magnetic Resonance, 2006, 183(1), - 145-151. - - """ - if weights_as_mask: - y, whittaker_weights = self._setup_whittaker(data, lam, diff_order, weights) - mask = whittaker_weights.astype(bool) - else: - y, weight_array = self._setup_classification(data, weights) - if scale is None: - # optimize_window(y) / 2 gives an "okay" estimate that at least scales - # with data size - scale = ceil(optimize_window(y) / 2) - # TODO is 2*scale enough padding to prevent edge effects from cwt? - half_window = scale * 2 - wavelet_cwt = cwt(pad_edges(y, half_window, **pad_kwargs), _haar, [scale]) - power = wavelet_cwt[0, half_window:-half_window]**2 - - mask = _refine_mask(_iter_threshold(power, num_std), min_length) - np.logical_and(mask, weight_array, out=mask) - whittaker_weights = mask.astype(float) - - # TODO should allow a p value so that weights are mask * (1-p) + (~mask) * p - # similar to mpls and mpspline? - baseline = whittaker_smooth( - y, lam=lam, diff_order=diff_order, weights=whittaker_weights, - check_finite=False, penalized_system=self.whittaker_system - ) - params = {'mask': mask, 'weights': whittaker_weights} - - return baseline, params - - -_classification_wrapper = _class_wrapper(_Classification) - - -def _refine_mask(mask, min_length=2): - """ - Removes small consecutive True values and lone False values from a boolean mask. - - Parameters - ---------- - mask : numpy.ndarray - The boolean array designating baseline points as True and peak points as False. - min_length : int, optional - The minimum consecutive length of True values needed for a section to remain True. - Lengths of True values less than `min_length` are converted to False. Default is - 2, which removes all lone True values. - - Returns - ------- - numpy.ndarray - The input mask after removing lone True and False values. - - Notes - ----- - Removes the lone True values first since True values designate the baseline. - That way, the approach is more conservative with assigning baseline points. - - Examples - -------- - >>> mask = np.array([1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1]) - >>> _remove_single_points(mask, 3).astype(int) - array([1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]) - >>> _remove_single_points(mask, 5).astype(int) - array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]) - - """ - min_length = max(min_length, 1) # has to be at least 1 for the binary opening - half_length = min_length // 2 - # do not use border_value=1 since that automatically makes the borders True and - # extends the True section by half_window on each side - output = binary_opening( - np.pad(mask, half_length, 'constant', constant_values=True), np.ones(min_length, bool) - )[half_length:len(mask) + half_length] - - # convert lone False values to True - np.logical_or(output, binary_erosion(output, [1, 0, 1]), out=output) - # TODO should there be an erosion step here, using another parameter (erode_hw)? - # that way, can control both the minimum length and then remove edges of baselines - # independently, allowing more control over the output mask - return output - - -def _find_peak_segments(mask): - """ - Identifies the peak starts and ends from a boolean mask. - - Parameters - ---------- - mask : numpy.ndarray - The boolean mask with peak points as 0 or False and baseline points - as 1 or True. - - Returns - ------- - peak_starts : numpy.ndarray - The array identifying the indices where each peak begins. - peak_ends : numpy.ndarray - The array identifying the indices where each peak ends. - - """ - extended_mask = np.concatenate(([True], mask, [True])) - peak_starts = extended_mask[1:-1] < extended_mask[:-2] - peak_starts = np.flatnonzero(peak_starts) - if len(peak_starts): - peak_starts[1 if peak_starts[0] == 0 else 0:] -= 1 - - peak_ends = extended_mask[1:-1] < extended_mask[2:] - peak_ends = np.flatnonzero(peak_ends) - if len(peak_ends): - peak_ends[:-1 if peak_ends[-1] == mask.shape[0] - 1 else None] += 1 - - return peak_starts, peak_ends - - -def _averaged_interp(x, y, mask, interp_half_window=0): - """ - Averages each anchor point and then interpolates between segments. - - Parameters - ---------- - x : numpy.ndarray - The x-values. - y : numpy.ndarray - The y-values. - mask : numpy.ndarray - A boolean array with 0 or False designating peak points and 1 or True - designating baseline points. - interp_half_window : int, optional - The half-window to use for averaging around the anchor points before interpolating. - Default is 0, which uses just the anchor point value. - - Returns - ------- - output : numpy.ndarray - A copy of the input `y` array with peak values in `mask` calulcated using linear - interpolation. - - """ - output = y.copy() - mask_sum = mask.sum() - if not mask_sum: # all points belong to peaks - # will just interpolate between first and last points - warnings.warn('there were no baseline points found', ParameterWarning) - elif mask_sum == mask.shape[0]: # all points belong to baseline - warnings.warn('there were no peak points found', ParameterWarning) - return output - - peak_starts, peak_ends = _find_peak_segments(mask) - num_y = y.shape[0] - for start, end in zip(peak_starts, peak_ends): - left_mean = np.mean( - y[max(0, start - interp_half_window):min(start + interp_half_window + 1, num_y)] - ) - right_mean = np.mean( - y[max(0, end - interp_half_window):min(end + interp_half_window + 1, num_y)] - ) - _interp_inplace(x[start:end + 1], output[start:end + 1], left_mean, right_mean) - - return output - - -@_classification_wrapper -def golotvin(data, x_data=None, half_window=None, num_std=2.0, sections=32, - smooth_half_window=None, interp_half_window=5, weights=None, min_length=2, - **pad_kwargs): - """ - Golotvin's method for identifying baseline regions. - - Divides the data into sections and takes the minimum standard deviation of all - sections as the noise standard deviation for the entire data. Then classifies any point - where the rolling max minus min is less than ``num_std * noise standard deviation`` - as belonging to the baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - half_window : int, optional - The half-window to use for the rolling maximum and rolling minimum calculations. - Should be approximately equal to the full-width-at-half-maximum of the peaks or - features in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 3.0. - sections : int, optional - The number of sections to divide the input data into for finding the minimum - standard deviation. - smooth_half_window : int, optional - The half window to use for smoothing the interpolated baseline with a moving average. - Default is None, which will use `half_window`. Set to 0 to not smooth the baseline. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from the moving average smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - - References - ---------- - Golotvin, S., et al. Improved Baseline Recognition and Modeling of - FT NMR Spectra. Journal of Magnetic Resonance. 2000, 146, 122-125. - - """ - - -def _iter_threshold(power, num_std=3.0): - """ - Iteratively thresholds a power spectrum based on the mean and standard deviation. - - Any values greater than the mean of the power spectrum plus a multiple of the - standard deviation are masked out to create a new power spectrum. The process - is performed iteratively until no further points are masked out. - - Parameters - ---------- - power : numpy.ndarray, shape (N,) - The power spectrum to threshold. - num_std : float, optional - The number of standard deviations to include when thresholding. Default is 3.0. - - Returns - ------- - mask : numpy.ndarray, shape (N,) - The boolean mask with True values where any point in the input power spectrum - was less than - - References - ---------- - Dietrich, W., et al. Fast and Precise Automatic Baseline Correction of One- and - Two-Dimensional NMR Spectra. Journal of Magnetic Resonance. 1991, 91, 1-11. - - """ - mask = power < np.mean(power) + num_std * np.std(power, ddof=1) - old_mask = np.ones_like(mask) - while not np.array_equal(mask, old_mask): - old_mask = mask - masked_power = power[mask] - if masked_power.size < 2: # need at least 2 points for std calculation - warnings.warn( - 'not enough baseline points found; "num_std" is likely too low', - ParameterWarning - ) - break - mask = power < np.mean(masked_power) + num_std * np.std(masked_power, ddof=1) - - return mask - - -@_classification_wrapper -def dietrich(data, x_data=None, smooth_half_window=None, num_std=3.0, - interp_half_window=5, poly_order=5, max_iter=50, tol=1e-3, weights=None, - return_coef=False, min_length=2, **pad_kwargs): - """ - Dietrich's method for identifying baseline regions. - - Calculates the power spectrum of the data as the squared derivative of the data. - Then baseline points are identified by iteratively removing points where the mean - of the power spectrum is less than `num_std` times the standard deviation of the - power spectrum. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - smooth_half_window : int, optional - The half window to use for smoothing the input data with a moving average. - Default is None, which will use N / 256. Set to 0 to not smooth the data. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 3.0. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - poly_order : int, optional - The polynomial order for fitting the identified baseline. Default is 5. - max_iter : int, optional - The maximum number of iterations for fitting a polynomial to the identified - baseline. If `max_iter` is 0, the returned baseline will be just the linear - interpolation of the baseline segments. Default is 50. - tol : float, optional - The exit criteria for fitting a polynomial to the identified baseline points. - Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input `x_data` and return them in the params dictionary. - Default is False, since the conversion takes time. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - * 'coef': numpy.ndarray, shape (poly_order,) - Only if `return_coef` is True and `max_iter` is greater than 0. The array - of polynomial coefficients for the baseline, in increasing order. Can be - used to create a polynomial using numpy.polynomial.polynomial.Polynomial(). - * 'tol_history': numpy.ndarray - Only if `max_iter` is greater than 1. An array containing the calculated - tolerance values for each iteration. The length of the array is the number - of iterations completed. If the last value in the array is greater than - the input `tol` value, then the function did not converge. - - Notes - ----- - When choosing parameters, first choose a `smooth_half_window` that appropriately - smooths the data, and then reduce `num_std` until no peak regions are included in - the baseline. If no value of `num_std` works, change `smooth_half_window` and repeat. - - If `max_iter` is 0, the baseline is simply a linear interpolation of the identified - baseline points. Otherwise, a polynomial is iteratively fit through the baseline - points, and the interpolated sections are replaced each iteration with the polynomial - fit. - - References - ---------- - Dietrich, W., et al. Fast and Precise Automatic Baseline Correction of One- and - Two-Dimensional NMR Spectra. Journal of Magnetic Resonance. 1991, 91, 1-11. - - """ - - -@jit(nopython=True, cache=True) -def _rolling_std(data, half_window, ddof): - """ - Computes the rolling standard deviation of an array. - - Parameters - ---------- - data : numpy.ndarray - The array for the calculation. Should be padded on the left and right - edges by `half_window`. - half_window : int - The half-window the rolling calculation. The full number of points for each - window is ``half_window * 2 + 1``. - ddof : int - The delta degrees of freedom for the calculation. Usually 0 (numpy default) or 1. - - Returns - ------- - numpy.ndarray - The array of the rolling standard deviation for each window. - - Notes - ----- - This implementation is a version of Welford's method [1]_, modified for a - fixed-length window [2]_. It is slightly modified from the version in [2]_ - in that it assumes the data is padded on the left and right. Other deviations - from [2]_ are noted within the function. - - References - ---------- - .. [1] https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance - .. [2] Chmielowiec, A. Algorithm for error-free determination of the variance of all - contiguous subsequences and fixed-length contiguous subsequences for a sequence - of industrial measurement data. Computational Statistics. 2021, 1-28. - - """ - window_size = half_window * 2 + 1 - num_y = data.shape[0] - squared_diff = np.zeros(num_y) - mean = data[0] - # fill the first window - for i in range(1, window_size): - val = data[i] - size_factor = i / (i + 1) - squared_diff[i] = squared_diff[i - 1] + 2 * size_factor * (val - mean)**2 - mean = mean * size_factor + val / (i + 1) - # at this point, mean == np.mean(data[:window_size]) - - # update squared_diff[half_window] with squared_diff[window_size - 1] / 2; if - # this isn't done, all values within [half_window:-half_window] in the output are - # off; no idea why... but it works - squared_diff[half_window] = squared_diff[window_size - 1] / 2 - for j in range(half_window + 1, num_y - half_window): - old_val = data[j - half_window - 1] - new_val = data[j + half_window] - val_diff = new_val - old_val # reference divided by window_size here - - new_mean = mean + val_diff / window_size - squared_diff[j] = squared_diff[j - 1] + val_diff * (old_val + new_val - mean - new_mean) - mean = new_mean - - # empty the last half-window - # TODO need to double-check this; not high priority since last half-window - # is discarded currently - size = window_size - for k in range(num_y - half_window + 1, num_y): - val = data[k] - size_factor = size / (size - 1) - squared_diff[k] = squared_diff[k - 1] + 2 * size_factor * (val - mean)**2 - mean = mean * size_factor + val / (size - 1) - size -= 1 - - return np.sqrt(squared_diff / (window_size - ddof)) - - -def _padded_rolling_std(data, half_window, ddof=0): - """ - Convenience function that pads data before performing rolling standard deviation calculation. - - Parameters - ---------- - data : array-like - The array for the calculation. - half_window : int - The half-window the rolling calculation. The full number of points for each - window is ``half_window * 2 + 1``. - ddof : int, optional - The delta degrees of freedom for the calculation. Default is 0. - - Returns - ------- - numpy.ndarray - The array of the rolling standard deviation for each window. - - Notes - ----- - Reflect the data since the standard deviation calculation requires noisy data to work. - - """ - padded_data = np.pad(data, half_window, 'reflect') - rolling_std = _rolling_std(padded_data, half_window, ddof)[half_window:-half_window] - return rolling_std - - -@_classification_wrapper -def std_distribution(data, x_data=None, half_window=None, interp_half_window=5, - fill_half_window=3, num_std=1.1, smooth_half_window=None, - weights=None, **pad_kwargs): - """ - Identifies baseline segments by analyzing the rolling standard deviation distribution. - - The rolling standard deviations are split into two distributions, with the smaller - distribution assigned to noise. Baseline points are then identified as any point - where the rolling standard deviation is less than a multiple of the median of the - noise's standard deviation distribution. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - half_window : int, optional - The half-window to use for the rolling standard deviation calculation. Should - be approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - fill_half_window : int, optional - When a point is identified as a peak point, all points +- `fill_half_window` - are likewise set as peak points. Default is 3. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 1.1. - smooth_half_window : int, optional - The half window to use for smoothing the interpolated baseline with a moving average. - Default is None, which will use `half_window`. Set to 0 to not smooth the baseline. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from the moving average smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - - References - ---------- - Wang, K.C., et al. Distribution-Based Classification Method for Baseline - Correction of Metabolomic 1D Proton Nuclear Magnetic Resonance Spectra. - Analytical Chemistry. 2013, 85, 1231-1239. - - """ - - -@_classification_wrapper -def fastchrom(data, x_data=None, half_window=None, threshold=None, min_fwhm=None, - interp_half_window=5, smooth_half_window=None, weights=None, - max_iter=100, min_length=2, **pad_kwargs): - """ - Identifies baseline segments by thresholding the rolling standard deviation distribution. - - Baseline points are identified as any point where the rolling standard deviation - is less than the specified threshold. Peak regions are iteratively interpolated - until the baseline is below the data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - half_window : int, optional - The half-window to use for the rolling standard deviation calculation. Should - be approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - threshold : float of Callable, optional - All points in the rolling standard deviation below `threshold` will be considered - as baseline. Higher values will assign more points as baseline. Default is None, - which will set the threshold as the 15th percentile of the rolling standard - deviation. If `threshold` is Callable, it should take the rolling standard deviation - as the only argument and output a float. - min_fwhm : int, optional - After creating the interpolated baseline, any region where the baseline - is greater than the data for `min_fwhm` consecutive points will have an additional - baseline point added and reinterpolated. Should be set to approximately the - index-based full-width-at-half-maximum of the smallest peak. Default is None, - which uses 2 * `half_window`. - interp_half_window : int, optional - When interpolating between baseline segments, will use the average of - ``data[i-interp_half_window:i+interp_half_window+1]``, where `i` is - the index of the peak start or end, to fit the linear segment. Default is 5. - smooth_half_window : int, optional - The half window to use for smoothing the interpolated baseline with a moving average. - Default is None, which will use `half_window`. Set to 0 to not smooth the baseline. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - max_iter : int, optional - The maximum number of iterations to attempt to fill in regions where the baseline - is greater than the input data. Default is 100. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from the moving average smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - - Notes - ----- - Only covers the baseline correction from FastChrom, not its peak finding and peak - grouping capabilities. - - References - ---------- - Johnsen, L., et al. An automated method for baseline correction, peak finding - and peak grouping in chromatographic data. Analyst. 2013, 138, 3502-3511. - - """ - - -@_classification_wrapper -def cwt_br(data, x_data=None, poly_order=5, scales=None, num_std=1.0, min_length=2, - max_iter=50, tol=1e-3, symmetric=False, weights=None, **pad_kwargs): - """ - Continuous wavelet transform baseline recognition (CWT-BR) algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 5. - scales : array-like, optional - The scales at which to perform the continuous wavelet transform. Default - is None, - num_std : float, optional - The number of standard deviations to include when thresholding. Default - is 1.0. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - max_iter : int, optional - The maximum number of iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - symmetric : bool, optional - When fitting the identified baseline points with a polynomial, if `symmetric` - is False (default), will add any point `i` as a baseline point where the fit - polynomial is greater than the input data for ``N/100`` consecutive points on both - sides of point `i`. If `symmetric` is True, then it means that both positive and - negative peaks exist and baseline points are not modified during the polynomial fitting. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution for the - continuous wavelet transform. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'best_scale' : scalar - The scale at which the Shannon entropy of the continuous wavelet transform - of the data is at a minimum. - - Notes - ----- - Uses the standard deviation for determining outliers during polynomial fitting rather - than the standard error as used in the reference since the number of standard errors - to include when thresholding varies with data size while the number of standard - deviations is independent of data size. - - References - ---------- - Bertinetto, C., et al. Automatic Baseline Recognition for the Correction of Large - Sets of Spectra Using Continuous Wavelet Transform and Iterative Fitting. Applied - Spectroscopy, 2014, 68(2), 155-164. - - """ - - -def _haar(num_points, scale=2): - """ - Creates a Haar wavelet. - - Parameters - ---------- - num_points : int - The number of points for the wavelet. Note that if `num_points` is odd - and `scale` is even, or if `num_points` is even and `scale` is odd, then - the length of the output wavelet will be `num_points` + 1 to ensure the - symmetry of the wavelet. - scale : int, optional - The scale at which the wavelet is evaluated. Default is 2. - - Returns - ------- - wavelet : numpy.ndarray - The Haar wavelet. - - Notes - ----- - This implementation is only designed to work for integer scales. - - Matches pywavelets's Haar implementation after applying patches from pywavelets - issue #365 and pywavelets pull request #580. - - References - ---------- - https://wikipedia.org/wiki/Haar_wavelet - - """ - # to maintain symmetry, even scales should have even windows and odd - # scales have odd windows - odd_scale = scale % 2 - odd_window = num_points % 2 - if (odd_scale and not odd_window) or (not odd_scale and odd_window): - num_points += 1 - # center at 0 rather than 1/2 to make calculation easier - # from [-scale/2 to 0), wavelet = 1; [0, scale/2), wavelet = -1 - x_vals = np.arange(num_points) - (num_points - 1) / 2 - wavelet = np.zeros(num_points) - if not odd_scale: - wavelet[(x_vals >= -scale / 2) & (x_vals < 0)] = 1 - wavelet[(x_vals < scale / 2) & (x_vals >= 0)] = -1 - else: - # set such that wavelet[x_vals == 0] = 0 - wavelet[(x_vals > -scale / 2) & (x_vals < 0)] = 1 - wavelet[(x_vals < scale / 2) & (x_vals > 0)] = -1 - - # the 1/sqrt(scale) is a normalization - return wavelet / (np.sqrt(scale)) - - -@_classification_wrapper -def fabc(data, lam=1e6, scale=None, num_std=3.0, diff_order=2, min_length=2, weights=None, - weights_as_mask=False, x_data=None, **pad_kwargs): - """ - Fully automatic baseline correction (fabc). - - Similar to Dietrich's method, except that the derivative is estimated using a - continuous wavelet transform and the baseline is calculated using Whittaker - smoothing through the identified baseline points. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - scale : int, optional - The scale at which to calculate the continuous wavelet transform. Should be - approximately equal to the index-based full-width-at-half-maximum of the peaks - or features in the data. Default is None, which will use half of the value from - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - num_std : float, optional - The number of standard deviations to include when thresholding. Higher values - will assign more points as baseline. Default is 3.0. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - min_length : int, optional - Any region of consecutive baseline points less than `min_length` is considered - to be a false positive and all points in the region are converted to peak points. - A higher `min_length` ensures less points are falsely assigned as baseline points. - Default is 2, which only removes lone baseline points. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - weights_as_mask : bool, optional - If True, signifies that the input `weights` is the mask to use for fitting, - which skips the continuous wavelet calculation and just smooths the input data. - Default is False. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution for the - continuous wavelet transform. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'mask': numpy.ndarray, shape (N,) - The boolean array designating baseline points as True and peak points - as False. - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - - Notes - ----- - The classification of baseline points is similar to :meth:`.dietrich`, except that - this method approximates the first derivative using a continous wavelet transform - with the Haar wavelet, which is more robust than the numerical derivative in - Dietrich's method. - - References - ---------- - Cobas, J., et al. A new general-purpose fully automatic baseline-correction - procedure for 1D and 2D NMR data. Journal of Magnetic Resonance, 2006, 183(1), - 145-151. - - """ diff --git a/GSASII/pybaselines/misc.py b/GSASII/pybaselines/misc.py deleted file mode 100644 index f8faeb9d8..000000000 --- a/GSASII/pybaselines/misc.py +++ /dev/null @@ -1,1261 +0,0 @@ -# -*- coding: utf-8 -*- -"""Miscellaneous functions for creating baselines. - -Created on April 2, 2021 -@author: Donald Erb - - -The function beads and all related functions were adapted from MATLAB code from -https://www.mathworks.com/matlabcentral/fileexchange/49974-beads-baseline-estimation-and-denoising-with-sparsity -(accessed June 28, 2021), which was licensed under the BSD-3-clause below. - -Copyright (c) 2018, Laurent Duval -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution -* Neither the name of IFP Energies nouvelles nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -The function _banded_dot_banded was adapted from bandmat (https://github.com/MattShannon/bandmat) -(accessed July 10, 2021), which was licensed under the BSD-3-clause below. - -Copyright 2013, 2014, 2015, 2016, 2017, 2018 Matt Shannon - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. - -""" - -import numpy as np -from scipy.interpolate import interp1d -from scipy.linalg import get_blas_funcs, solve_banded, solveh_banded -from scipy.ndimage import uniform_filter1d -from scipy.sparse import spdiags -from scipy.sparse.linalg import splu, spsolve - -from ._algorithm_setup import _Algorithm, _class_wrapper -from ._compat import _HAS_NUMBA, jit -from ._validation import _check_array, _check_lam -from .utils import _MIN_FLOAT, relative_difference - - -class _Misc(_Algorithm): - """A base class for all miscellaneous algorithms.""" - - @_Algorithm._register - def interp_pts(self, data=None, baseline_points=(), interp_method='linear'): - """ - Creates a baseline by interpolating through input points. - - Parameters - ---------- - data : array-like, optional - The y-values. Not used by this function, but input is allowed for consistency - with other functions. - baseline_points : array-like, shape (n, 2) - An array of ((x_1, y_1), (x_2, y_2), ..., (x_n, y_n)) values for - each point representing the baseline. - interp_method : str, optional - The method to use for interpolation. See :class:`scipy.interpolate.interp1d` - for all options. Default is 'linear', which connects each point with a - line segment. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The baseline array constructed from interpolating between - each input baseline point. - dict - An empty dictionary, just to match the output of all other algorithms. - - Raises - ------ - ValueError - Raised of `baseline_points` does not contain at least two values, signifying - one x-y point. - - Notes - ----- - This method is only suggested for use within user-interfaces. - - Regions of the baseline where `x_data` is less than the minimum x-value - or greater than the maximum x-value in `baseline_points` will be assigned - values of 0. - - """ - points = np.atleast_2d( - _check_array(baseline_points, check_finite=self._check_finite, ensure_1d=False) - ) - if points.shape[1] != 2: - raise ValueError( - 'baseline_points must have shape (number of x-y pairs, 2), but ' - f'instead is {points.shape}' - ) - interpolator = interp1d( - points[:, 0], points[:, 1], kind=interp_method, bounds_error=False, fill_value=0 - ) - baseline = interpolator(self.x) - - return baseline, {} - - @_Algorithm._register(sort_keys=('signal',)) - def beads(self, data, freq_cutoff=0.005, lam_0=1.0, lam_1=1.0, lam_2=1.0, asymmetry=6.0, - filter_type=1, cost_function=2, max_iter=50, tol=1e-2, eps_0=1e-6, - eps_1=1e-6, fit_parabola=True, smooth_half_window=None): - r""" - Baseline estimation and denoising with sparsity (BEADS). - - Decomposes the input data into baseline and pure, noise-free signal by modeling - the baseline as a low pass filter and by considering the signal and its derivatives - as sparse [1]_. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - freq_cutoff : float, optional - The cutoff frequency of the high pass filter, normalized such that - 0 < `freq_cutoff` < 0.5. Default is 0.005. - lam_0 : float, optional - The regularization parameter for the signal values. Default is 1.0. Higher - values give a higher penalty. - lam_1 : float, optional - The regularization parameter for the first derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - lam_2 : float, optional - The regularization parameter for the second derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - asymmetry : float, optional - A number greater than 0 that determines the weighting of negative values - compared to positive values in the cost function. Default is 6.0, which gives - negative values six times more impact on the cost function that positive values. - Set to 1 for a symmetric cost function, or a value less than 1 to weigh positive - values more. - filter_type : int, optional - An integer describing the high pass filter type. The order of the high pass - filter is ``2 * filter_type``. Default is 1 (second order filter). - cost_function : {2, 1, "l1_v1", "l1_v2"}, optional - An integer or string indicating which approximation of the l1 (absolute value) - penalty to use. 1 or "l1_v1" will use :math:`l(x) = \sqrt{x^2 + \text{eps_1}}` - and 2 (default) or "l1_v2" will use - :math:`l(x) = |x| - \text{eps_1}\log{(|x| + \text{eps_1})}`. - max_iter : int, optional - The maximum number of iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-2. - eps_0 : float, optional - The cutoff threshold between absolute loss and quadratic loss. Values in the signal - with absolute value less than `eps_0` will have quadratic loss. Default is 1e-6. - eps_1 : float, optional - A small, positive value used to prevent issues when the first or second order - derivatives are close to zero. Default is 1e-6. - fit_parabola : bool, optional - If True (default), will fit a parabola to the data and subtract it before - performing the beads fit as suggested in [2]_. This ensures the endpoints of - the fit data are close to 0, which is required by beads. If the data is already - close to 0 on both endpoints, set `fit_parabola` to False. - smooth_half_window : int, optional - The half-window to use for smoothing the derivatives of the data with a moving - average and full window size of `2 * smooth_half_window + 1`. Smoothing can - improve the convergence of the calculation, and make the calculation less sensitive - to small changes in `lam_1` and `lam_2`, as noted in the pybeads package [3]_. - Default is None, which will not perform any smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'signal': numpy.ndarray, shape (N,) - The pure signal portion of the input `data` without noise or the baseline. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Notes - ----- - The default `lam_0`, `lam_1`, and `lam_2` values are good starting points for a - dataset with 1000 points. Typically, smaller values are needed for larger datasets - and larger values for smaller datasets. - - When finding the best parameters for fitting, it is usually best to find the optimal - `freq_cutoff` for the noise in the data before adjusting any other parameters since - it has the largest effect [2]_. - - Raises - ------ - ValueError - Raised if `asymmetry` is less than 0. - - References - ---------- - .. [1] Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - .. [2] Navarro-Huerta, J.A., et al. Assisted baseline subtraction in complex chromatograms - using the BEADS algorithm. Journal of Chromatography A, 2017, 1507, 1-10. - .. [3] https://github.com/skotaro/pybeads. - - """ - # TODO maybe add the log-transform from Navarro-Huerta to improve fit for data spanning - # multiple scales, or at least mention in Notes section; also should add the function - # in Navarro-Huerta that helps choosing the best freq_cutoff for a dataset - y0 = self._setup_misc(data) - if isinstance(cost_function, str): # allow string to maintain parity with MATLAB version - cost_function = cost_function.lower() - use_v2_loss = {'l1_v1': False, 'l1_v2': True, 1: False, 2: True}[cost_function] - if asymmetry <= 0: - raise ValueError('asymmetry must be greater than 0') - - if fit_parabola: - parabola = _parabola(y0) - y = y0 - parabola - else: - y = y0 - # ensure that 0 + eps_0[1] > 0 to prevent numerical issues - eps_0 = max(eps_0, _MIN_FLOAT) - eps_1 = max(eps_1, _MIN_FLOAT) - if smooth_half_window is None: - smooth_half_window = 0 - - lam_0 = _check_lam(lam_0, True) - lam_1 = _check_lam(lam_1, True) - lam_2 = _check_lam(lam_2, True) - if _HAS_NUMBA: - baseline, params = _banded_beads( - y, freq_cutoff, lam_0, lam_1, lam_2, asymmetry, filter_type, use_v2_loss, - max_iter, tol, eps_0, eps_1, smooth_half_window - ) - else: - baseline, params = _sparse_beads( - y, freq_cutoff, lam_0, lam_1, lam_2, asymmetry, filter_type, use_v2_loss, - max_iter, tol, eps_0, eps_1, smooth_half_window - ) - - if fit_parabola: - baseline = baseline + parabola - - return baseline, params - - -_misc_wrapper = _class_wrapper(_Misc) - - -@_misc_wrapper -def interp_pts(x_data, baseline_points=(), interp_method='linear', data=None): - """ - Creates a baseline by interpolating through input points. - - Parameters - ---------- - x_data : array-like, shape (N,) - The x-values of the measured data. - baseline_points : array-like, shape (n, 2) - An array of ((x_1, y_1), (x_2, y_2), ..., (x_n, y_n)) values for - each point representing the baseline. - interp_method : str, optional - The method to use for interpolation. See :class:`scipy.interpolate.interp1d` - for all options. Default is 'linear', which connects each point with a - line segment. - data : array-like, optional - The y-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The baseline array constructed from interpolating between - each input baseline point. - dict - An empty dictionary, just to match the output of all other algorithms. - - Notes - ----- - This method is only suggested for use within user-interfaces. - - Regions of the baseline where `x_data` is less than the minimum x-value - or greater than the maximum x-value in `baseline_points` will be assigned - values of 0. - - """ - - -def _banded_dot_vector(ab, x, ab_lu, a_full_shape): - """ - Computes the dot product of the matrix `a` in banded format (`ab`) with the vector `x`. - - Parameters - ---------- - ab : array-like, shape (`n_lower` + `n_upper` + 1, N) - The banded matrix. - x : array-like, shape (N,) - The vector. - ab_lu : Container(int, int) - The number of lower (`n_lower`) and upper (`n_upper`) diagonals in `ab`. - a_full_shape : Container(int, int) - The number of rows and columns in the full `a` matrix. - - Returns - ------- - output : numpy.ndarray, shape (N,) - The dot product of `ab` and `x`. - - Notes - ----- - BLAS's symmetric version, 'sbmv', shows no significant speed increase, so just - uses the general 'gbmv' function to simplify the function. - - The function is faster if the input `ab` matrix is Fortran-ordered (has the - F_CONTIGUOUS numpy flag), since the underlying 'gbmv' BLAS function is - implemented in Fortran. - - """ - matrix = np.asarray(ab) - vector = np.asarray(x) - - gbmv = get_blas_funcs(['gbmv'], (matrix, vector))[0] - # gbmv computes y = alpha * a * x + beta * y where a is the banded matrix - # (in compressed form), x is the input vector, y is the output vector, and alpha - # and beta are scalar multipliers - output = gbmv( - m=a_full_shape[0], # number of rows of `a` matrix in full form - n=a_full_shape[1], # number of columns of `a` matrix in full form - kl=ab_lu[0], # sub-diagonals - ku=ab_lu[1], # super-diagonals - alpha=1.0, # alpha, required - a=matrix, # `a` matrix in compressed form - x=vector, # `x` vector - # trans=False, # tranpose a, optional; may allow later - ) - - return output - - -# adapted from bandmat (bandmat/tensor.pyx/dot_mm_plus_equals and dot_mm); see license above -@jit(nopython=True, cache=True) -def _numba_banded_dot_banded(a, b, c, a_lower, a_upper, b_lower, b_upper, c_upper, - diag_length, lower_bound): - """ - Calculates the matrix multiplication, ``C = A @ B``, with `a`, `b`, and `c` in banded forms. - - `a` and `b` must be square matrices in their full form or else this calculation - may be incorrect. - - Parameters - ---------- - a : array-like, shape (`a_lu[0]` + `a_lu[1]` + 1, N) - A banded matrix. - b : array-like, shape (`b_lu[0]` + `b_lu[1]` + 1, N) - The second banded matrix. - c : numpy.ndarray, shape (D, N) - The preallocated output matrix. Should be zeroed before passing to this function. - Will be modified inplace. - a_lower : int - The number of lower diagonals in `a`. - a_upper : int - The number of upper diagonals in `a`. - b_lower : int - The number of lower diagonals in `b`. - b_upper : int - The number of upper diagonals in `b`. - c_upper : int - The number of upper diagonals in `c`. - diag_length : int - The length of the diagonal in the full matrix. Equal to `N`. - lower_bound : int - The lowest diagonal to compute in `c`. Either 0 if `c` is symmetric and only - the upper diagonals need computed, or ``a_lower + b_lower`` to compute all bands. - - Returns - ------- - c : numpy.ndarray - The matrix multiplication of `a` and `b`. The number of lower diagonals is the - minimum of `a_lu[0]` + `b_lu[0]` and `a_full_shape[0]` - 1, the number of upper - diagonals is the minimum of `a_lu[1]` + `b_lu[1]` and `b_full_shape[1]` - 1, and - the total shape is (lower diagonals + upper diagonals + 1, N). - - Raises - ------ - ValueError - Raised if `a` and `b` do not have the same number of rows or if `a_full_shape[1]` - and `b_full_shape[0]` are not equal. - - Notes - ----- - Derived from bandmat (https://github.com/MattShannon/bandmat/blob/master/bandmat/tensor.pyx) - function `dot_mm`, licensed under the BSD-3-Clause. - - """ - # TODO need to revisit this later and use a different implementation than bandmat's - # so that a and b don't have to be square matrices; - # see https://github.com/JuliaMatrices/BandedMatrices.jl and - # https://www.netlib.org/utk/lsi/pcwLSI/text/node153.html for other implementations - - # TODO could be done in parallel, but does it speed up at all? - for o_c in range(-(a_upper + b_upper), lower_bound + 1): - for o_a in range(-min(a_upper, b_lower - o_c), min(a_lower, b_upper + o_c) + 1): - o_b = o_c - o_a - row_a = a_upper + o_a - row_b = b_upper + o_b - row_c = c_upper + o_c - d_a = 0 - d_b = -o_b - d_c = -o_b - for frame in range(max(0, -o_a, o_b), max(0, diag_length + min(0, -o_a, o_b))): - c[row_c, frame + d_c] += a[row_a, frame + d_a] * b[row_b, frame + d_b] - - return c - - -# adapted from bandmat (bandmat/tensor.pyx/dot_mm_plus_equals and dot_mm); see license above -def _banded_dot_banded(a, b, a_lu, b_lu, a_full_shape, b_full_shape, symmetric_output=False): - """ - Calculates the matrix multiplication, ``C = A @ B``, with `a` and `b` in banded forms. - - `a` and `b` must be square matrices in their full form or else this calculation - may be incorrect. - - Parameters - ---------- - a : array-like, shape (`a_lu[0]` + `a_lu[1]` + 1, N) - A banded matrix. - b : array-like, shape (`b_lu[0]` + `b_lu[1]` + 1, N) - The second banded matrix. - a_lu : Container(int, int) - A container of intergers designating the number lower and upper diagonals of `a`. - b_lu : Container(int, int) - A container of intergers designating the number lower and upper diagonals of `b`. - a_full_shape : Container(int, int) - A container of intergers designating the number of rows and columns in the full - matrix representation of `a`. - b_full_shape : Container(int, int) - A container of intergers designating the number of rows and columns in the full - matrix representation of `b`. - symmetric_output : bool, optional - Whether the output matrix is known to be symmetric. If True, will only calculate - the matrix multiplication for the upper bands, and the lower bands will be filled - in using the upper bands. Default is False. - - Returns - ------- - output : numpy.ndarray - The matrix multiplication of `a` and `b`. The number of lower diagonals is the - minimum of `a_lu[0]` + `b_lu[0]` and `a_full_shape[0]` - 1, the number of upper - diagonals is the minimum of `a_lu[1]` + `b_lu[1]` and `b_full_shape[1]` - 1, and - the total shape is (lower diagonals + upper diagonals + 1, N). - - Raises - ------ - ValueError - Raised if `a` and `b` do not have the same number of rows or if `a_full_shape[1]` - and `b_full_shape[0]` are not equal. - - Notes - ----- - Derived from bandmat (https://github.com/MattShannon/bandmat/blob/master/bandmat/tensor.pyx), - licensed under the BSD-3-Clause. - - """ - # TODO also need to check cases where a_lower + b_lower is > a_full_shape[0] - 1 - # and/or a_upper + b_upper is > b_full_shape[1] - 1 and see if the loops need to change at all - if a_full_shape[1] != b_full_shape[0]: - raise ValueError('dimension mismatch; a_full_shape[0] and b_full_shape[1] must be equal') - # a and b must both be square banded matrices - a = np.asarray(a) - b = np.asarray(b) - a_rows = a.shape[1] - b_rows = b.shape[1] - if a_rows != b_rows: - raise ValueError('a and b must have the same number of rows') - diag_length = a_rows # main diagonal length - a_lower, a_upper = a_lu - b_lower, b_upper = b_lu - c_upper = min(a_upper + b_upper, b_full_shape[1] - 1) - c_lower = min(a_lower + b_lower, a_full_shape[0] - 1) - if symmetric_output: - lower_bound = 0 # only fills upper bands - else: - lower_bound = a_lower + b_lower - # create output matrix outside of this function since numba's implementation - # of np.zeros is much slower than numpy's (https://github.com/numba/numba/issues/7259) - output = np.zeros((c_lower + c_upper + 1, diag_length)) - _numba_banded_dot_banded( - a, b, output, a_lower, a_upper, b_lower, b_upper, c_upper, diag_length, lower_bound - ) - - if symmetric_output: - for row in range(1, a_lower + b_lower + 1): - offset = a_lower + b_lower + 1 - row - # TODO should not use negative indices since empty 0 rows can sometimes - # be added; instead, work down from main diagonal; or should at least use - # the output's number of lower diagonals rather than a_l + b_l - output[-row, :-offset] = output[row - 1, offset:] - - return output - - -def _parabola(data): - """ - Makes a parabola that fits the input data at the two endpoints. - - Used in the beads calculation so that ``data - _parabola(data)`` is close - to 0 on the two endpoints, which gives better fits for beads. - - Parameters - ---------- - data : array-like, shape (N,) - The data values. - - Returns - ------- - numpy.ndarray, shape (N,) - The parabola fitting the two endpoints of the input `data`. - - Notes - ----- - Does not allow inputting x-values since beads does not work properly with unevenly - spaced data. Fitting custom x-values makes the parabola fit better when the data is - unevenly spaced, but the resulting beads fit is no better than when using the - default, evenly spaced x-values. - - Sets `A` as ``min(data)`` so that - ``max(data - _parabola(data)) - min(data - _parabola(data))`` is approximately the - same as ``max(data) - min(data)``. - - """ - y = np.asarray(data) - x = np.linspace(-1, 1, len(y)) - # use only the endpoints; when trying to use the mean of the last few values, the - # fit is usually not as good since beads expects the endpoints to be 0; may allow - # setting mean_width as a parameter later - A = y.min() - y1 = y[0] - A - y2 = y[-1] - A - # mean_width = 5 - # y1 = y[:mean_width].mean() - A - # y2 = y[-mean_width:].mean() - A - - # if parabola == p(x) = A + B * x + C * x**2, find coefficients such that - # p(x[0]==x1) = y[0] - min(y)==y1, p(x[-1]==x2) = y[-1] - min(y)==y2, and p(x_middle==0) = 0: - # A = min(y) - # C = (x1 * y2 - x2 * y1) / (x1 * x2**2 - x2 * x1**2) - # B = (y1 - C) / x1 - # then replace x1 with -1, x2 with 1, and simplify - C = (y2 + y1) / 2 - B = C - y1 - - return A + B * x + C * x**2 - - -# adapted from MATLAB beads version; see license above -def _high_pass_filter(data_size, freq_cutoff=0.005, filter_type=1, full_matrix=False): - """ - Creates the banded matrices A and B such that B(A^-1) is a high pass filter. - - Parameters - ---------- - data_size : int - The number of data points. - freq_cutoff : float, optional - The cutoff frequency of the high pass filter, normalized such that - 0 < `freq_cutoff` < 0.5. Default is 0.005. - filter_type : int, optional - An integer describing the high pass filter type. The order of the high pass - filter is ``2 * filter_type``. Default is 1 (second order filter). - full_matrix : bool, optional - If True, will return the full sparse diagonal matrices of A and B. If False - (default), will return the banded matrix versions of A and B. - - Raises - ------ - ValueError - Raised if `freq_cutoff` is not between 0 and 0.5 or if `filter_type` is - less than 1. - - References - ---------- - Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - - """ - if not 0 < freq_cutoff < 0.5: - raise ValueError('cutoff frequency must be between 0 and 0.5') - elif filter_type < 1: - raise ValueError('filter_type must be at least 1') - - # use finite differences instead of convolution to calculate a and b since - # it's faster - filter_order = 2 * filter_type - b = np.zeros(2 * filter_order + 1) - b[filter_order] = -1 if filter_type % 2 else 1 # same as (-1)**filter_type - for _ in range(filter_order): - b = b[:-1] - b[1:] - a = abs(b) - - cos_freq = np.cos(2 * np.pi * freq_cutoff) - t = ((1 - cos_freq) / max(1 + cos_freq, _MIN_FLOAT))**filter_type - - a_diags = np.repeat((b + a * t).reshape(1, -1), data_size, axis=0).T - b_diags = np.repeat(b.reshape(1, -1), data_size, axis=0).T - if full_matrix: - offsets = np.arange(-filter_type, filter_type + 1) - A = spdiags(a_diags, offsets, data_size, data_size, 'csr') - B = spdiags(b_diags, offsets, data_size, data_size, 'csr') - else: - # add zeros on edges to create the actual banded structure; - # creates same structure as diags(a[b]_diags, offsets).todia().data[::-1] - for i in range(filter_type): - offset = filter_type - i - a_diags[i][:offset] = 0 - a_diags[-i - 1][-offset:] = 0 - b_diags[i][:offset] = 0 - b_diags[-i - 1][-offset:] = 0 - A = a_diags - B = b_diags - - return A, B - - -# adapted from MATLAB beads version; see license above -def _beads_theta(x, asymmetry=6, eps_0=1e-6): - """ - The cost function for the pure signal `x`. - - Parameters - ---------- - x : numpy.ndarray - The array of the signal. - asymmetry : int, optional - The asymmetrical parameter that determines the weighting of negative values - compared to positive values in the cost function. Default is 6.0, which gives - negative values six times more impact on the cost function that positive values. - Set to 1 for a symmetric cost function, or a value less than 1 to weigh positive - values more. - eps_0 : float, optional - The cutoff threshold between absolute loss and quadratic loss. Values in `x` with - absolute value less than `eps_0` will have quadratic loss. Default is 1e-6. - - Returns - ------- - abs_x : numpy.ndarray - The absolute value of `x`. Used in other parts of the beads calculation, so - return it here to avoid having to calculate again. - large_mask : numpy.ndarray - The boolean array indicating which values in `abs_x` are greater than `eps_0`. - Used in other parts of the beads calculation, so return it here to avoid - having to calculate again. - theta : float - The summation of the cost function of `x`. - - Notes - ----- - The cost function is a modification of a Huber cost function, with the `asymmetry` - parameter dictating the cost of negative values compared to positive values. - - References - ---------- - Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - - """ - abs_x = np.abs(x) - large_mask = abs_x > eps_0 - small_x = x[~large_mask] - - theta = ( - x[(x > eps_0)].sum() - asymmetry * x[x < -eps_0].sum() - + ( - ((1 + asymmetry) / (4 * eps_0)) * small_x**2 + ((1 - asymmetry) / 2) * small_x - + eps_0 * (1 + asymmetry) / 4 - ).sum() - ) - return abs_x, large_mask, theta - - -# adapted from MATLAB beads version; see license above -def _beads_loss(x, use_v2=True, eps_1=1e-6): - """ - Approximates the absolute loss cost function. - - Parameters - ---------- - x : numpy.ndarray - The array of the absolute value of an n-order derivative of the signal. - use_v2 : bool, optional - If True (default), approximates the absolute loss using logarithms. If False, - uses the square root of the sqaured values. - eps_1 : float, optional - A small, positive value used to prevent issues when the first or second order - derivatives are close to zero. Default is 1e-6. - - Returns - ------- - loss : numpy.ndarray - The array of loss values. - - Notes - ----- - The input `x` should be the absolute value of the array. - - References - ---------- - Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - - - """ - if use_v2: - loss = x - eps_1 * np.log(x + eps_1) - else: - loss = np.sqrt(x**2 + eps_1) - - return loss - - -# adapted from MATLAB beads version; see license above -def _beads_weighting(x, use_v2=True, eps_1=1e-6): - """ - Approximates the weighting from absolute loss. - - Parameters - ---------- - x : numpy.ndarray - The array of the absolute value of an n-order derivative of the signal. - use_v2 : bool, optional - If True (default), approximates the absolute loss using logarithms. If False, - uses the square root of the sqaured values. - eps_1 : float, optional - A small, positive value used to prevent issues when the first or second order - derivatives are close to zero. Default is 1e-6. - - Returns - ------- - weight : numpy.ndarray - The weight array. - - Notes - ----- - The input `x` should be the absolute value of the array. - - The calculation is `f'(x)/x`, where `f'(x)` is the derivative of the function - `f(x)`, where `f(x)` is the loss function (calculated in _beads_loss). - - References - ---------- - Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - - """ - if use_v2: - weight = 1 / (x + eps_1) - else: - weight = 1 / np.sqrt(x**2 + eps_1) - - return weight - - -def _abs_diff(x, smooth_half_window=0): - """ - Computes the absolute values of the first and second derivatives of input data. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The array of the signal. - smooth_half_window : int, optional - The half-window for smoothing. Default is 0, which does no smoothing. - - Returns - ------- - d1_x : numpy.ndarray, shape (N - 1,) - The absolute value of the first derivative of `x`. - d2_x : numpy.ndarray, shape (N - 2,) - The absolute value of the second derivative of `x`. - - """ - d1_x = x[1:] - x[:-1] - if smooth_half_window > 0: - smooth_window = 2 * smooth_half_window + 1 - # TODO should mode be constant with cval=0 since derivative should be 0, or - # does reflect give better results? - # TODO should probably just smooth the first derivative and compute the second - # derivative from the smoothed value rather than smoothing both. - d2_x = np.abs(uniform_filter1d(d1_x[1:] - d1_x[:-1], smooth_window)) - uniform_filter1d(d1_x, smooth_window, output=d1_x) - else: - d2_x = np.abs(d1_x[1:] - d1_x[:-1]) - np.abs(d1_x, out=d1_x) - - return d1_x, d2_x - - -# adapted from MATLAB beads version; see license above -def _sparse_beads(y, freq_cutoff=0.005, lam_0=1.0, lam_1=1.0, lam_2=1.0, asymmetry=6, - filter_type=1, use_v2_loss=True, max_iter=50, tol=1e-2, eps_0=1e-6, - eps_1=1e-6, smooth_half_window=0): - """ - The beads algorithm using full, sparse matrices. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, with N data points. - freq_cutoff : float, optional - The cutoff frequency of the high pass filter, normalized such that - 0 < `freq_cutoff` < 0.5. Default is 0.005. - lam_0 : float, optional - The regularization parameter for the signal values. Default is 1.0. Higher - values give a higher penalty. - lam_1 : float, optional - The regularization parameter for the first derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - lam_2 : float, optional - The regularization parameter for the second derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - asymmetry : float, optional - The asymmetrical parameter that determines the weighting of negative values - compared to positive values in the cost function. Default is 6.0, which gives - negative values six times more impact on the cost function that positive values. - Set to 1 for a symmetric cost function, or a value less than 1 to weigh positive - values more. - filter_type : int, optional - An integer describing the high pass filter type. The order of the high pass - filter is ``2 * filter_type``. Default is 1 (second order filter). - use_v2_loss : bool, optional - If True (default), approximates the absolute loss using logarithms. If False, - uses the square root of the sqaured values. - max_iter : int, optional - The maximum number of iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-2. - eps_0 : float, optional - The cutoff threshold between absolute loss and quadratic loss. Values in the signal - with absolute value less than `eps_0` will have quadratic loss. Default is 1e-6. - eps_1 : float, optional - A small, positive value used to prevent issues when the first or second order - derivatives are close to zero. Default is 1e-6. - smooth_half_window : int, optional - The half-window to use for smoothing the derivatives of the data with a moving - average. Default is 0, which provides no smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'signal': numpy.ndarray, shape (N,) - The pure signal portion of the input `data` without noise or the baseline. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Notes - ----- - `A` and `B` matrices are symmetric, so their transposes are never used. - - References - ---------- - Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - - https://www.mathworks.com/matlabcentral/fileexchange/49974-beads-baseline-estimation- - and-denoising-with-sparsity. - - """ - num_y = y.shape[0] - d1_diags = np.zeros((5, num_y)) - d2_diags = np.zeros((5, num_y)) - offsets = np.arange(2, -3, -1) - A, B = _high_pass_filter(num_y, freq_cutoff, filter_type, True) - # factorize A since A is unchanged in the function and its factorization - # is used repeatedly; much faster than calling spsolve each time - A_factor = splu(A.tocsc(), permc_spec='NATURAL') - BTB = B * B - - x = y - d1_x, d2_x = _abs_diff(x, smooth_half_window) - # line 2 of Table 3 in beads paper - d = BTB.dot(A_factor.solve(y)) - A.dot(np.full(num_y, lam_0 * (1 - asymmetry) / 2)) - gamma = np.empty(num_y) - gamma_factor = lam_0 * (1 + asymmetry) / 2 # 2 * lam_0 * (1 + asymmetry) / 4 - cost_old = 0 - abs_x = np.abs(x) - big_x = abs_x > eps_0 - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - # calculate line 6 of Table 3 in beads paper using banded matrices rather - # than sparse matrices since it is much faster; Gamma + D.T * Lambda * D - - # row 1 and 3 instead of 0 and 2 to account for zeros on top and bottom - d1_diags[1][1:] = d1_diags[3][:-1] = -_beads_weighting(d1_x, use_v2_loss, eps_1) - d1_diags[2] = -(d1_diags[1] + d1_diags[3]) - - d2_diags[0][2:] = d2_diags[-1][:-2] = _beads_weighting(d2_x, use_v2_loss, eps_1) - d2_diags[1] = 2 * (d2_diags[0] - np.roll(d2_diags[0], -1, 0)) - 4 * d2_diags[0] - d2_diags[-2][:-1] = d2_diags[1][1:] - d2_diags[2] = -(d2_diags[0] + d2_diags[1] + d2_diags[-1] + d2_diags[-2]) - - d_diags = lam_1 * d1_diags + lam_2 * d2_diags - gamma[~big_x] = gamma_factor / eps_0 - gamma[big_x] = gamma_factor / abs_x[big_x] - d_diags[2] += gamma - - x = A.dot( - spsolve( - BTB + A.dot(spdiags(d_diags, offsets, num_y, num_y, 'csr').dot(A)), - d, 'NATURAL' - ) - ) - h = B.dot(A_factor.solve(y - x)) - d1_x, d2_x = _abs_diff(x, smooth_half_window) - abs_x, big_x, theta = _beads_theta(x, asymmetry, eps_0) - cost = ( - 0.5 * h.dot(h) - + lam_0 * theta - + lam_1 * _beads_loss(d1_x, use_v2_loss, eps_1).sum() - + lam_2 * _beads_loss(d2_x, use_v2_loss, eps_1).sum() - ) - cost_difference = relative_difference(cost_old, cost) - tol_history[i] = cost_difference - if cost_difference < tol: - break - cost_old = cost - - diff = y - x - baseline = diff - B.dot(A_factor.solve(diff)) - - return baseline, {'signal': x, 'tol_history': tol_history[:i + 1]} - - -# adapted from MATLAB beads version; see license above -def _banded_beads(y, freq_cutoff=0.005, lam_0=1.0, lam_1=1.0, lam_2=1.0, asymmetry=6, - filter_type=1, use_v2_loss=True, max_iter=50, tol=1e-2, eps_0=1e-6, - eps_1=1e-6, smooth_half_window=0): - """ - The beads algorithm using banded matrices rather than full, sparse matrices. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, with N data points. - freq_cutoff : float, optional - The cutoff frequency of the high pass filter, normalized such that - 0 < `freq_cutoff` < 0.5. Default is 0.005. - lam_0 : float, optional - The regularization parameter for the signal values. Default is 1.0. Higher - values give a higher penalty. - lam_1 : float, optional - The regularization parameter for the first derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - lam_2 : float, optional - The regularization parameter for the second derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - asymmetry : float, optional - The asymmetrical parameter that determines the weighting of negative values - compared to positive values in the cost function. Default is 6.0, which gives - negative values six times more impact on the cost function that positive values. - Set to 1 for a symmetric cost function, or a value less than 1 to weigh positive - values more. - filter_type : int, optional - An integer describing the high pass filter type. The order of the high pass - filter is ``2 * filter_type``. Default is 1 (second order filter). - use_v2_loss : bool, optional - If True (default), approximates the absolute loss using logarithms. If False, - uses the square root of the sqaured values. - max_iter : int, optional - The maximum number of iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-2. - eps_0 : float, optional - The cutoff threshold between absolute loss and quadratic loss. Values in the signal - with absolute value less than `eps_0` will have quadratic loss. Default is 1e-6. - eps_1 : float, optional - A small, positive value used to prevent issues when the first or second order - derivatives are close to zero. Default is 1e-6. - smooth_half_window : int, optional - The half-window to use for smoothing the derivatives of the data with a moving - average. Default is 0, which provides no smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'signal': numpy.ndarray, shape (N,) - The pure signal portion of the input `data` without noise or the baseline. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Notes - ----- - This function is ~75% faster than _sparse_beads (independent of data size) if Numba is - installed due to the faster banded solvers. If Numba is not installed, the calculation - of the dot product of banded matrices makes this calculation significantly slower than - the sparse implementation. - - It is no faster to pre-compute the Cholesky factorization of A_lower and use - that with scipy.linalg.cho_solve_banded compared to using A_lower in solveh_banded. - - `A` and `B` matrices are symmetric, so their transposes are never used. - - References - ---------- - Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - - https://www.mathworks.com/matlabcentral/fileexchange/49974-beads-baseline-estimation- - and-denoising-with-sparsity. - - """ - num_y = y.shape[0] - d1_diags = np.zeros((5, num_y)) - d2_diags = np.zeros((5, num_y)) - A, B = _high_pass_filter(num_y, freq_cutoff, filter_type, False) - # the number of lower and upper diagonals for both A and B - ab_lu = (filter_type, filter_type) - # the shape of A and B, and D.T*D matrices in their full forms rather than banded forms - full_shape = (num_y, num_y) - A_lower = A[filter_type:] - BTB = _banded_dot_banded(B, B, ab_lu, ab_lu, full_shape, full_shape, True) - # number of lower and upper diagonals of A.T * (D.T * D) * A - num_diags = (2 * filter_type + 2, 2 * filter_type + 2) - - # line 2 of Table 3 in beads paper - d = ( - _banded_dot_vector( - np.asfortranarray(BTB), - solveh_banded(A_lower, y, check_finite=False, lower=True), - (2 * filter_type, 2 * filter_type), full_shape - ) - - _banded_dot_vector( - A, np.full(num_y, lam_0 * (1 - asymmetry) / 2), ab_lu, full_shape - ) - ) - gamma = np.empty(num_y) - gamma_factor = lam_0 * (1 + asymmetry) / 2 # 2 * lam_0 * (1 + asymmetry) / 4 - x = y - d1_x, d2_x = _abs_diff(x, smooth_half_window) - cost_old = 0 - abs_x = np.abs(x) - big_x = abs_x > eps_0 - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - # calculate line 6 of Table 3 in beads paper using banded matrices rather - # than sparse matrices since it is much faster; Gamma + D.T * Lambda * D - - # row 1 and 3 instead of 0 and 2 to account for zeros on top and bottom - d1_diags[1][1:] = d1_diags[3][:-1] = -_beads_weighting(d1_x, use_v2_loss, eps_1) - d1_diags[2] = -(d1_diags[1] + d1_diags[3]) - - d2_diags[0][2:] = d2_diags[-1][:-2] = _beads_weighting(d2_x, use_v2_loss, eps_1) - d2_diags[1] = 2 * (d2_diags[0] - np.roll(d2_diags[0], -1, 0)) - 4 * d2_diags[0] - d2_diags[-2][:-1] = d2_diags[1][1:] - d2_diags[2] = -(d2_diags[0] + d2_diags[1] + d2_diags[-1] + d2_diags[-2]) - - d_diags = lam_1 * d1_diags + lam_2 * d2_diags - - gamma[~big_x] = gamma_factor / eps_0 - gamma[big_x] = gamma_factor / abs_x[big_x] - d_diags[2] += gamma - - temp = _banded_dot_banded( - _banded_dot_banded(A, d_diags, ab_lu, (2, 2), full_shape, full_shape), - A, (filter_type + 2, filter_type + 2), ab_lu, full_shape, full_shape, True - ) - temp[2:-2] += BTB - - # cannot use solveh_banded since temp is not guaranteed to be positive-definite - # and diagonally-dominant - x = _banded_dot_vector( - A, - solve_banded(num_diags, temp, d, overwrite_ab=True, check_finite=False), - ab_lu, full_shape - ) - - abs_x, big_x, theta = _beads_theta(x, asymmetry, eps_0) - d1_x, d2_x = _abs_diff(x, smooth_half_window) - h = _banded_dot_vector( - B, - solveh_banded(A_lower, y - x, check_finite=False, overwrite_b=True, lower=True), - ab_lu, full_shape - ) - cost = ( - 0.5 * h.dot(h) - + lam_0 * theta - + lam_1 * _beads_loss(d1_x, use_v2_loss, eps_1).sum() - + lam_2 * _beads_loss(d2_x, use_v2_loss, eps_1).sum() - ) - cost_difference = relative_difference(cost_old, cost) - tol_history[i] = cost_difference - if cost_difference < tol: - break - cost_old = cost - - diff = y - x - baseline = ( - diff - - _banded_dot_vector( - B, - solveh_banded(A_lower, diff, check_finite=False, overwrite_ab=True, lower=True), - ab_lu, full_shape - ) - ) - - return baseline, {'signal': x, 'tol_history': tol_history[:i + 1]} - - -@_misc_wrapper -def beads(data, freq_cutoff=0.005, lam_0=1.0, lam_1=1.0, lam_2=1.0, asymmetry=6.0, - filter_type=1, cost_function=2, max_iter=50, tol=1e-2, eps_0=1e-6, - eps_1=1e-6, fit_parabola=True, smooth_half_window=None, x_data=None): - r""" - Baseline estimation and denoising with sparsity (BEADS). - - Decomposes the input data into baseline and pure, noise-free signal by modeling - the baseline as a low pass filter and by considering the signal and its derivatives - as sparse [1]_. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - freq_cutoff : float, optional - The cutoff frequency of the high pass filter, normalized such that - 0 < `freq_cutoff` < 0.5. Default is 0.005. - lam_0 : float, optional - The regularization parameter for the signal values. Default is 1.0. Higher - values give a higher penalty. - lam_1 : float, optional - The regularization parameter for the first derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - lam_2 : float, optional - The regularization parameter for the second derivative of the signal. Default - is 1.0. Higher values give a higher penalty. - asymmetry : float, optional - A number greater than 0 that determines the weighting of negative values - compared to positive values in the cost function. Default is 6.0, which gives - negative values six times more impact on the cost function that positive values. - Set to 1 for a symmetric cost function, or a value less than 1 to weigh positive - values more. - filter_type : int, optional - An integer describing the high pass filter type. The order of the high pass - filter is ``2 * filter_type``. Default is 1 (second order filter). - cost_function : {2, 1, "l1_v1", "l1_v2"}, optional - An integer or string indicating which approximation of the l1 (absolute value) - penalty to use. 1 or "l1_v1" will use :math:`l(x) = \sqrt{x^2 + \text{eps_1}}` - and 2 (default) or "l1_v2" will use - :math:`l(x) = |x| - \text{eps_1}\log{(|x| + \text{eps_1})}`. - max_iter : int, optional - The maximum number of iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-2. - eps_0 : float, optional - The cutoff threshold between absolute loss and quadratic loss. Values in the signal - with absolute value less than `eps_0` will have quadratic loss. Default is 1e-6. - eps_1 : float, optional - A small, positive value used to prevent issues when the first or second order - derivatives are close to zero. Default is 1e-6. - fit_parabola : bool, optional - If True (default), will fit a parabola to the data and subtract it before - performing the beads fit as suggested in [2]_. This ensures the endpoints of - the fit data are close to 0, which is required by beads. If the data is already - close to 0 on both endpoints, set `fit_parabola` to False. - smooth_half_window : int, optional - The half-window to use for smoothing the derivatives of the data with a moving - average and full window size of `2 * smooth_half_window + 1`. Smoothing can - improve the convergence of the calculation, and make the calculation less sensitive - to small changes in `lam_1` and `lam_2`, as noted in the pybeads package [3]_. - Default is None, which will not perform any smoothing. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'signal': numpy.ndarray, shape (N,) - The pure signal portion of the input `data` without noise or the baseline. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Notes - ----- - The default `lam_0`, `lam_1`, and `lam_2` values are good starting points for a - dataset with 1000 points. Typically, smaller values are needed for larger datasets - and larger values for smaller datasets. - - When finding the best parameters for fitting, it is usually best to find the optimal - `freq_cutoff` for the noise in the data before adjusting any other parameters since - it has the largest effect [2]_. - - Raises - ------ - ValueError - Raised if `asymmetry` is less than 0. - - References - ---------- - .. [1] Ning, X., et al. Chromatogram baseline estimation and denoising using sparsity - (BEADS). Chemometrics and Intelligent Laboratory Systems, 2014, 139, 156-167. - .. [2] Navarro-Huerta, J.A., et al. Assisted baseline subtraction in complex chromatograms - using the BEADS algorithm. Journal of Chromatography A, 2017, 1507, 1-10. - .. [3] https://github.com/skotaro/pybeads. - - """ diff --git a/GSASII/pybaselines/morphological.py b/GSASII/pybaselines/morphological.py deleted file mode 100644 index 621423cee..000000000 --- a/GSASII/pybaselines/morphological.py +++ /dev/null @@ -1,1678 +0,0 @@ -# -*- coding: utf-8 -*- -"""Morphological techniques for fitting baselines to experimental data. - -Created on March 5, 2021 -@author: Donald Erb - -""" - -import numpy as np -from scipy.ndimage import grey_closing, grey_dilation, grey_erosion, grey_opening, uniform_filter1d - -from ._algorithm_setup import _Algorithm, _class_wrapper, _sort_array -from ._validation import _check_lam -from .utils import ( - _mollifier_kernel, pad_edges, padded_convolve, relative_difference, whittaker_smooth -) - - -class _Morphological(_Algorithm): - """A base class for all morphological algorithms.""" - - @_Algorithm._register(sort_keys=('weights',)) - def mpls(self, data, half_window=None, lam=1e6, p=0.0, diff_order=2, tol=1e-3, max_iter=50, - weights=None, **window_kwargs): - """ - The Morphological penalized least squares (MPLS) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Anchor points - identified by the procedure in [4]_ are given a weight of `1 - p`, and all - other points have a weight of `p`. Default is 0.0. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the weights will be - calculated following the procedure in [4]_. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'half_window': int - The half window used for the morphological calculations. - - Raises - ------ - ValueError - Raised if p is not between 0 and 1. - - References - ---------- - .. [4] Li, Zhong, et al. Morphological weighted penalized least squares for - background correction. Analyst, 2013, 138, 4483-4492. - - """ - if not 0 <= p <= 1: - raise ValueError('p must be between 0 and 1') - - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - if weights is not None: - w = weights - else: - rough_baseline = grey_opening(y, [2 * half_wind + 1]) - diff = np.diff( - np.concatenate([rough_baseline[:1], rough_baseline, rough_baseline[-1:]]) - ) - # diff == 0 means the point is on a flat segment, and diff != 0 means the - # adjacent point is not the same flat segment. The union of the two finds - # the endpoints of each segment, and np.flatnonzero converts the mask to - # indices; indices will always be even-sized. - indices = np.flatnonzero( - ((diff[1:] == 0) | (diff[:-1] == 0)) & ((diff[1:] != 0) | (diff[:-1] != 0)) - ) - w = np.full(y.shape[0], p) - # find the index of min(y) in the region between flat regions - for previous_segment, next_segment in zip(indices[1::2], indices[2::2]): - index = np.argmin(y[previous_segment:next_segment + 1]) + previous_segment - w[index] = 1 - p - - # have to invert the weight ordering the matching the original input y ordering - # since it will be sorted within _setup_whittaker - w = _sort_array(w, self._inverted_order) - - _, weight_array = self._setup_whittaker(y, lam, diff_order, w) - baseline = whittaker_smooth( - y, lam=lam, diff_order=diff_order, weights=weight_array, - check_finite=False, penalized_system=self.whittaker_system - ) - - params = {'weights': weight_array, 'half_window': half_wind} - return baseline, params - - @_Algorithm._register - def mor(self, data, half_window=None, **window_kwargs): - """ - A Morphological based (Mor) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - - References - ---------- - Perez-Pueyo, R., et al. Morphology-Based Automated Baseline Removal for - Raman Spectra of Artistic Pigments. Applied Spectroscopy, 2010, 64, 595-600. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - opening = grey_opening(y, [2 * half_wind + 1]) - baseline = np.minimum(opening, _avg_opening(y, half_wind, opening)) - - return baseline, {'half_window': half_wind} - - @_Algorithm._register - def imor(self, data, half_window=None, tol=1e-3, max_iter=200, **window_kwargs): - """ - An Improved Morphological based (IMor) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 200. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Dai, L., et al. An Automated Baseline Correction Method Based on Iterative - Morphological Operations. Applied Spectroscopy, 2018, 72(5), 731-739. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - baseline = y - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline_new = np.minimum(y, _avg_opening(baseline, half_wind)) - calc_difference = relative_difference(baseline, baseline_new) - tol_history[i] = calc_difference - if calc_difference < tol: - break - baseline = baseline_new - - params = {'half_window': half_wind, 'tol_history': tol_history[:i + 1]} - return baseline, params - - @_Algorithm._register - def amormol(self, data, half_window=None, tol=1e-3, max_iter=200, pad_kwargs=None, - **window_kwargs): - """ - Iteratively averaging morphological and mollified (aMorMol) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 200. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from convolution. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Chen, H., et al. An Adaptive and Fully Automated Baseline Correction - Method for Raman Spectroscopy Based on Morphological Operations and - Mollifications. Applied Spectroscopy, 2019, 73(3), 284-293. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - window_size = 2 * half_wind + 1 - kernel = _mollifier_kernel(window_size) - data_bounds = slice(window_size, -window_size) - - pad_kws = pad_kwargs if pad_kwargs is not None else {} - y = pad_edges(y, window_size, **pad_kws) - baseline = y - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline_old = baseline - baseline = padded_convolve( - np.minimum( - y, - 0.5 * ( - grey_closing(baseline, [window_size]) - + grey_opening(baseline, [window_size]) - ) - ), - kernel - ) - calc_difference = relative_difference(baseline_old[data_bounds], baseline[data_bounds]) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - params = {'half_window': half_wind, 'tol_history': tol_history[:i + 1]} - return baseline[data_bounds], params - - @_Algorithm._register - def mormol(self, data, half_window=None, tol=1e-3, max_iter=250, smooth_half_window=None, - pad_kwargs=None, **window_kwargs): - """ - Iterative morphological and mollified (MorMol) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 200. - smooth_half_window : int, optional - The half-window to use for smoothing the data before performing the - morphological operation. Default is None, which will use a value of 1, - which gives no smoothing. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from convolution. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Koch, M., et al. Iterative morphological and mollifier-based baseline - correction for Raman spectra. J Raman Spectroscopy, 2017, 48(2), 336-342. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - window_size = 2 * half_wind + 1 - kernel = _mollifier_kernel(window_size) - if smooth_half_window is None: - smooth_half_window = 1 - smooth_kernel = _mollifier_kernel(smooth_half_window) - data_bounds = slice(window_size, -window_size) - - pad_kws = pad_kwargs if pad_kwargs is not None else {} - y = pad_edges(y, window_size, **pad_kws) - baseline = np.zeros(y.shape[0]) - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline_old = baseline - y_smooth = padded_convolve(y - baseline, smooth_kernel) - baseline = baseline + padded_convolve(grey_erosion(y_smooth, window_size), kernel) - calc_difference = relative_difference( - baseline_old[data_bounds], baseline[data_bounds] - ) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - params = {'half_window': half_wind, 'tol_history': tol_history[:i + 1]} - return baseline[data_bounds], params - - @_Algorithm._register - def rolling_ball(self, data, half_window=None, smooth_half_window=None, - pad_kwargs=None, **window_kwargs): - """ - The rolling ball baseline algorithm. - - Applies a minimum and then maximum moving window, and subsequently smooths the - result, giving a baseline that resembles rolling a ball across the data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - smooth_half_window : int, optional - The half-window to use for smoothing the data after performing the - morphological operation. Default is None, which will use the same - value as used for the morphological operation. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from the moving average. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int or numpy.ndarray(int) - The half window or array of half windows used for the - morphological calculations. - - Notes - ----- - To use a changing window size for either the morphological or smoothing - operations, the half windows must be arrays. Otherwise, the size of the - rolling ball is assumed to be constant. - - References - ---------- - Kneen, M.A., et al. Algorithm for fitting XRF, SEM and PIXE X-ray spectra - backgrounds. Nuclear Instruments and Methods in Physics Research B, 1996, - 109, 209-213. - - Liland, K., et al. Optimal Choice of Baseline Correction for Multivariate - Calibration of Spectra. Applied Spectroscopy, 2010, 64(9), 1007-1016. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - if smooth_half_window is None: - smooth_half_window = half_wind - - rough_baseline = grey_opening(y, 2 * half_wind + 1) - pad_kws = pad_kwargs if pad_kwargs is not None else {} - baseline = uniform_filter1d( - pad_edges(rough_baseline, smooth_half_window, **pad_kws), - 2 * smooth_half_window + 1 - )[smooth_half_window:self._len + smooth_half_window] - - return baseline, {'half_window': half_wind} - - @_Algorithm._register - def mwmv(self, data, half_window=None, smooth_half_window=None, - pad_kwargs=None, **window_kwargs): - """ - Moving window minimum value (MWMV) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - smooth_half_window : int, optional - The half-window to use for smoothing the data after performing the - morphological operation. Default is None, which will use the same - value as used for the morphological operation. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from the moving average. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - - Notes - ----- - Performs poorly when baseline is rapidly changing. - - References - ---------- - Yaroshchyk, P., et al. Automatic correction of continuum background in Laser-induced - Breakdown Spectroscopy using a model-free algorithm. Spectrochimica Acta Part B, 2014, - 99, 138-149. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - if smooth_half_window is None: - smooth_half_window = half_wind - - rough_baseline = grey_erosion(y, 2 * half_wind + 1) - pad_kws = pad_kwargs if pad_kwargs is not None else {} - baseline = uniform_filter1d( - pad_edges(rough_baseline, smooth_half_window, **pad_kws), - 2 * smooth_half_window + 1 - )[smooth_half_window:self._len + smooth_half_window] - - return baseline, {'half_window': half_wind} - - @_Algorithm._register - def tophat(self, data, half_window=None, **window_kwargs): - """ - Estimates the baseline using a top-hat transformation (morphological opening). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphological opening. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - - Notes - ----- - The actual top-hat transformation is defined as `data - opening(data)`, where - `opening` is the morphological opening operation. This function, however, returns - `opening(data)`, since that is technically the baseline defined by the operation. - - References - ---------- - Perez-Pueyo, R., et al. Morphology-Based Automated Baseline Removal for - Raman Spectra of Artistic Pigments. Applied Spectroscopy, 2010, 64, 595-600. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - baseline = grey_opening(y, [2 * half_wind + 1]) - - return baseline, {'half_window': half_wind} - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def mpspline(self, data, half_window=None, lam=1e4, lam_smooth=1e-2, p=0.0, - num_knots=100, spline_degree=3, diff_order=2, weights=None, - pad_kwargs=None, **window_kwargs): - """ - Morphology-based penalized spline baseline. - - Identifies baseline points using morphological operations, and then uses weighted - least-squares to fit a penalized spline to the baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - lam : float, optional - The smoothing parameter for the penalized spline when fitting the baseline. - Larger values will create smoother baselines. Default is 1e4. Larger values - are needed for larger `num_knots`. - lam_smooth : float, optional - The smoothing parameter for the penalized spline when smoothing the input - data. Default is 1e-2. Larger values are needed for noisy data or for larger - `num_knots`. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Anchor points - identified by the procedure in the reference are given a weight of `1 - p`, - and all other points have a weight of `p`. Default is 0.0. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the weights will be - calculated following the procedure in the reference. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'half_window': int - The half window used for the morphological calculations. - - Raises - ------ - ValueError - Raised if `half_window` is < 1, if `lam` or `lam_smooth` is <= 0, or if - `p` is not between 0 and 1. - - Notes - ----- - The optimal opening is calculated as the element-wise minimum of the opening and - the average of the erosion and dilation of the opening. The reference used the - erosion and dilation of the smoothed data, rather than the opening, which tends to - overestimate the baseline. - - Rather than setting knots at the intersection points of the optimal opening and the - smoothed data as described in the reference, weights are assigned to `1 - p` at the - intersection points and `p` elsewhere. This simplifies the penalized spline - calculation by allowing the use of equally spaced knots, but should otherwise give - similar results as the reference algorithm. - - References - ---------- - Gonzalez-Vidal, J., et al. Automatic morphology-based cubic p-spline fitting - methodology for smoothing and baseline-removal of Raman spectra. Journal of - Raman Spectroscopy. 2017, 48(6), 878-883. - - """ - if half_window is not None and half_window < 1: - raise ValueError('half-window must be greater than 0') - elif not 0 <= p <= 1: - raise ValueError('p must be between 0 and 1') - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam_smooth - ) - - # TODO should this use np.isclose instead? - # TODO this overestimates the data when there is a lot of noise, leading to an - # overestimated baseline; could alternatively just fit a p-spline to - # 0.5 * (grey_closing(y, 3) + grey_opening(y, 3)), which averages noisy data better; - # could add it as a boolean parameter - spline_fit = self.pspline.solve_pspline( - y, weights=(y == grey_closing(y, 3)).astype(float, copy=False) - ) - if weights is None: - _, half_window = self._setup_morphology(spline_fit, half_window, **window_kwargs) - full_window = 2 * half_window + 1 - - pad_kws = pad_kwargs if pad_kwargs is not None else {} - padded_spline = pad_edges(spline_fit, full_window, **pad_kws) - opening = grey_opening(padded_spline, full_window) - # using the opening rather than padded_spline is more conservative when identifying - # baseline points and results in much better results - optimal_opening = np.minimum( - opening, _avg_opening(y, half_window, opening) - )[full_window:-full_window] - - # TODO should this use np.isclose instead? - mask = spline_fit == optimal_opening - weight_array[mask] = 1 - p - weight_array[~mask] = p - - self.pspline.penalty = (_check_lam(lam) / lam_smooth) * self.pspline.penalty - baseline = self.pspline.solve_pspline(spline_fit, weight_array) - - return baseline, {'half_window': half_window, 'weights': weight_array} - - @_Algorithm._register(sort_keys=('signal',)) - def jbcd(self, data, half_window=None, alpha=0.1, beta=1e1, gamma=1., beta_mult=1.1, - gamma_mult=0.909, diff_order=1, max_iter=20, tol=1e-2, tol_2=1e-3, - robust_opening=True, **window_kwargs): - """ - Joint Baseline Correction and Denoising (jbcd) Algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - alpha : float, optional - The regularization parameter that controls how close the baseline must fit the - calculated morphological opening. Larger values make the fit more constrained to - the opening and can make the baseline less smooth. Default is 0.1. - beta : float, optional - The regularization parameter that controls how smooth the baseline is. Larger - values produce smoother baselines. Default is 1e1. - gamma : float, optional - The regularization parameter that controls how smooth the signal is. Larger - values produce smoother baselines. Default is 1. - beta_mult : float, optional - The value that `beta` is multiplied by each iteration. Default is 1.1. - gamma_mult : float, optional - The value that `gamma` is multiplied by each iteration. Default is 0.909. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 1 - (first order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The maximum number of iterations. Default is 20. - tol : float, optional - The exit criteria for the change in the calculated signal. Default is 1e-2. - tol_2 : float, optional - The exit criteria for the change in the calculated baseline. Default is 1e-2. - robust_opening : bool, optional - If True (default), the opening used to represent the initial baseline is the - element-wise minimum between the morphological opening and the average of the - morphological erosion and dilation of the opening, similar to :meth:`.mor`. If - False, the opening is just the morphological opening, as used in the reference. - The robust opening typically represents the baseline better. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray, shape (K, 2) - An array containing the calculated tolerance values for each - iteration. Index 0 are the tolerence values for the relative change in - the signal, and index 1 are the tolerance values for the relative change - in the baseline. The length of the array is the number of iterations - completed, K. If the last values in the array are greater than the input - `tol` or `tol_2` values, then the function did not converge. - * 'signal': numpy.ndarray, shape (N,) - The pure signal portion of the input `data` without noise or the baseline. - - References - ---------- - Liu, H., et al. Joint Baseline-Correction and Denoising for Raman Spectra. - Applied Spectroscopy, 2015, 69(9), 1013-1022. - - """ - y, half_wind = self._setup_morphology(data, half_window, **window_kwargs) - self._setup_whittaker(y, lam=1, diff_order=diff_order) - beta = _check_lam(beta) - gamma = _check_lam(gamma, allow_zero=True) - - opening = grey_opening(y, 2 * half_wind + 1) - if robust_opening: - opening = np.minimum(opening, _avg_opening(y, half_wind, opening)) - - baseline_old = opening - signal_old = y - main_diag_idx = self.whittaker_system.main_diagonal_index - partial_rhs_2 = (2 * alpha) * opening - tol_history = np.empty((max_iter + 1, 2)) - for i in range(max_iter + 1): - lhs_1 = gamma * self.whittaker_system.penalty - lhs_1[main_diag_idx] += 1 - lhs_2 = (2 * beta) * self.whittaker_system.penalty - lhs_2[main_diag_idx] += 1 + 2 * alpha - - signal = self.whittaker_system.solve( - lhs_1, y - baseline_old, overwrite_ab=True, overwrite_b=True - ) - baseline = self.whittaker_system.solve( - lhs_2, y - signal + partial_rhs_2, overwrite_ab=True, overwrite_b=True - ) - - calc_tol_1 = relative_difference(signal_old, signal) - calc_tol_2 = relative_difference(baseline_old, baseline) - tol_history[i] = (calc_tol_1, calc_tol_2) - if calc_tol_1 < tol and calc_tol_2 < tol_2: - break - signal_old = signal - baseline_old = baseline - gamma *= gamma_mult - beta *= beta_mult - - params = {'half_window': half_wind, 'tol_history': tol_history[:i + 1], 'signal': signal} - - return baseline, params - - -_morphological_wrapper = _class_wrapper(_Morphological) - - -def _avg_opening(y, half_window, opening=None): - """ - Averages the dilation and erosion of a morphological opening on data. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The array of the measured data. - half_window : int, optional - The half window size to use for the operations. - opening : numpy.ndarray, optional - The output of scipy.ndimage.grey_opening(y, window_size). Default is - None, which will compute the value. - - Returns - ------- - numpy.ndarray, shape (N,) - The average of the dilation and erosion of the opening. - - References - ---------- - Perez-Pueyo, R., et al. Morphology-Based Automated Baseline Removal for - Raman Spectra of Artistic Pigments. Applied Spectroscopy, 2010, 64 595-600. - - """ - window_size = 2 * half_window + 1 - if opening is None: - opening = grey_opening(y, [window_size]) - return 0.5 * (grey_dilation(opening, [window_size]) + grey_erosion(opening, [window_size])) - - -@_morphological_wrapper -def mpls(data, half_window=None, lam=1e6, p=0.0, diff_order=2, tol=1e-3, max_iter=50, - weights=None, x_data=None, **window_kwargs): - """ - The Morphological penalized least squares (MPLS) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Anchor points - identified by the procedure in [1]_ are given a weight of `1 - p`, and all - other points have a weight of `p`. Default is 0.0. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the weights will be - calculated following the procedure in [1]_. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'half_window': int - The half window used for the morphological calculations. - - Raises - ------ - ValueError - Raised if p is not between 0 and 1. - - References - ---------- - .. [1] Li, Zhong, et al. Morphological weighted penalized least squares for - background correction. Analyst, 2013, 138, 4483-4492. - - """ - - -@_morphological_wrapper -def mor(data, half_window=None, x_data=None, **window_kwargs): - """ - A Morphological based (Mor) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - - References - ---------- - Perez-Pueyo, R., et al. Morphology-Based Automated Baseline Removal for - Raman Spectra of Artistic Pigments. Applied Spectroscopy, 2010, 64, 595-600. - - """ - - -@_morphological_wrapper -def imor(data, half_window=None, tol=1e-3, max_iter=200, x_data=None, **window_kwargs): - """ - An Improved Morphological based (IMor) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 200. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Dai, L., et al. An Automated Baseline Correction Method Based on Iterative - Morphological Operations. Applied Spectroscopy, 2018, 72(5), 731-739. - - """ - - -@_morphological_wrapper -def amormol(data, half_window=None, tol=1e-3, max_iter=200, pad_kwargs=None, x_data=None, - **window_kwargs): - """ - Iteratively averaging morphological and mollified (aMorMol) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 200. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from convolution. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Chen, H., et al. An Adaptive and Fully Automated Baseline Correction - Method for Raman Spectroscopy Based on Morphological Operations and - Mollifications. Applied Spectroscopy, 2019, 73(3), 284-293. - - """ - - -@_morphological_wrapper -def mormol(data, half_window=None, tol=1e-3, max_iter=250, smooth_half_window=None, - pad_kwargs=None, x_data=None, **window_kwargs): - """ - Iterative morphological and mollified (MorMol) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 200. - smooth_half_window : int, optional - The half-window to use for smoothing the data before performing the - morphological operation. Default is None, which will use a value of 1, - which gives no smoothing. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from convolution. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Koch, M., et al. Iterative morphological and mollifier-based baseline - correction for Raman spectra. J Raman Spectroscopy, 2017, 48(2), 336-342. - - """ - - -@_morphological_wrapper -def rolling_ball(data, half_window=None, smooth_half_window=None, pad_kwargs=None, - x_data=None, **window_kwargs): - """ - The rolling ball baseline algorithm. - - Applies a minimum and then maximum moving window, and subsequently smooths the - result, giving a baseline that resembles rolling a ball across the data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - smooth_half_window : int, optional - The half-window to use for smoothing the data after performing the - morphological operation. Default is None, which will use the same - value as used for the morphological operation. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from the moving average. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int or numpy.ndarray(int) - The half window or array of half windows used for the - morphological calculations. - - Notes - ----- - To use a changing window size for either the morphological or smoothing - operations, the half windows must be arrays. Otherwise, the size of the - rolling ball is assumed to be constant. - - References - ---------- - Kneen, M.A., et al. Algorithm for fitting XRF, SEM and PIXE X-ray spectra - backgrounds. Nuclear Instruments and Methods in Physics Research B, 1996, - 109, 209-213. - - Liland, K., et al. Optimal Choice of Baseline Correction for Multivariate - Calibration of Spectra. Applied Spectroscopy, 2010, 64(9), 1007-1016. - - """ - - -@_morphological_wrapper -def mwmv(data, half_window=None, smooth_half_window=None, pad_kwargs=None, - x_data=None, **window_kwargs): - """ - Moving window minimum value (MWMV) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - smooth_half_window : int, optional - The half-window to use for smoothing the data after performing the - morphological operation. Default is None, which will use the same - value as used for the morphological operation. - pad_kwargs : dict, optional - A dictionary of keyword arguments to pass to :func:`.pad_edges` for - padding the edges of the data to prevent edge effects from the moving average. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - - Notes - ----- - Performs poorly when baseline is rapidly changing. - - References - ---------- - Yaroshchyk, P., et al. Automatic correction of continuum background in Laser-induced - Breakdown Spectroscopy using a model-free algorithm. Spectrochimica Acta Part B, 2014, - 99, 138-149. - - """ - - -@_morphological_wrapper -def tophat(data, half_window=None, x_data=None, **window_kwargs): - """ - Estimates the baseline using a top-hat transformation (morphological opening). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphological opening. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - - Notes - ----- - The actual top-hat transformation is defined as `data - opening(data)`, where - `opening` is the morphological opening operation. This function, however, returns - `opening(data)`, since that is technically the baseline defined by the operation. - - References - ---------- - Perez-Pueyo, R., et al. Morphology-Based Automated Baseline Removal for - Raman Spectra of Artistic Pigments. Applied Spectroscopy, 2010, 64, 595-600. - - """ - - -@_morphological_wrapper -def mpspline(data, half_window=None, lam=1e4, lam_smooth=1e-2, p=0.0, num_knots=100, - spline_degree=3, diff_order=2, weights=None, pad_kwargs=None, x_data=None, - **window_kwargs): - """ - Morphology-based penalized spline baseline. - - Identifies baseline points using morphological operations, and then uses weighted - least-squares to fit a penalized spline to the baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - lam : float, optional - The smoothing parameter for the penalized spline when fitting the baseline. - Larger values will create smoother baselines. Default is 1e4. Larger values - are needed for larger `num_knots`. - lam_smooth : float, optional - The smoothing parameter for the penalized spline when smoothing the input - data. Default is 1e-2. Larger values are needed for noisy data or for larger - `num_knots`. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Anchor points - identified by the procedure in the reference are given a weight of `1 - p`, - and all other points have a weight of `p`. Default is 0.0. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the weights will be - calculated following the procedure in the reference. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'half_window': int - The half window used for the morphological calculations. - - Raises - ------ - ValueError - Raised if `half_window` is < 1, if `lam` or `lam_smooth` is <= 0, or if - `p` is not between 0 and 1. - - Notes - ----- - The optimal opening is calculated as the element-wise minimum of the opening and - the average of the erosion and dilation of the opening. The reference used the - erosion and dilation of the smoothed data, rather than the opening, which tends to - overestimate the baseline. - - Rather than setting knots at the intersection points of the optimal opening and the - smoothed data as described in the reference, weights are assigned to `1 - p` at the - intersection points and `p` elsewhere. This simplifies the penalized spline - calculation by allowing the use of equally spaced knots, but should otherwise give - similar results as the reference algorithm. - - References - ---------- - Gonzalez-Vidal, J., et al. Automatic morphology-based cubic p-spline fitting - methodology for smoothing and baseline-removal of Raman spectra. Journal of - Raman Spectroscopy. 2017, 48(6), 878-883. - - """ - - -@_morphological_wrapper -def jbcd(data, half_window=None, alpha=0.1, beta=1e1, gamma=1., beta_mult=1.1, gamma_mult=0.909, - diff_order=1, max_iter=20, tol=1e-2, tol_2=1e-3, robust_opening=True, x_data=None, - **window_kwargs): - """ - Joint Baseline Correction and Denoising (jbcd) Algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window used for the morphology functions. If a value is input, - then that value will be used. Default is None, which will optimize the - half-window size using :func:`.optimize_window` and `window_kwargs`. - alpha : float, optional - The regularization parameter that controls how close the baseline must fit the - calculated morphological opening. Larger values make the fit more constrained to - the opening and can make the baseline less smooth. Default is 0.1. - beta : float, optional - The regularization parameter that controls how smooth the baseline is. Larger - values produce smoother baselines. Default is 1e1. - gamma : float, optional - The regularization parameter that controls how smooth the signal is. Larger - values produce smoother baselines. Default is 1. - beta_mult : float, optional - The value that `beta` is multiplied by each iteration. Default is 1.1. - gamma_mult : float, optional - The value that `gamma` is multiplied by each iteration. Default is 0.909. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 1 - (first order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The maximum number of iterations. Default is 20. - tol : float, optional - The exit criteria for the change in the calculated signal. Default is 1e-2. - tol_2 : float, optional - The exit criteria for the change in the calculated baseline. Default is 1e-2. - robust_opening : bool, optional - If True (default), the opening used to represent the initial baseline is the - element-wise minimum between the morphological opening and the average of the - morphological erosion and dilation of the opening, similar to :meth:`.mor`. If - False, the opening is just the morphological opening, as used in the reference. - The robust opening typically represents the baseline better. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **window_kwargs - Values for setting the half window used for the morphology operations. - Items include: - - * 'increment': int - The step size for iterating half windows. Default is 1. - * 'max_hits': int - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the - optimum value. Default is 1. - * 'window_tol': float - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - * 'max_half_window': int - The maximum allowable window size. If None (default), will be set - to (len(data) - 1) / 2. - * 'min_half_window': int - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'half_window': int - The half window used for the morphological calculations. - * 'tol_history': numpy.ndarray, shape (K, 2) - An array containing the calculated tolerance values for each - iteration. Index 0 are the tolerence values for the relative change in - the signal, and index 1 are the tolerance values for the relative change - in the baseline. The length of the array is the number of iterations - completed, K. If the last values in the array are greater than the input - `tol` or `tol_2` values, then the function did not converge. - * 'signal': numpy.ndarray, shape (N,) - The pure signal portion of the input `data` without noise or the baseline. - - References - ---------- - Liu, H., et al. Joint Baseline-Correction and Denoising for Raman Spectra. - Applied Spectroscopy, 2015, 69(9), 1013-1022. - - """ diff --git a/GSASII/pybaselines/optimizers.py b/GSASII/pybaselines/optimizers.py deleted file mode 100644 index d27532c1f..000000000 --- a/GSASII/pybaselines/optimizers.py +++ /dev/null @@ -1,948 +0,0 @@ -# -*- coding: utf-8 -*- -"""High level functions for making better use of baseline algorithms. - -Functions in this module make use of other baseline algorithms in -pybaselines to provide better results or optimize parameters. - -Created on March 3, 2021 -@author: Donald Erb - -""" - -from math import ceil - -import numpy as np - -from . import classification, misc, morphological, polynomial, smooth, spline, whittaker -from ._algorithm_setup import _Algorithm, _class_wrapper, _sort_array -from ._validation import _check_optional_array -from .utils import _check_scalar, _get_edges, gaussian, whittaker_smooth - - -class _Optimizers(_Algorithm): - """A base class for all optimizer algorithms.""" - - @_Algorithm._register(ensure_1d=False) - def collab_pls(self, data, average_dataset=True, method='asls', method_kwargs=None): - """ - Collaborative Penalized Least Squares (collab-PLS). - - Averages the data or the fit weights for an entire dataset to get more - optimal results. Uses any Whittaker-smoothing-based or weighted spline algorithm. - - Parameters - ---------- - data : array-like, shape (M, N) - An array with shape (M, N) where M is the number of entries in - the dataset and N is the number of data points in each entry. - average_dataset : bool, optional - If True (default) will average the dataset before fitting to get the - weighting. If False, will fit each individual entry in the dataset and - then average the weights to get the weighting for the dataset. - method : str, optional - A string indicating the Whittaker-smoothing-based or weighted spline method to - use for fitting the baseline. Default is 'asls'. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the selected `method` function. - Default is None, which will use an empty dictionary. - - Returns - ------- - baselines : np.ndarray, shape (M, N) - An array of all of the baselines. - params : dict - A dictionary with the following items: - - * 'average_weights': numpy.ndarray, shape (N,) - The weight array used to fit all of the baselines. - * 'average_alpha': numpy.ndarray, shape (N,) - Only returned if `method` is 'aspls' or 'pspline_aspls'. The - `alpha` array used to fit all of the baselines for the - :meth:`~pybaselines.whittaker.Whittaker.aspls` or - :meth:`~pybaselines.spline.Spline.pspline_aspls` methods. - - Additional items depend on the output of the selected method. Every - other key will have a list of values, with each item corresponding to a - fit. - - Notes - ----- - If `method` is 'aspls' or 'pspline_aspls', `collab_pls` will also calculate - the `alpha` array for the entire dataset in the same manner as the weights. - - References - ---------- - Chen, L., et al. Collaborative Penalized Least Squares for Background - Correction of Multiple Raman Spectra. Journal of Analytical Methods - in Chemistry, 2018, 2018. - - """ - dataset, baseline_func, _, method_kws, _ = self._setup_optimizer( - data, method, (whittaker, morphological, classification, spline), method_kwargs, - True - ) - data_shape = dataset.shape - if len(data_shape) != 2: - raise ValueError(( - 'the input data must have a shape of (number of measurements, number of points), ' - f'but instead has a shape of {data_shape}' - )) - method = method.lower() - # if using aspls or pspline_aspls, also need to calculate the alpha array - # for the entire dataset - calc_alpha = method in ('aspls', 'pspline_aspls') - - # step 1: calculate weights for the entire dataset - if average_dataset: - _, fit_params = baseline_func(np.mean(dataset, axis=0), **method_kws) - method_kws['weights'] = fit_params['weights'] - if calc_alpha: - method_kws['alpha'] = fit_params['alpha'] - else: - weights = np.empty(data_shape) - if calc_alpha: - alpha = np.empty(data_shape) - for i, entry in enumerate(dataset): - _, fit_params = baseline_func(entry, **method_kws) - weights[i] = fit_params['weights'] - if calc_alpha: - alpha[i] = fit_params['alpha'] - method_kws['weights'] = np.mean(weights, axis=0) - if calc_alpha: - method_kws['alpha'] = np.mean(alpha, axis=0) - - # step 2: use the dataset weights from step 1 (stored in method_kws['weights']) - # to fit each individual data entry; set tol to infinity so that only one - # iteration is done and new weights are not calculated - method_kws['tol'] = np.inf - baselines = np.empty(data_shape) - params = {'average_weights': method_kws['weights']} - if calc_alpha: - params['average_alpha'] = method_kws['alpha'] - if method == 'fabc': - # set weights as mask so it just fits the data - method_kws['weights_as_mask'] = True - - for i, entry in enumerate(dataset): - baselines[i], param = baseline_func(entry, **method_kws) - for key, value in param.items(): - if key in params: - params[key].append(value) - else: - params[key] = [value] - - return _sort_array(baselines, self._sort_order), params - - @_Algorithm._register - def optimize_extended_range(self, data, method='asls', side='both', width_scale=0.1, - height_scale=1., sigma_scale=1. / 12., min_value=2, max_value=8, - step=1, pad_kwargs=None, method_kwargs=None): - """ - Extends data and finds the best parameter value for the given baseline method. - - Adds additional data to the left and/or right of the input data, and then iterates - through parameter values to find the best fit. Useful for calculating the optimum - `lam` or `poly_order` value required to optimize other algorithms. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - method : str, optional - A string indicating the Whittaker-smoothing-based, polynomial, or spline method - to use for fitting the baseline. Default is 'asls'. - side : {'both', 'left', 'right'}, optional - The side of the measured data to extend. Default is 'both'. - width_scale : float, optional - The number of data points added to each side is `width_scale` * N. Default - is 0.1. - height_scale : float, optional - The height of the added Gaussian peak(s) is calculated as - `height_scale` * max(`data`). Default is 1. - sigma_scale : float, optional - The sigma value for the added Gaussian peak(s) is calculated as - `sigma_scale` * `width_scale` * N. Default is 1/12, which will make - the Gaussian span +- 6 sigma, making its total width about half of the - added length. - min_value : int or float, optional - The minimum value for the `lam` or `poly_order` value to use with the - indicated method. If using a polynomial method, `min_value` must be an - integer. If using a Whittaker-smoothing-based method, `min_value` should - be the exponent to raise to the power of 10 (eg. a `min_value` value of 2 - designates a `lam` value of 10**2). - Default is 2. - max_value : int or float, optional - The maximum value for the `lam` or `poly_order` value to use with the - indicated method. If using a polynomial method, `max_value` must be an - integer. If using a Whittaker-smoothing-based method, `max_value` should - be the exponent to raise to the power of 10 (eg. a `max_value` value of 3 - designates a `lam` value of 10**3). - Default is 8. - step : int or float, optional - The step size for iterating the parameter value from `min_value` to `max_value`. - If using a polynomial method, `step` must be an integer. - pad_kwargs : dict, optional - A dictionary of options to pass to :func:`.pad_edges` for padding - the edges of the data when adding the extended left and/or right sections. - Default is None, which will use an empty dictionary. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the selected `method` function. - Default is None, which will use an empty dictionary. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The baseline calculated with the optimum parameter. - method_params : dict - A dictionary with the following items: - - * 'optimal_parameter': int or float - The `lam` or `poly_order` value that produced the lowest - root-mean-squared-error. - * 'min_rmse': float - The minimum root-mean-squared-error obtained when using - the optimal parameter. - - Additional items depend on the output of the selected method. - - Raises - ------ - ValueError - Raised if `side` is not 'left', 'right', or 'both'. - TypeError - Raised if using a polynomial method and `min_value`, `max_value`, or - `step` is not an integer. - ValueError - Raised if using a Whittaker-smoothing-based method and `min_value`, - `max_value`, or `step` is greater than 100. - - Notes - ----- - Based on the extended range penalized least squares (erPLS) method from [5]_. - The method proposed by [5]_ was for optimizing lambda only for the aspls - method by extending only the right side of the spectrum. The method was - modified by allowing extending either side following [6]_, and for optimizing - lambda or the polynomial degree for all of the affected algorithms in - pybaselines. - - References - ---------- - .. [5] Zhang, F., et al. An Automatic Baseline Correction Method Based on - the Penalized Least Squares Method. Sensors, 2020, 20(7), 2015. - .. [6] Krishna, H., et al. Range-independent background subtraction algorithm - for recovery of Raman spectra of biological tissue. Journal of Raman - Spectroscopy. 2012, 43(12), 1884-1894. - - """ - side = side.lower() - if side not in ('left', 'right', 'both'): - raise ValueError('side must be "left", "right", or "both"') - - y, baseline_func, func_module, method_kws, fit_object = self._setup_optimizer( - data, method, (whittaker, polynomial, morphological, spline, classification), - method_kwargs, True - ) - method = method.lower() - if func_module == 'polynomial' or method in ('dietrich', 'cwt_br'): - if any(not isinstance(val, int) for val in (min_value, max_value, step)): - raise TypeError(( - 'min_value, max_value, and step must all be integers when' - ' using a polynomial method' - )) - param_name = 'poly_order' - else: - if any(val > 100 for val in (min_value, max_value, step)): - raise ValueError(( - 'min_value, max_value, and step should be the power of 10 to use ' - '(eg. min_value=2 denotes 10**2), not the actual "lam" value, and ' - 'thus should not be greater than 100' - )) - param_name = 'lam' - - added_window = int(self._len * width_scale) - for key in ('weights', 'alpha'): - if key in method_kws: - method_kws[key] = np.pad( - method_kws[key], - [0 if side == 'right' else added_window, 0 if side == 'left' else added_window], - 'constant', constant_values=1 - ) - - min_x = self.x_domain[0] - max_x = self.x_domain[1] - x_range = max_x - min_x - known_background = np.array([]) - fit_x_data = self.x - fit_data = y - lower_bound = upper_bound = 0 - - if pad_kwargs is None: - pad_kwargs = {} - # use data rather than y since data is sorted correctly - added_left, added_right = _get_edges(data, added_window, **pad_kwargs) - added_gaussian = gaussian( - np.linspace(-added_window / 2, added_window / 2, added_window), - height_scale * abs(y.max()), 0, added_window * sigma_scale - ) - if side in ('right', 'both'): - added_x = np.linspace( - max_x, max_x + x_range * (width_scale / 2), added_window + 1 - )[1:] - fit_x_data = np.concatenate((fit_x_data, added_x)) - fit_data = np.concatenate((fit_data, added_gaussian + added_right)) - known_background = added_right - upper_bound += added_window - if side in ('left', 'both'): - added_x = np.linspace( - min_x - x_range * (width_scale / 2), min_x, added_window + 1 - )[:-1] - fit_x_data = np.concatenate((added_x, fit_x_data)) - fit_data = np.concatenate((added_gaussian + added_left, fit_data)) - known_background = np.concatenate((known_background, added_left)) - lower_bound += added_window - - added_len = 2 * added_window if side == 'both' else added_window - upper_idx = len(fit_data) - upper_bound - min_sum_squares = np.inf - best_val = None - - if self._sort_order is None: - new_sort_order = None - else: - if side == 'right': - new_sort_order = np.empty(self._len + added_len, dtype=np.intp) - new_sort_order[:self._len] = self._sort_order - new_sort_order[self._len:] = np.arange( - self._len, self._len + added_len, dtype=np.intp - ) - elif side == 'left': - new_sort_order = np.empty(self._len + added_len, dtype=np.intp) - new_sort_order[added_len:] = self._sort_order + added_len - new_sort_order[:added_len] = np.arange(added_len, dtype=np.intp) - else: - new_sort_order = np.concatenate(( - np.arange(added_window, dtype=np.intp), - self._sort_order + added_window, - np.arange(self._len + added_window, self._len + added_len, dtype=np.intp) - )) - - # TODO maybe switch to linspace since arange is inconsistent when using floats - with fit_object._override_x(fit_x_data, new_sort_order=new_sort_order): - for var in np.arange(min_value, max_value + step, step): - if param_name == 'lam': - method_kws[param_name] = 10**var - else: - method_kws[param_name] = var - fit_baseline, fit_params = baseline_func(fit_data, **method_kws) - # TODO change the known baseline so that np.roll does not have to be - # calculated each time, since it requires additional time - residual = ( - known_background - np.roll(fit_baseline, upper_bound)[:added_len] - ) - # just calculate the sum of squares to reduce time from using sqrt for rmse - sum_squares = residual.dot(residual) - if sum_squares < min_sum_squares: - baseline = fit_baseline[lower_bound:upper_idx] - params = fit_params - best_val = var - min_sum_squares = sum_squares - - params.update( - {'optimal_parameter': best_val, 'min_rmse': np.sqrt(min_sum_squares / added_len)} - ) - for key in ('weights', 'alpha'): - if key in params: - params[key] = params[key][ - 0 if side == 'right' else added_window: - None if side == 'left' else -added_window - ] - - return _sort_array(baseline, self._sort_order), params - - @_Algorithm._register - def adaptive_minmax(self, data, poly_order=None, method='modpoly', weights=None, - constrained_fraction=0.01, constrained_weight=1e5, - estimation_poly_order=2, method_kwargs=None): - """ - Fits polynomials of different orders and uses the maximum values as the baseline. - - Each polynomial order fit is done both unconstrained and constrained at the - endpoints. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int or Sequence(int, int) or None, optional - The two polynomial orders to use for fitting. If a single integer is given, - then will use the input value and one plus the input value. Default is None, - which will do a preliminary fit using a polynomial of order `estimation_poly_order` - and then select the appropriate polynomial orders according to [7]_. - method : {'modpoly', 'imodpoly'}, optional - The method to use for fitting each polynomial. Default is 'modpoly'. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - constrained_fraction : float or Sequence(float, float), optional - The fraction of points at the left and right edges to use for the - constrained fit. Default is 0.01. If `constrained_fraction` is a sequence, - the first item is the fraction for the left edge and the second is the - fraction for the right edge. - constrained_weight : float or Sequence(float, float), optional - The weighting to give to the endpoints. Higher values ensure that the - end points are fit, but can cause large fluctuations in the other sections - of the polynomial. Default is 1e5. If `constrained_weight` is a sequence, - the first item is the weight for the left edge and the second is the - weight for the right edge. - estimation_poly_order : int, optional - The polynomial order used for estimating the baseline-to-signal ratio - to select the appropriate polynomial orders if `poly_order` is None. - Default is 2. - method_kwargs : dict, optional - Additional keyword arguments to pass to - :meth:`~pybaselines.polynomial.Polynomial.modpoly` or - :meth:`~pybaselines.polynomial.Polynomial.imodpoly`. These include - `tol`, `max_iter`, `use_original`, `mask_initial_peaks`, and `num_std`. - - Returns - ------- - numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'constrained_weights': numpy.ndarray, shape (N,) - The weight array used for the endpoint-constrained fits. - * 'poly_order': numpy.ndarray, shape (2,) - An array of the two polynomial orders used for the fitting. - - References - ---------- - .. [7] Cao, A., et al. A robust method for automated background subtraction - of tissue fluorescence. Journal of Raman Spectroscopy, 2007, 38, - 1199-1205. - - """ - y, baseline_func, _, method_kws, _ = self._setup_optimizer( - data, method, [polynomial], method_kwargs, False - ) - sort_weights = weights is not None - weight_array = _check_optional_array(self._len, weights, check_finite=self._check_finite) - if poly_order is None: - poly_orders = _determine_polyorders( - y, estimation_poly_order, weight_array, baseline_func, **method_kws - ) - else: - poly_orders, scalar_poly_order = _check_scalar(poly_order, 2, True, dtype=int) - if scalar_poly_order: - poly_orders[1] += 1 # add 1 since they are initially equal if scalar input - - # use high weighting rather than Lagrange multipliers to constrain the points - # to better work with noisy data - weightings = _check_scalar(constrained_weight, 2, True)[0] - constrained_fractions = _check_scalar(constrained_fraction, 2, True)[0] - if np.any(constrained_fractions < 0) or np.any(constrained_fractions > 1): - raise ValueError('constrained_fraction must be between 0 and 1') - - # have to temporarily sort weights to match x- and y-ordering so that left and right edges - # are correct - if sort_weights: - weight_array = _sort_array(weight_array, self._sort_order) - - constrained_weights = weight_array.copy() - constrained_weights[:ceil(self._len * constrained_fractions[0])] = weightings[0] - constrained_weights[ - self._len - ceil(self._len * constrained_fractions[1]): - ] = weightings[1] - - # and now change back to original ordering - if sort_weights: - weight_array = _sort_array(weight_array, self._inverted_order) - constrained_weights = _sort_array(constrained_weights, self._inverted_order) - - # TODO should make parameters available; a list with an item for each fit like collab_pls - # TODO could maybe just use itertools.permutations, but would want to know the order in - # which the parameters are used - baselines = np.empty((4, self._len)) - baselines[0] = baseline_func( - data=y, poly_order=poly_orders[0], weights=weight_array, **method_kws - )[0] - baselines[1] = baseline_func( - data=y, poly_order=poly_orders[0], weights=constrained_weights, **method_kws - )[0] - baselines[2] = baseline_func( - data=y, poly_order=poly_orders[1], weights=weight_array, **method_kws - )[0] - baselines[3] = baseline_func( - data=y, poly_order=poly_orders[1], weights=constrained_weights, **method_kws - )[0] - - # TODO should the coefficients also be made available? Would need to get them from - # each of the fits - params = { - 'weights': weight_array, 'constrained_weights': constrained_weights, - 'poly_order': poly_orders - } - - return _sort_array(np.maximum.reduce(baselines), self._sort_order), params - - @_Algorithm._register - def custom_bc(self, data, method='asls', rois=(None, None), sampling=1, lam=None, - diff_order=2, method_kwargs=None): - """ - [summary] - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - method : str, optional - A string indicating the algorithm to use for fitting the baseline; can be any - non-optimizer algorithm in pybaselines. Default is 'asls'. - rois : array-like, shape (M, 2), optional - The two dimensional array containing the start and stop indices for each region of - interest. Each region is defined as ``data[start:stop]``. Default is (None, None), - which will use all points. - sampling : int or array-like, optional - The sampling step size for each region defined in `rois`. If `sampling` is an integer, - then all regions in `rois` will use the same index step size; if `sampling` is an - array-like, its length must be equal to `M`, the first dimension in `rois`. Default - is 1, which will use all points. - lam : float or None, optional - The value for smoothing the calculated interpolated baseline using Whittaker - smoothing, in order to reduce the kinks between regions. Default is None, which - will not smooth the baseline; a value of 0 will also not perform smoothing. - diff_order : int, optional - The difference order used for Whittaker smoothing of the calculated baseline. - Default is 2. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the selected `method` function. - Default is None, which will use an empty dictionary. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The baseline calculated with the optimum parameter. - method_params : dict - A dictionary with the following items: - - - Additional items depend on the output of the selected method. - - Raises - ------ - ValueError - Raised if `rois` is not two dimensional or if `sampling` is not the same length - as `rois.shape[0]`. - - Notes - ----- - Uses Whittaker smoothing to smooth the transitions between regions rather than LOESS - as used in [31]_ since LOESS introduces additional kinks in the regions between the - smoothed and non-smoothed regions. - - References - ---------- - .. [31] Liland, K., et al. Customized baseline correction. Chemometrics and - Intelligent Laboratory Systems, 2011, 109(1), 51-56. - - """ - y, baseline_func, _, method_kws, fitting_object = self._setup_optimizer( - data, method, - (classification, misc, morphological, polynomial, smooth, spline, whittaker), - method_kwargs, True - ) - roi_array = np.atleast_2d(rois) - roi_shape = roi_array.shape - if len(roi_shape) != 2 or roi_shape[1] != 2: - raise ValueError('rois must be a two dimensional sequence of (start, stop) values') - - steps = _check_scalar(sampling, roi_shape[0], fill_scalar=True, dtype=np.intp)[0] - if np.any(steps < 1): - raise ValueError('all step sizes in "sampling" must be >= 1') - - # TODO maybe use binning (sum or average) to get a better signal to noise ratio rather - # than just indexing - x_sections = [] - x_mask = np.ones(self._len, dtype=bool) - for (start, stop), step in zip(roi_array, steps): - if start is None: - start = 0 - if stop is None: - stop = self._len - x_sections.append(np.arange(start, stop, step, dtype=np.intp)) - x_mask[start:stop] = False - x_sections.append(np.arange(self._len, dtype=np.intp)[x_mask]) - indices = np.unique(np.clip(np.concatenate(x_sections), 0, self._len - 1)) - y_fit = y[indices] - x_fit = self.x[indices] - - # TODO what about the sort ordering? - with fitting_object._override_x(x_fit): - baseline_fit, params = baseline_func(y_fit, **method_kws) - - baseline = np.interp(self.x, x_fit, baseline_fit) - params.update({'x_data': x_fit, 'data': y_fit}) - if lam is not None and lam != 0: - _, weights = self._setup_whittaker(y, lam=lam, diff_order=diff_order) - baseline = whittaker_smooth( - baseline, lam=lam, diff_order=diff_order, weights=weights, - check_finite=self._check_finite, penalized_system=self.whittaker_system - ) - - return _sort_array(baseline, self._sort_order), params - - -_optimizers_wrapper = _class_wrapper(_Optimizers) - - -@_optimizers_wrapper -def collab_pls(data, average_dataset=True, method='asls', method_kwargs=None, x_data=None): - """ - Collaborative Penalized Least Squares (collab-PLS). - - Averages the data or the fit weights for an entire dataset to get more - optimal results. Uses any Whittaker-smoothing-based or weighted spline algorithm. - - Parameters - ---------- - data : array-like, shape (M, N) - An array with shape (M, N) where M is the number of entries in - the dataset and N is the number of data points in each entry. - average_dataset : bool, optional - If True (default) will average the dataset before fitting to get the - weighting. If False, will fit each individual entry in the dataset and - then average the weights to get the weighting for the dataset. - method : str, optional - A string indicating the Whittaker-smoothing-based or weighted spline method to - use for fitting the baseline. Default is 'asls'. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the selected `method` function. - Default is None, which will use an empty dictionary. - x_data : array-like, shape (N,), optional - The x values for the data. Not used by most Whittaker-smoothing algorithms. - - Returns - ------- - baselines : np.ndarray, shape (M, N) - An array of all of the baselines. - params : dict - A dictionary with the following items: - - * 'average_weights': numpy.ndarray, shape (N,) - The weight array used to fit all of the baselines. - * 'average_alpha': numpy.ndarray, shape (N,) - Only returned if `method` is 'aspls' or 'pspline_aspls'. The - `alpha` array used to fit all of the baselines for the - :meth:`.aspls` or :meth:`.pspline_aspls` methods. - - Additional items depend on the output of the selected method. Every - other key will have a list of values, with each item corresponding to a - fit. - - Notes - ----- - If `method` is 'aspls' or 'pspline_aspls', `collab_pls` will also calculate - the `alpha` array for the entire dataset in the same manner as the weights. - - References - ---------- - Chen, L., et al. Collaborative Penalized Least Squares for Background - Correction of Multiple Raman Spectra. Journal of Analytical Methods - in Chemistry, 2018, 2018. - - """ - - -@_optimizers_wrapper -def optimize_extended_range(data, x_data=None, method='asls', side='both', width_scale=0.1, - height_scale=1., sigma_scale=1. / 12., min_value=2, max_value=8, - step=1, pad_kwargs=None, method_kwargs=None): - """ - Extends data and finds the best parameter value for the given baseline method. - - Adds additional data to the left and/or right of the input data, and then iterates - through parameter values to find the best fit. Useful for calculating the optimum - `lam` or `poly_order` value required to optimize other algorithms. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - method : str, optional - A string indicating the Whittaker-smoothing-based, polynomial, or spline method - to use for fitting the baseline. Default is 'asls'. - side : {'both', 'left', 'right'}, optional - The side of the measured data to extend. Default is 'both'. - width_scale : float, optional - The number of data points added to each side is `width_scale` * N. Default - is 0.1. - height_scale : float, optional - The height of the added Gaussian peak(s) is calculated as - `height_scale` * max(`data`). Default is 1. - sigma_scale : float, optional - The sigma value for the added Gaussian peak(s) is calculated as - `sigma_scale` * `width_scale` * N. Default is 1/12, which will make - the Gaussian span +- 6 sigma, making its total width about half of the - added length. - min_value : int or float, optional - The minimum value for the `lam` or `poly_order` value to use with the - indicated method. If using a polynomial method, `min_value` must be an - integer. If using a Whittaker-smoothing-based method, `min_value` should - be the exponent to raise to the power of 10 (eg. a `min_value` value of 2 - designates a `lam` value of 10**2). - Default is 2. - max_value : int or float, optional - The maximum value for the `lam` or `poly_order` value to use with the - indicated method. If using a polynomial method, `max_value` must be an - integer. If using a Whittaker-smoothing-based method, `max_value` should - be the exponent to raise to the power of 10 (eg. a `max_value` value of 3 - designates a `lam` value of 10**3). - Default is 8. - step : int or float, optional - The step size for iterating the parameter value from `min_value` to `max_value`. - If using a polynomial method, `step` must be an integer. - pad_kwargs : dict, optional - A dictionary of options to pass to :func:`.pad_edges` for padding - the edges of the data when adding the extended left and/or right sections. - Default is None, which will use an empty dictionary. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the selected `method` function. - Default is None, which will use an empty dictionary. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The baseline calculated with the optimum parameter. - method_params : dict - A dictionary with the following items: - - * 'optimal_parameter': int or float - The `lam` or `poly_order` value that produced the lowest - root-mean-squared-error. - * 'min_rmse': float - The minimum root-mean-squared-error obtained when using - the optimal parameter. - - Additional items depend on the output of the selected method. - - Raises - ------ - ValueError - Raised if `side` is not 'left', 'right', or 'both'. - TypeError - Raised if using a polynomial method and `min_value`, `max_value`, or - `step` is not an integer. - ValueError - Raised if using a Whittaker-smoothing-based method and `min_value`, - `max_value`, or `step` is greater than 100. - - Notes - ----- - Based on the extended range penalized least squares (erPLS) method from [1]_. - The method proposed by [1]_ was for optimizing lambda only for the aspls - method by extending only the right side of the spectrum. The method was - modified by allowing extending either side following [2]_, and for optimizing - lambda or the polynomial degree for all of the affected algorithms in - pybaselines. - - References - ---------- - .. [1] Zhang, F., et al. An Automatic Baseline Correction Method Based on - the Penalized Least Squares Method. Sensors, 2020, 20(7), 2015. - .. [2] Krishna, H., et al. Range-independent background subtraction algorithm - for recovery of Raman spectra of biological tissue. Journal of Raman - Spectroscopy. 2012, 43(12), 1884-1894. - - """ - - -def _determine_polyorders(y, poly_order, weights, fit_function, **fit_kwargs): - """ - Selects the appropriate polynomial orders based on the baseline-to-signal ratio. - - Parameters - ---------- - y : numpy.ndarray - The array of y-values. - poly_order : int - The polynomial order for fitting. - weights : numpy.ndarray - The weight array for fitting. - fit_function : Callable - The function to use for the polynomial fit. - **fit_kwargs - Additional keyword arguments to pass to `fit_function`. - - Returns - ------- - orders : numpy.ndarray, shape (2,) - The two polynomial orders to use based on the baseline to signal - ratio according to the reference. - - References - ---------- - Cao, A., et al. A robust method for automated background subtraction - of tissue fluorescence. Journal of Raman Spectroscopy, 2007, 38, 1199-1205. - - """ - baseline = fit_function(y, poly_order=poly_order, weights=weights, **fit_kwargs)[0] - signal = y - baseline - baseline_to_signal = (baseline.max() - baseline.min()) / (signal.max() - signal.min()) - # Table 2 in reference - if baseline_to_signal < 0.2: - orders = (1, 2) - elif baseline_to_signal < 0.75: - orders = (2, 3) - elif baseline_to_signal < 8.5: - orders = (3, 4) - elif baseline_to_signal < 55: - orders = (4, 5) - elif baseline_to_signal < 240: - orders = (5, 6) - elif baseline_to_signal < 517: - orders = (6, 7) - else: - orders = (6, 8) # not a typo, use 6 and 8 rather than 7 and 8 - - return np.array(orders) - - -@_optimizers_wrapper -def adaptive_minmax(data, x_data=None, poly_order=None, method='modpoly', - weights=None, constrained_fraction=0.01, constrained_weight=1e5, - estimation_poly_order=2, method_kwargs=None): - """ - Fits polynomials of different orders and uses the maximum values as the baseline. - - Each polynomial order fit is done both unconstrained and constrained at the - endpoints. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int or Sequence(int, int) or None, optional - The two polynomial orders to use for fitting. If a single integer is given, - then will use the input value and one plus the input value. Default is None, - which will do a preliminary fit using a polynomial of order `estimation_poly_order` - and then select the appropriate polynomial orders according to [3]_. - method : {'modpoly', 'imodpoly'}, optional - The method to use for fitting each polynomial. Default is 'modpoly'. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - constrained_fraction : float or Sequence(float, float), optional - The fraction of points at the left and right edges to use for the - constrained fit. Default is 0.01. If `constrained_fraction` is a sequence, - the first item is the fraction for the left edge and the second is the - fraction for the right edge. - constrained_weight : float or Sequence(float, float), optional - The weighting to give to the endpoints. Higher values ensure that the - end points are fit, but can cause large fluctuations in the other sections - of the polynomial. Default is 1e5. If `constrained_weight` is a sequence, - the first item is the weight for the left edge and the second is the - weight for the right edge. - estimation_poly_order : int, optional - The polynomial order used for estimating the baseline-to-signal ratio - to select the appropriate polynomial orders if `poly_order` is None. - Default is 2. - method_kwargs : dict, optional - Additional keyword arguments to pass to :meth:`.modpoly` or - :meth:`.imodpoly`. These include `tol`, `max_iter`, `use_original`, - `mask_initial_peaks`, and `num_std`. - - Returns - ------- - numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'constrained_weights': numpy.ndarray, shape (N,) - The weight array used for the endpoint-constrained fits. - * 'poly_order': numpy.ndarray, shape (2,) - An array of the two polynomial orders used for the fitting. - - References - ---------- - .. [3] Cao, A., et al. A robust method for automated background subtraction - of tissue fluorescence. Journal of Raman Spectroscopy, 2007, 38, - 1199-1205. - - """ - - -@_optimizers_wrapper -def custom_bc(data, x_data=None, method='asls', rois=(None, None), sampling=1, lam=None, - diff_order=2, method_kwargs=None): - """ - [summary] - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - method : str, optional - [description]. Default is 'asls'. - rois : array-like, shape (M, 2), optional - The two dimensional array containing the start and stop indices for each region of - interest. Each region is defined as ``data[start:stop]``. Default is (None, None), - which will use all points. - sampling : int or array-like, optional - The sampling step size for each region defined in `rois`. If `sampling` is an integer, - then all regions in `rois` will use the same index step size; if `sampling` is an - array-like, its length must be equal to `M`, the first dimension in `rois`. Default - is 1, which will use all points. - lam : float or None, optional - The value for smoothing the calculated interpolated baseline using Whittaker - smoothing, in order to reduce the kinks between regions. Default is None, which - will not smooth the baseline; a value of 0 will also not perform smoothing. - diff_order : int, optional - The difference order used for Whittaker smoothing of the calculated baseline. - Default is 2. - method_kwargs : dict, optional - A dictionary of keyword arguments to pass to the selected `method` function. - Default is None, which will use an empty dictionary. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The baseline calculated with the optimum parameter. - method_params : dict - A dictionary with the following items: - - - Additional items depend on the output of the selected method. - - Raises - ------ - ValueError - Raised if `rois` is not two dimensional or if `sampling` is not the same length - as `rois.shape[0]`. - - Notes - ----- - Uses Whittaker smoothing to smooth the transitions between regions rather than LOESS - as used in [4]_ since LOESS introduces additional kinks in the regions between the - smoothed and non-smoothed regions. - - References - ---------- - .. [4] Liland, K., et al. Customized baseline correction. Chemometrics and - Intelligent Laboratory Systems, 2011, 109(1), 51-56. - - """ diff --git a/GSASII/pybaselines/polynomial.py b/GSASII/pybaselines/polynomial.py deleted file mode 100644 index eb33bab65..000000000 --- a/GSASII/pybaselines/polynomial.py +++ /dev/null @@ -1,2249 +0,0 @@ -# -*- coding: utf-8 -*- -"""Polynomial techniques for fitting baselines to experimental data. - -Created on Feb. 27, 2021 -@author: Donald Erb - - -The function penalized_poly was adapted from MATLAB code from -https://www.mathworks.com/matlabcentral/fileexchange/27429-background-correction -(accessed March 18, 2021), which was licensed under the BSD-2-clause below. - -License: 2-clause BSD - -Copyright (c) 2012, Vincent Mazet -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - -The function loess was adapted from code from https://gist.github.com/agramfort/850437 -(accessed March 25, 2021), which was licensed under the BSD-3-clause below. - -# Authors: Alexandre Gramfort -# -# License: BSD (3-clause) -Copyright (c) 2015, Alexandre Gramfort -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" - -from math import ceil -import warnings - -import numpy as np - -from . import _weighting -from ._algorithm_setup import _Algorithm, _class_wrapper -from ._compat import jit, prange -from .utils import ( - _MIN_FLOAT, ParameterWarning, _convert_coef, _interp_inplace, relative_difference -) - - -class _Polynomial(_Algorithm): - """A base class for all polynomial algorithms.""" - - @_Algorithm._register(sort_keys=('weights',)) - def poly(self, data, poly_order=2, weights=None, return_coef=False): - """ - Computes a polynomial that fits the baseline of the data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'coef': numpy.ndarray, shape (poly_order,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Notes - ----- - To only fit regions without peaks, supply a weight array with zero values - at the indices where peaks are located. - - """ - y, weight_array, pseudo_inverse = self._setup_polynomial( - data, weights, poly_order, calc_vander=True, calc_pinv=True - ) - sqrt_w = np.sqrt(weight_array) - - coef = pseudo_inverse @ (sqrt_w * y) - baseline = self.vandermonde @ coef - params = {'weights': weight_array} - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def modpoly(self, data, poly_order=2, tol=1e-3, max_iter=250, weights=None, - use_original=False, mask_initial_peaks=False, return_coef=False): - """ - The modified polynomial (ModPoly) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - use_original : bool, optional - If False (default), will compare the baseline of each iteration with - the y-values of that iteration [8]_ when choosing minimum values. If True, - will compare the baseline with the original y-values given by `data` [9]_. - mask_initial_peaks : bool, optional - If True, will mask any data where the initial baseline fit + the standard - deviation of the residual is less than measured data [10]_. Default is False. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Notes - ----- - Algorithm originally developed in [9]_ and then slightly modified in [8]_. - - References - ---------- - .. [8] Gan, F., et al. Baseline correction by improved iterative polynomial - fitting with automatic threshold. Chemometrics and Intelligent - Laboratory Systems, 2006, 82, 59-65. - .. [9] Lieber, C., et al. Automated method for subtraction of fluorescence - from biological raman spectra. Applied Spectroscopy, 2003, 57(11), - 1363-1367. - .. [10] Zhao, J., et al. Automated Autofluorescence Background Subtraction - Algorithm for Biomedical Raman Spectroscopy, Applied Spectroscopy, - 2007, 61(11), 1225-1232. - - """ - y, weight_array, pseudo_inverse = self._setup_polynomial( - data, weights, poly_order, calc_vander=True, calc_pinv=True, copy_weights=True - ) - sqrt_w = np.sqrt(weight_array) - if use_original: - y0 = y - - coef = pseudo_inverse @ (sqrt_w * y) - baseline = self.vandermonde @ coef - if mask_initial_peaks: - # use baseline + deviation since without deviation, half of y should be above baseline - weight_array[baseline + np.std(y - baseline) < y] = 0 - sqrt_w = np.sqrt(weight_array) - pseudo_inverse = np.linalg.pinv(sqrt_w[:, None] * self.vandermonde) - - tol_history = np.empty(max_iter) - for i in range(max_iter): - baseline_old = baseline - y = np.minimum(y0 if use_original else y, baseline) - coef = pseudo_inverse @ (sqrt_w * y) - baseline = self.vandermonde @ coef - calc_difference = relative_difference(baseline_old, baseline) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def imodpoly(self, data, poly_order=2, tol=1e-3, max_iter=250, weights=None, - use_original=False, mask_initial_peaks=True, return_coef=False, num_std=1): - """ - The improved modofied polynomial (IModPoly) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - use_original : bool, optional - If False (default), will compare the baseline of each iteration with - the y-values of that iteration [11]_ when choosing minimum values. If True, - will compare the baseline with the original y-values given by `data` [12]_. - mask_initial_peaks : bool, optional - If True (default), will mask any data where the initial baseline fit + - the standard deviation of the residual is less than measured data [13]_. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - num_std : float, optional - The number of standard deviations to include when thresholding. Default - is 1. Must be greater or equal to 0. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `num_std` is less than 0. - - Notes - ----- - Algorithm originally developed in [13]_. - - References - ---------- - .. [11] Gan, F., et al. Baseline correction by improved iterative polynomial - fitting with automatic threshold. Chemometrics and Intelligent - Laboratory Systems, 2006, 82, 59-65. - .. [12] Lieber, C., et al. Automated method for subtraction of fluorescence - from biological raman spectra. Applied Spectroscopy, 2003, 57(11), - 1363-1367. - .. [13] Zhao, J., et al. Automated Autofluorescence Background Subtraction - Algorithm for Biomedical Raman Spectroscopy, Applied Spectroscopy, - 2007, 61(11), 1225-1232. - - """ - if num_std < 0: - raise ValueError('num_std must be greater than or equal to 0') - - y, weight_array, pseudo_inverse = self._setup_polynomial( - data, weights, poly_order, calc_vander=True, calc_pinv=True, copy_weights=True - ) - sqrt_w = np.sqrt(weight_array) - if use_original: - y0 = y - - coef = pseudo_inverse @ (sqrt_w * y) - baseline = self.vandermonde @ coef - deviation = np.std(y - baseline) - if mask_initial_peaks: - weight_array[baseline + deviation < y] = 0 - sqrt_w = np.sqrt(weight_array) - pseudo_inverse = np.linalg.pinv(sqrt_w[:, None] * self.vandermonde) - - tol_history = np.empty(max_iter) - for i in range(max_iter): - y = np.minimum(y0 if use_original else y, baseline + num_std * deviation) - coef = pseudo_inverse @ (sqrt_w * y) - baseline = self.vandermonde @ coef - new_deviation = np.std(y - baseline) - # use new_deviation as dividing term in relative difference - calc_difference = relative_difference(new_deviation, deviation) - tol_history[i] = calc_difference - if calc_difference < tol: - break - deviation = new_deviation - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - # adapted from - # https://www.mathworks.com/matlabcentral/fileexchange/27429-background-correction; - # see license above - @_Algorithm._register(sort_keys=('weights',)) - def penalized_poly(self, data, poly_order=2, tol=1e-3, max_iter=250, weights=None, - cost_function='asymmetric_truncated_quadratic', threshold=None, - alpha_factor=0.99, return_coef=False): - """ - Fits a polynomial baseline using a non-quadratic cost function. - - The non-quadratic cost functions penalize residuals with larger values, - giving a more robust fit compared to normal least-squares. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - cost_function : str, optional - The non-quadratic cost function to minimize. Must indicate symmetry of the - method by appending 'a' or 'asymmetric' for asymmetric loss, and 's' or - 'symmetric' for symmetric loss. Default is 'asymmetric_truncated_quadratic'. - Available methods, and their associated reference, are: - - * 'asymmetric_truncated_quadratic'[14]_ - * 'symmetric_truncated_quadratic'[14]_ - * 'asymmetric_huber'[14]_ - * 'symmetric_huber'[14]_ - * 'asymmetric_indec'[15]_ - * 'symmetric_indec'[15]_ - - threshold : float, optional - The threshold value for the loss method, where the function goes from - quadratic loss (such as used for least squares) to non-quadratic. For - symmetric loss methods, residual values with absolute value less than - threshold will have quadratic loss. For asymmetric loss methods, residual - values less than the threshold will have quadratic loss. Default is None, - which sets `threshold` to one-tenth of the standard deviation of the input - data. - alpha_factor : float, optional - A value between 0 and 1 that controls the value of the penalty. Default is - 0.99. Typically should not need to change this value. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `alpha_factor` is not between 0 and 1. - - Notes - ----- - In baseline literature, this procedure is sometimes called "backcor". - - References - ---------- - .. [14] Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - .. [15] Liu, J., et al. Goldindec: A Novel Algorithm for Raman Spectrum Baseline - Correction. Applied Spectroscopy, 2015, 69(7), 834-842. - - """ - if not 0 < alpha_factor <= 1: - raise ValueError('alpha_factor must be between 0 and 1') - symmetric_loss, method = _identify_loss_method(cost_function) - loss_function = { - 'huber': _huber_loss, - 'truncated_quadratic': _truncated_quadratic_loss, - 'indec': _indec_loss - }[method] - - y, weight_array, pseudo_inverse = self._setup_polynomial( - data, weights, poly_order, calc_vander=True, calc_pinv=True - ) - if threshold is None: - threshold = np.std(y) / 10 - loss_kwargs = { - 'threshold': threshold, 'alpha_factor': alpha_factor, 'symmetric': symmetric_loss - } - - sqrt_w = np.sqrt(weight_array) - y = sqrt_w * y - - coef = pseudo_inverse @ y - baseline = self.vandermonde @ coef - tol_history = np.empty(max_iter) - for i in range(max_iter): - baseline_old = baseline - coef = pseudo_inverse @ (y + loss_function(y - sqrt_w * baseline, **loss_kwargs)) - baseline = self.vandermonde @ coef - calc_difference = relative_difference(baseline_old, baseline) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('weights', 'coef')) - def loess(self, data, fraction=0.2, total_points=None, poly_order=1, scale=3.0, - tol=1e-3, max_iter=10, symmetric_weights=False, use_threshold=False, - num_std=1, use_original=False, weights=None, return_coef=False, - conserve_memory=True, delta=0.0): - """ - Locally estimated scatterplot smoothing (LOESS). - - Performs polynomial regression at each data point using the nearest points. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - fraction : float, optional - The fraction of N data points to include for the fitting on each point. - Default is 0.2. Not used if `total_points` is not None. - total_points : int, optional - The total number of points to include for the fitting on each point. Default - is None, which will use `fraction` * N to determine the number of points. - scale : float, optional - A scale factor applied to the weighted residuals to control the robustness - of the fit. Default is 3.0, as used in [16]_. Note that the original loess - procedure in [17]_ used a `scale` of ~4.05. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 1. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 10. - symmetric_weights : bool, optional - If False (default), will apply weighting asymmetrically, with residuals - < 0 having a weight of 1, according to [16]_. If True, will apply weighting - the same for both positive and negative residuals, which is regular LOESS. - If `use_threshold` is True, this parameter is ignored. - use_threshold : bool, optional - If False (default), will compute weights each iteration to perform the - robust fitting, which is regular LOESS. If True, will apply a threshold - on the data being fit each iteration, based on the maximum values of the - data and the fit baseline, as proposed by [18]_, similar to the modpoly - and imodpoly techniques. - num_std : float, optional - The number of standard deviations to include when thresholding. Default - is 1, which is the value used for the imodpoly technique. Only used if - `use_threshold` is True. - use_original : bool, optional - If False (default), will compare the baseline of each iteration with - the y-values of that iteration [19]_ when choosing minimum values for - thresholding. If True, will compare the baseline with the original - y-values given by `data` [20]_. Only used if `use_threshold` is True. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - conserve_memory : bool, optional - If False, will cache the distance-weighted kernels for each value - in `x_data` on the first iteration and reuse them on subsequent iterations to - save time. The shape of the array of kernels is (len(`x_data`), `total_points`). - If True (default), will recalculate the kernels each iteration, which uses very - little memory, but is slower. Can usually set to False unless `x_data` and`total_points` - are quite large and the function causes memory issues when cacheing the kernels. If - numba is installed, there is no significant time difference since the calculations are - sped up. - delta : float, optional - If `delta` is > 0, will skip all but the last x-value in the range x_last + `delta`, - where x_last is the last x-value to be fit using weighted least squares, and instead - use linear interpolation to calculate the fit for those x-values (same behavior as in - statsmodels [21]_ and Cleveland's original Fortran lowess implementation [22]_). - Fits all x-values if `delta` is <= 0. Default is 0.0. Note that `x_data` is scaled to - fit in the range [-1, 1], so `delta` should likewise be scaled. For example, if the - desired `delta` value was ``0.01 * (max(x_data) - min(x_data))``, then the - correctly scaled `delta` would be 0.02 (ie. ``0.01 * (1 - (-1))``). - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. Does NOT contain the - individual distance-weighted kernels for each x-value. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (N, poly_order + 1) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a polynomial - using numpy.polynomial.polynomial.Polynomial(). If `delta` is > 0, the - coefficients for any skipped x-value will all be 0. - - Raises - ------ - ValueError - Raised if the number of points per window for the fitting is less than - `poly_order` + 1 or greater than the total number of points, or if the - values in `self.x` are not strictly increasing. - - Notes - ----- - The iterative, robust, aspect of the fitting can be achieved either through - reweighting based on the residuals (the typical usage), or thresholding the - fit data based on the residuals, as proposed by [18]_, similar to the modpoly - and imodpoly techniques. - - In baseline literature, this procedure is sometimes called "rbe", meaning - "robust baseline estimate". - - References - ---------- - .. [16] Ruckstuhl, A.F., et al. Baseline subtraction using robust local - regression estimation. J. Quantitative Spectroscopy and Radiative - Transfer, 2001, 68, 179-193. - .. [17] Cleveland, W. Robust locally weighted regression and smoothing - scatterplots. Journal of the American Statistical Association, - 1979, 74(368), 829-836. - .. [18] Komsta, Ł. Comparison of Several Methods of Chromatographic - Baseline Removal with a New Approach Based on Quantile Regression. - Chromatographia, 2011, 73, 721-731. - .. [19] Gan, F., et al. Baseline correction by improved iterative polynomial - fitting with automatic threshold. Chemometrics and Intelligent - Laboratory Systems, 2006, 82, 59-65. - .. [20] Lieber, C., et al. Automated method for subtraction of fluorescence - from biological raman spectra. Applied Spectroscopy, 2003, 57(11), - 1363-1367. - .. [21] https://github.com/statsmodels/statsmodels. - .. [22] https://www.netlib.org/go (lowess.f is the file). - - """ - if np.any(self.x[1:] < self.x[:-1]): - raise ValueError('x must be strictly increasing') - - if total_points is None: - total_points = ceil(fraction * self._len) - if total_points < poly_order + 1: - raise ValueError('total points must be greater than polynomial order + 1') - elif total_points > self._len: - raise ValueError(( - 'points per window is higher than total number of points; lower either ' - '"fraction" or "total_points"' - )) - elif poly_order > 2: - warnings.warn( - ('polynomial orders greater than 2 can have numerical issues;' - ' consider using a polynomial order of 1 or 2 instead'), - ParameterWarning, stacklevel=2 - ) - - y, weight_array = self._setup_polynomial(data, weights, poly_order, calc_vander=True) - if use_original: - y0 = y - - # x is the scaled version of self.x to fit within the [-1, 1] domain - x = np.polynomial.polyutils.mapdomain(self.x, self.x_domain, np.array([-1., 1.])) - # find the indices for fitting beforehand so that the fitting can be done - # in parallel; cast delta as float so numba does not have to compile for - # both int and float - windows, fits, skips = _determine_fits(x, self._len, total_points, float(delta)) - - # np.polynomial.polynomial.polyvander returns a Fortran-ordered array, which - # when matrix multiplied with the C-ordered coefficient array gives a warning - # when using numba, so convert Vandermonde matrix to C-ordering. - self.vandermonde = np.ascontiguousarray(self.vandermonde) - - baseline = y - coefs = np.zeros((self._len, poly_order + 1)) - tol_history = np.empty(max_iter + 1) - sqrt_w = np.sqrt(weight_array) - # do max_iter + 1 since a max_iter of 0 would return y as baseline otherwise - for i in range(max_iter + 1): - baseline_old = baseline - if conserve_memory: - baseline = _loess_low_memory( - x, y, sqrt_w, coefs, self.vandermonde, self._len, windows, fits - ) - elif i == 0: - kernels, baseline = _loess_first_loop( - x, y, sqrt_w, coefs, self.vandermonde, total_points, self._len, windows, fits - ) - else: - baseline = _loess_nonfirst_loops( - y, sqrt_w, coefs, self.vandermonde, kernels, windows, self._len, fits - ) - - _fill_skips(x, baseline, skips) - - calc_difference = relative_difference(baseline_old, baseline) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - if use_threshold: - y = np.minimum( - y0 if use_original else y, baseline + num_std * np.std(y - baseline) - ) - else: - residual = y - baseline - # TODO median_absolute_value can be 0 if more than half of residuals are - # 0 (perfect fit); can that ever really happen? if so, should prevent dividing by 0 - sqrt_w = _tukey_square( - residual / _median_absolute_value(residual), scale, symmetric_weights - ) - - params = {'weights': sqrt_w**2, 'tol_history': tol_history[:i + 1]} - if return_coef: - # TODO maybe leave out the coefficients from the rest of the calculations - # since they are otherwise unused, and just fit x vs baseline here; would - # save a little memory; is providing coefficients for loess even useful? - params['coef'] = np.array([_convert_coef(coef, self.x_domain) for coef in coefs]) - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def quant_reg(self, data, poly_order=2, quantile=0.05, tol=1e-6, max_iter=250, - weights=None, eps=None, return_coef=False): - """ - Approximates the baseline of the data using quantile regression. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - quantile : float, optional - The quantile at which to fit the baseline. Default is 0.05. - tol : float, optional - The exit criteria. Default is 1e-6. For extreme quantiles (`quantile` < 0.01 - or `quantile` > 0.99), may need to use a lower value to get a good fit. - max_iter : int, optional - The maximum number of iterations. Default is 250. For extreme quantiles - (`quantile` < 0.01 or `quantile` > 0.99), may need to use a higher value to - ensure convergence. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - eps : float, optional - A small value added to the square of the residual to prevent dividing by 0. - Default is None, which uses the square of the maximum-absolute-value of the - fit each iteration multiplied by 1e-6. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input `x_data` and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `quantile` is not between 0 and 1. - - Notes - ----- - Application of quantile regression for baseline fitting ss described in [23]_. - - Performs quantile regression using iteratively reweighted least squares (IRLS) - as described in [24]_. - - References - ---------- - .. [23] Komsta, Ł. Comparison of Several Methods of Chromatographic - Baseline Removal with a New Approach Based on Quantile Regression. - Chromatographia, 2011, 73, 721-731. - .. [24] Schnabel, S., et al. Simultaneous estimation of quantile curves using - quantile sheets. AStA Advances in Statistical Analysis, 2013, 97, 77-87. - - """ - # TODO provide a way to estimate best poly_order based on AIC like in Komsta? could be - # useful for all polynomial methods; maybe could be an optimizer function - if not 0 < quantile < 1: - raise ValueError('quantile must be between 0 and 1.') - - y, weight_array = self._setup_polynomial(data, weights, poly_order, calc_vander=True) - # estimate first iteration using least squares - sqrt_w = np.sqrt(weight_array) - coef = np.linalg.lstsq(self.vandermonde * sqrt_w[:, None], y * sqrt_w, None)[0] - baseline = self.vandermonde @ coef - tol_history = np.empty(max_iter) - for i in range(max_iter): - baseline_old = baseline - sqrt_w = np.sqrt(_weighting._quantile(y, baseline, quantile, eps)) - coef = np.linalg.lstsq(self.vandermonde * sqrt_w[:, None], y * sqrt_w, None)[0] - baseline = self.vandermonde @ coef - # relative_difference(baseline_old, baseline, 1) gives nearly same result and - # the l2 norm is faster to calculate, so use that instead of l1 norm - calc_difference = relative_difference(baseline_old, baseline) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - params = {'weights': sqrt_w**2, 'tol_history': tol_history[:i + 1]} - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def goldindec(self, data, poly_order=2, tol=1e-3, max_iter=250, weights=None, - cost_function='asymmetric_indec', peak_ratio=0.5, alpha_factor=0.99, - tol_2=1e-3, tol_3=1e-6, max_iter_2=100, return_coef=False): - """ - Fits a polynomial baseline using a non-quadratic cost function. - - The non-quadratic cost functions penalize residuals with larger values, - giving a more robust fit compared to normal least-squares. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria for the fitting with a given threshold value. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations for fitting a threshold value. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - cost_function : str, optional - The non-quadratic cost function to minimize. Unlike :func:`.penalized_poly`, - this function only works with asymmetric cost functions, so the symmetry prefix - ('a' or 'asymmetric') is optional (eg. 'indec' and 'a_indec' are the same). Default - is 'asymmetric_indec'. Available methods, and their associated reference, are: - - * 'asymmetric_indec'[25]_ - * 'asymmetric_truncated_quadratic'[26]_ - * 'asymmetric_huber'[26]_ - - peak_ratio : float, optional - A value between 0 and 1 that designates how many points in the data belong - to peaks. Values are valid within ~10% of the actual peak ratio. Default is 0.5. - alpha_factor : float, optional - A value between 0 and 1 that controls the value of the penalty. Default is - 0.99. Typically should not need to change this value. - tol_2 : float, optional - The exit criteria for the difference between the optimal up-down ratio (number of - points above 0 in the residual compared to number of points below 0) and the up-down - ratio for a given threshold value. Default is 1e-3. - tol_3 : float, optional - The exit criteria for the relative change in the threshold value. Default is 1e-6. - max_iter_2 : float, optional - The number of iterations for iterating between different threshold values. - Default is 100. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray, shape (J, K) - An array containing the calculated tolerance values for each iteration - of both threshold values and fit values. Index 0 are the tolerence values - for the difference in up-down ratios, index 1 are the tolerance values for - the relative change in the threshold, and indices >= 2 are the tolerance values - for each fit. All values that were not used in fitting have values of 0. Shape J - is 2 plus the number of iterations for the threshold to converge (related to - `max_iter_2`, `tol_2`, `tol_3`), and shape K is the maximum of the number of - iterations for the threshold and the maximum number of iterations for all of - the fits of the various threshold values (related to `max_iter` and `tol`). - * 'threshold' : float - The optimal threshold value. Could be used in :func:`.penalized_poly` - for fitting other similar data. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `alpha_factor` or `peak_ratio` are not between 0 and 1, or if the - specified cost function is symmetric. - - References - ---------- - .. [25] Liu, J., et al. Goldindec: A Novel Algorithm for Raman Spectrum Baseline - Correction. Applied Spectroscopy, 2015, 69(7), 834-842. - .. [26] Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - - """ - if not 0 < alpha_factor <= 1: - raise ValueError('alpha_factor must be between 0 and 1') - elif not 0 < peak_ratio < 1: - raise ValueError('peak_ratio must be between 0 and 1') - try: - symmetric_loss, method = _identify_loss_method(cost_function) - except ValueError: # do not require a prefix since cost must be asymmetric - symmetric_loss, method = _identify_loss_method('a_' + cost_function) - if symmetric_loss: - # symmetric cost functions don't work due to how the up-down ratio vs - # peak_ratio function was created in the reference; in theory, could simulate - # spectra with both positive and negative peaks following the reference - # and build another empirical function, but would likely need to also - # add other parameters detailing the percent of positive vs negative peaks, - # etc., so it's not worth the effort - raise ValueError('goldindec only works for asymmetric cost functions') - - loss_function = { - 'huber': _huber_loss, - 'truncated_quadratic': _truncated_quadratic_loss, - 'indec': _indec_loss - }[method] - y, weight_array, pseudo_inverse = self._setup_polynomial( - data, weights, poly_order, calc_vander=True, calc_pinv=True - ) - up_down_ratio_goal = ( - 0.7679 + 11.2358 * peak_ratio - 39.7064 * peak_ratio**2 + 92.3583 * peak_ratio**3 - ) - # TODO reference states threshold must be <= 2 for half-quadratic minimization to - # be valid for indec cost function, and normalized y so that threshold is always <= 2; - # however, it seems to work fine without normalization; just be aware in case errors - # occur, may have to normalize y in both this function and penalized_poly - sqrt_w = np.sqrt(weight_array) - y_fit = sqrt_w * y - - coef = pseudo_inverse @ y_fit - initial_baseline = self.vandermonde @ coef - - a = 0 - # reference used b=1, but normalized y before fitting; instead, set b as max of - # initial residual - b = abs((y - initial_baseline).max()) - threshold = a + 0.618 * (b - a) - loss_kwargs = { - 'threshold': threshold, 'alpha_factor': alpha_factor, - 'symmetric': symmetric_loss - } - # have to use zeros rather than empty for tol_history since each inner fit may - # have a different number of iterations - tol_history = np.zeros((max_iter_2 + 2, max(max_iter, max_iter_2))) - j_max = 0 - for i in range(max_iter_2): - baseline = initial_baseline - for j in range(max_iter): - baseline_old = baseline - coef = pseudo_inverse @ ( - y_fit + loss_function(y_fit - sqrt_w * baseline, **loss_kwargs) - ) - baseline = self.vandermonde @ coef - calc_difference = relative_difference(baseline_old, baseline) - tol_history[i + 2, j] = calc_difference - if calc_difference < tol: - break - if j > j_max: - j_max = j - - up_count = (y > baseline).sum() - up_down_ratio = up_count / max(1, self._len - up_count) - calc_difference = up_down_ratio - up_down_ratio_goal - tol_history[0, i] = calc_difference - if calc_difference > tol_2: - a = threshold - elif calc_difference < -tol_2: - b = threshold - else: - break - threshold = a + 0.618 * (b - a) - # this exit criteria was not stated in the reference, but the change in threshold - # becomes zero fairly quickly, so need to also exit rather than needlessly - # continuing to calculate with the same threshold value - calc_difference = relative_difference(loss_kwargs['threshold'], threshold) - tol_history[1, i] = calc_difference - if calc_difference < tol_3: - break - loss_kwargs['threshold'] = threshold - - params = { - 'weights': weight_array, 'tol_history': tol_history[:i + 3, :max(i, j_max) + 1], - 'threshold': loss_kwargs['threshold'] - } - if return_coef: - params['coef'] = _convert_coef(coef, self.x_domain) - - return baseline, params - - -_polynomial_wrapper = _class_wrapper(_Polynomial) - - -@_polynomial_wrapper -def poly(data, x_data=None, poly_order=2, weights=None, return_coef=False): - """ - Computes a polynomial that fits the baseline of the data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'coef': numpy.ndarray, shape (poly_order,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Notes - ----- - To only fit regions without peaks, supply a weight array with zero values - at the indices where peaks are located. - - """ - - -@_polynomial_wrapper -def modpoly(data, x_data=None, poly_order=2, tol=1e-3, max_iter=250, weights=None, - use_original=False, mask_initial_peaks=False, return_coef=False): - """ - The modified polynomial (ModPoly) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - use_original : bool, optional - If False (default), will compare the baseline of each iteration with - the y-values of that iteration [1]_ when choosing minimum values. If True, - will compare the baseline with the original y-values given by `data` [2]_. - mask_initial_peaks : bool, optional - If True, will mask any data where the initial baseline fit + the standard - deviation of the residual is less than measured data [3]_. Default is False. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Notes - ----- - Algorithm originally developed in [2]_ and then slightly modified in [1]_. - - References - ---------- - .. [1] Gan, F., et al. Baseline correction by improved iterative polynomial - fitting with automatic threshold. Chemometrics and Intelligent - Laboratory Systems, 2006, 82, 59-65. - .. [2] Lieber, C., et al. Automated method for subtraction of fluorescence - from biological raman spectra. Applied Spectroscopy, 2003, 57(11), - 1363-1367. - .. [3] Zhao, J., et al. Automated Autofluorescence Background Subtraction - Algorithm for Biomedical Raman Spectroscopy, Applied Spectroscopy, - 2007, 61(11), 1225-1232. - - """ - - -@_polynomial_wrapper -def imodpoly(data, x_data=None, poly_order=2, tol=1e-3, max_iter=250, weights=None, - use_original=False, mask_initial_peaks=True, return_coef=False, num_std=1): - """ - The improved modofied polynomial (IModPoly) baseline algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - use_original : bool, optional - If False (default), will compare the baseline of each iteration with - the y-values of that iteration [4]_ when choosing minimum values. If True, - will compare the baseline with the original y-values given by `data` [5]_. - mask_initial_peaks : bool, optional - If True (default), will mask any data where the initial baseline fit + - the standard deviation of the residual is less than measured data [6]_. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - num_std : float, optional - The number of standard deviations to include when thresholding. Default - is 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Notes - ----- - Algorithm originally developed in [6]_. - - References - ---------- - .. [4] Gan, F., et al. Baseline correction by improved iterative polynomial - fitting with automatic threshold. Chemometrics and Intelligent - Laboratory Systems, 2006, 82, 59-65. - .. [5] Lieber, C., et al. Automated method for subtraction of fluorescence - from biological raman spectra. Applied Spectroscopy, 2003, 57(11), - 1363-1367. - .. [6] Zhao, J., et al. Automated Autofluorescence Background Subtraction - Algorithm for Biomedical Raman Spectroscopy, Applied Spectroscopy, - 2007, 61(11), 1225-1232. - - """ - - -# adapted from (https://www.mathworks.com/matlabcentral/fileexchange/27429-background-correction); -# see license above -def _huber_loss(residual, threshold=1.0, alpha_factor=0.99, symmetric=True): - """ - The Huber non-quadratic cost function. - - Parameters - ---------- - residual : numpy.ndarray, shape (N,) - The residual array. - threshold : float, optional - Any residual values below the threshold are given quadratic loss. - Default is 1.0. - alpha_factor : float, optional - The scale between 0 and 1 to multiply the cost function's alpha_max - value (see Notes below). Default is 0.99. - symmetric : bool, optional - If True (default), the cost function is symmetric and applies the same - weighting for positive and negative values. If False, will apply weights - asymmetrically so that only positive weights are given the non-quadratic - weigting and negative weights have normal, quadratic weighting. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The weight array. - - Notes - ----- - The returned result is - - -residual + alpha_factor * alpha_max * phi'(residual) - - where phi'(x) is the derivative of the huber loss function, phi(x). - - References - ---------- - Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - - """ - alpha = alpha_factor * 0.5 # alpha_max for huber is 0.5 - if symmetric: - mask = (np.abs(residual) < threshold) - weights = ( - mask * residual * (2 * alpha - 1) - + (~mask) * 2 * alpha * threshold * np.sign(residual) - ) - else: - mask = (residual < threshold) - weights = ( - mask * residual * (2 * alpha - 1) - + (~mask) * (2 * alpha * threshold - residual) - ) - return weights - - -# adapted from (https://www.mathworks.com/matlabcentral/fileexchange/27429-background-correction); -# see license above -def _truncated_quadratic_loss(residual, threshold=1.0, alpha_factor=0.99, symmetric=True): - """ - The Truncated-Quadratic non-quadratic cost function. - - Parameters - ---------- - residual : numpy.ndarray, shape (N,) - The residual array. - threshold : float, optional - Any residual values below the threshold are given quadratic loss. - Default is 1.0. - alpha_factor : float, optional - The scale between 0 and 1 to multiply the cost function's alpha_max - value (see Notes below). Default is 0.99. - symmetric : bool, optional - If True (default), the cost function is symmetric and applies the same - weighting for positive and negative values. If False, will apply weights - asymmetrically so that only positive weights are given the non-quadratic - weigting and negative weights have normal, quadratic weighting. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The weight array. - - Notes - ----- - The returned result is - - -residual + alpha_factor * alpha_max * phi'(residual) - - where phi'(x) is the derivative of the truncated quadratic function, phi(x). - - References - ---------- - Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - - """ - alpha = alpha_factor * 0.5 # alpha_max for truncated quadratic is 0.5 - if symmetric: - mask = (np.abs(residual) < threshold) - else: - mask = (residual < threshold) - return mask * residual * (2 * alpha - 1) - (~mask) * residual - - -def _indec_loss(residual, threshold=1.0, alpha_factor=0.99, symmetric=True): - """ - The Indec non-quadratic cost function. - - Parameters - ---------- - residual : numpy.ndarray, shape (N,) - The residual array. - threshold : float, optional - Any residual values below the threshold are given quadratic loss. - Default is 1.0. - alpha_factor : float, optional - The scale between 0 and 1 to multiply the cost function's alpha_max - value (see Notes below). Default is 0.99. - symmetric : bool, optional - If True (default), the cost function is symmetric and applies the same - weighting for positive and negative values. If False, will apply weights - asymmetrically so that only positive weights are given the non-quadratic - weigting and negative weights have normal, quadratic weighting. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The weight array. - - Notes - ----- - The returned result is - - -residual + alpha_factor * alpha_max * phi'(residual) - - where phi'(x) is the derivative of the Indec function, phi(x). - - References - ---------- - Liu, J., et al. Goldindec: A Novel Algorithm for Raman Spectrum Baseline - Correction. Applied Spectroscopy, 2015, 69(7), 834-842. - - Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - - """ - alpha = alpha_factor * 0.5 # alpha_max for indec is 0.5 - if symmetric: - mask = (np.abs(residual) < threshold) - multiple = np.sign(residual) - else: - mask = (residual < threshold) - # multiple=1 is same as sign(residual) since residual is always > 0 - # for asymmetric case, but this allows not doing the sign calculation - multiple = 1 - weights = ( - mask * residual * (2 * alpha - 1) - - (~mask) * ( - residual + alpha * multiple * threshold**3 / np.maximum(2 * residual**2, _MIN_FLOAT) - ) - ) - return weights - - -def _identify_loss_method(loss_method): - """ - Identifies the symmetry for the given loss method. - - Parameters - ---------- - loss_method : str - The loss method to use. Should have the symmetry identifier as - the prefix. - - Returns - ------- - symmetric : bool - True if `loss_method` had 's_' or 'symmetric_' as the prefix, else False. - str - The input `loss_method` value without the first section that indicated - the symmetry. - - Raises - ------ - ValueError - Raised if the loss method does not have the correct form. - - """ - prefix, *split_method = loss_method.lower().split('_') - if prefix not in ('a', 's', 'asymmetric', 'symmetric') or not split_method: - raise ValueError('must specify loss function symmetry by appending "a_" or "s_"') - if prefix in ('a', 'asymmetric'): - symmetric = False - else: - symmetric = True - return symmetric, '_'.join(split_method) - - -# adapted from (https://www.mathworks.com/matlabcentral/fileexchange/27429-background-correction); -# see license above -@_polynomial_wrapper -def penalized_poly(data, x_data=None, poly_order=2, tol=1e-3, max_iter=250, - weights=None, cost_function='asymmetric_truncated_quadratic', - threshold=None, alpha_factor=0.99, return_coef=False): - """ - Fits a polynomial baseline using a non-quadratic cost function. - - The non-quadratic cost functions penalize residuals with larger values, - giving a more robust fit compared to normal least-squares. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - cost_function : str, optional - The non-quadratic cost function to minimize. Must indicate symmetry of the - method by appending 'a' or 'asymmetric' for asymmetric loss, and 's' or - 'symmetric' for symmetric loss. Default is 'asymmetric_truncated_quadratic'. - Available methods, and their associated reference, are: - - * 'asymmetric_truncated_quadratic'[7]_ - * 'symmetric_truncated_quadratic'[7]_ - * 'asymmetric_huber'[7]_ - * 'symmetric_huber'[7]_ - * 'asymmetric_indec'[8]_ - * 'symmetric_indec'[8]_ - - threshold : float, optional - The threshold value for the loss method, where the function goes from - quadratic loss (such as used for least squares) to non-quadratic. For - symmetric loss methods, residual values with absolute value less than - threshold will have quadratic loss. For asymmetric loss methods, residual - values less than the threshold will have quadratic loss. Default is None, - which sets `threshold` to one-tenth of the standard deviation of the input - data. - alpha_factor : float, optional - A value between 0 and 1 that controls the value of the penalty. Default is - 0.99. Typically should not need to change this value. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `alpha_factor` is not between 0 and 1. - - Notes - ----- - In baseline literature, this procedure is sometimes called "backcor". - - References - ---------- - .. [7] Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - .. [8] Liu, J., et al. Goldindec: A Novel Algorithm for Raman Spectrum Baseline - Correction. Applied Spectroscopy, 2015, 69(7), 834-842. - - """ - - -def _tukey_square(residual, scale=3, symmetric=False): - """ - The square root of Tukey's bisquare function. - - Parameters - ---------- - residual : numpy.ndarray, shape (N,) - The residual array of the fit. - scale : float, optional - A scale factor applied to the weighted residuals to control the - robustness of the fit. Default is 3.0. - symmetric : bool, optional - If False (default), will apply weighting asymmetrically, with residuals - < 0 having full weight. If True, will apply weighting the same for both - positive and negative residuals, which is regular LOESS. - - Returns - ------- - weights : numpy.ndarray, shape (N,) - The weighting array. - - Notes - ----- - The function is technically sqrt(Tukey's bisquare) since the outer - power of 2 is not performed. This is intentional, so that the square - root for weighting in least squares does not need to be done, speeding - up the calculation. - - References - ---------- - Ruckstuhl, A.F., et al., Baseline subtraction using robust local regression - estimation. J. Quantitative Spectroscopy and Radiative Transfer, 2001, 68, - 179-193. - - """ - if symmetric: - inner = residual / scale - weights = np.maximum(0, 1 - inner * inner) - else: - weights = np.ones_like(residual) - mask = residual > 0 - inner = residual[mask] / scale - weights[mask] = np.maximum(0, 1 - inner * inner) - return weights - - -def _median_absolute_value(values): - """ - Computes the median absolute value (MAV) of an array. - - Parameters - ---------- - values : array-like - The array of values to use for the calculation. - - Returns - ------- - float - The scaled median absolute value for the input array. - - Notes - ----- - The 1/0.6744897501960817 scale factor is to make the result comparable to the - standard deviation of a Gaussian distribution. The divisor is obtained by - calculating the value at which the cumulative distribution function of a Gaussian - distribution is 0.75 (see https://en.wikipedia.org/wiki/Median_absolute_deviation), - which can be obtained by:: - - from scipy.special import ndtri - ndtri(0.75) # equals 0.6744897501960817 - - To calculate the median absolute difference (MAD) using this function, simply do:: - - _median_absolute_value(values - np.median(values)) - - References - ---------- - Ruckstuhl, A.F., et al., Baseline subtraction using robust local regression - estimation. J. Quantitative Spectroscopy and Radiative Transfer, 2001, 68, - 179-193. - - https://en.wikipedia.org/wiki/Median_absolute_deviation. - - """ - return np.median(np.abs(values)) / 0.6744897501960817 - - -@jit(nopython=True, cache=True) -def _loess_solver(AT, b): - """ - Solves the equation `A x = b` given `A.T` and `b`. - - Parameters - ---------- - AT : numpy.ndarray, shape (M, N) - The transposed `A` matrix. - b : numpy.ndarray, shape (N,) - The `b` array. - - Returns - ------- - numpy.ndarray, shape (N,) - The solution to the normal equation. - - Notes - ----- - Uses np.linalg.solve (which uses LU decomposition) rather than np.linalg.lstsq - (which uses SVD) since solve is ~30-60% faster. np.linalg.solve requires ``A.T * A``, - which squares the condition number of ``A``, but on tested datasets the relative - difference when using solve vs lstsq (using np.allclose) is ~1e-10 to 1e-13 for - poly_orders of 1 or 2, which seems fine; the relative differences increase to - ~1e-6 to 1e-9 for a poly_order of 3, and ~1e-4 to 1e-6 for a poly_order of 4, but - loess should use a poly_order <= 2, so that should not be a problem. - - """ - return np.linalg.solve(AT.dot(AT.T), AT.dot(b)) - - -@jit(nopython=True, cache=True, parallel=True) -def _fill_skips(x, baseline, skips): - """ - Fills in the skipped baseline points using linear interpolation. - - Parameters - ---------- - x : numpy.ndarray - The array of x-values. - baseline : numpy.ndarray - The array of baseline values with all fit points allocated. All skipped points - will be filled in using interpolation. - skips : numpy.ndarray, shape (G, 2) - The array of left and right indices that define the windows for interpolation, - with length G being the number of interpolation segments. Indices are set such - that `baseline[skips[i][0]:skips[i][1]]` will have fitted values at the first - and last indices and all other values (the slice [1:-1]) will be calculated by - interpolation. - - Notes - ----- - All changes to `baseline` are done inplace. - - """ - for i in prange(skips.shape[0]): - window = skips[i] - left = window[0] - right = window[1] - _interp_inplace(x[left:right], baseline[left:right], baseline[left], baseline[right - 1]) - - -# adapted from (https://gist.github.com/agramfort/850437); see license above -@jit(nopython=True, cache=True, parallel=True) -def _loess_low_memory(x, y, weights, coefs, vander, num_x, windows, fits): - """ - A version of loess that uses near constant memory. - - The distance-weighted kernel for each x-value is computed each loop, rather - than cached, so memory usage is low but the calculation is slightly slower. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values of the measured data, with N data points. - y : numpy.ndarray, shape (N,) - The y-values of the measured data, with N points. - weights : numpy.ndarray, shape (N,) - The array of weights. - coefs : numpy.ndarray, shape (N, poly_order + 1) - The array of polynomial coefficients (with polynomial order poly_order), - for each value in `x`. - vander : numpy.ndarray, shape (N, poly_order + 1) - The Vandermonde matrix for the `x` array. - num_x : int - The number of data points in `x`, also known as N. - windows : numpy.ndarray, shape (F, 2) - An array of left and right indices that define the fitting window for each fit - x-value. The length is F, which is the total number of fit points. If `fit_dx` - is <= 0, F is equal to N, the total number of x-values. - fits : numpy.ndarray, shape (F,) - The array of indices indicating which x-values to fit. - - Notes - ----- - The coefficient array, `coefs`, is modified inplace. - - """ - baseline = np.empty(num_x) - y_fit = y * weights - vander_fit = vander.T * weights - for idx in prange(fits.shape[0]): - i = fits[idx] - window = windows[idx] - left = window[0] - right = window[1] - - difference = np.abs(x[left:right] - x[i]) - difference = difference / max(difference[0], difference[-1]) - difference = difference * difference * difference - difference = 1 - difference - kernel = np.sqrt(difference * difference * difference) - - coef = _loess_solver( - kernel * vander_fit[:, left:right], kernel * y_fit[left:right] - ) - baseline[i] = vander[i].dot(coef) - coefs[i] = coef - - return baseline - - -# adapted from (https://gist.github.com/agramfort/850437); see license above -@jit(nopython=True, cache=True, parallel=True) -def _loess_first_loop(x, y, weights, coefs, vander, total_points, num_x, windows, fits): - """ - The initial fit for loess that also caches the window values for each x-value. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values of the measured data, with N data points. - y : numpy.ndarray, shape (N,) - The y-values of the measured data, with N points. - weights : numpy.ndarray, shape (N,) - The array of weights. - coefs : numpy.ndarray, shape (N, poly_order + 1) - The array of polynomial coefficients (with polynomial order poly_order), - for each value in `x`. - vander : numpy.ndarray, shape (N, poly_order + 1) - The Vandermonde matrix for the `x` array. - total_points : int - The number of points to include when fitting each x-value. - num_x : int - The number of data points in `x`, also known as N. - windows : numpy.ndarray, shape (F, 2) - An array of left and right indices that define the fitting window for each fit - x-value. The length is F, which is the total number of fit points. If `fit_dx` - is <= 0, F is equal to N, the total number of x-values. - fits : numpy.ndarray, shape (F,) - The array of indices indicating which x-values to fit. - - Returns - ------- - kernels : numpy.ndarray, shape (num_x, total_points) - The array containing the distance-weighted kernel for each x-value. - - Notes - ----- - The coefficient array, `coefs`, is modified inplace. - - """ - kernels = np.empty((num_x, total_points)) - baseline = np.empty(num_x) - y_fit = y * weights - vander_fit = vander.T * weights - for idx in prange(fits.shape[0]): - i = fits[idx] - window = windows[idx] - left = window[0] - right = window[1] - - difference = np.abs(x[left:right] - x[i]) - difference = difference / max(difference[0], difference[-1]) - difference = difference * difference * difference - difference = 1 - difference - kernel = np.sqrt(difference * difference * difference) - - kernels[i] = kernel - coef = _loess_solver( - kernel * vander_fit[:, left:right], kernel * y_fit[left:right] - ) - baseline[i] = vander[i].dot(coef) - coefs[i] = coef - - return kernels, baseline - - -@jit(nopython=True, cache=True, parallel=True) -def _loess_nonfirst_loops(y, weights, coefs, vander, kernels, windows, num_x, fits): - """ - The loess fit to use after the first loop that uses the cached window values. - - Parameters - ---------- - y : numpy.ndarray, shape (N,) - The y-values of the measured data, with N points. - weights : numpy.ndarray, shape (N,) - The array of weights. - coefs : numpy.ndarray, shape (N, poly_order + 1) - The array of polynomial coefficients (with polynomial order poly_order), - for each value in `x`. - vander : numpy.ndarray, shape (N, poly_order + 1) - The Vandermonde matrix for the `x` array. - kernels : numpy.ndarray, shape (N, total_points) - The array containing the distance-weighted kernel for each x-value. Each - kernel has a length of total_points. - windows : numpy.ndarray, shape (F, 2) - An array of left and right indices that define the fitting window for each fit - x-value. The length is F, which is the total number of fit points. If `fit_dx` - is <= 0, F is equal to N, the total number of x-values. - num_x : int - The total number of values, N. - fits : numpy.ndarray, shape (F,) - The array of indices indicating which x-values to fit. - - Notes - ----- - The coefficient array, `coefs`, is modified inplace. - - """ - baseline = np.empty(num_x) - y_fit = y * weights - vander_fit = vander.T * weights - for idx in prange(fits.shape[0]): - i = fits[idx] - window = windows[idx] - left = window[0] - right = window[1] - kernel = kernels[i] - coef = _loess_solver( - kernel * vander_fit[:, left:right], kernel * y_fit[left:right] - ) - baseline[i] = vander[i].dot(coef) - coefs[i] = coef - - return baseline - - -@jit(nopython=True, cache=True) -def _determine_fits(x, num_x, total_points, delta): - """ - Determines the x-values to fit and the left and right indices for each fit x-value. - - The windows are set before fitting so that fitting can be done in parallel - when numba is installed, since the left and right indices would otherwise - need to be determined in order. Similarly, determining which x-values to fit would - not be able to be done in parallel since it requires knowledge of the last x-value - fit. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The array of x-values. - num_x : int - The total number of x-values, N. - total_points : int - The number of values to include in each fitting window. - delta : float - If `delta` is > 0, will skip all but the last x-value in the range x_last + `delta`, - where x_last is the last x-value to be fit. Fits all x-values if `delta` is <= 0. - - Returns - ------- - windows : numpy.ndarray, shape (F, 2) - An array of left and right indices that define the fitting window for each fit - x-value. The length is F, which is the total number of fit points. If `fit_dx` - is <= 0, F is equal to N, the total number of x-values. Indices are set such - that the number of values in `x[windows[i][0]:windows[i][1]] is equal to - `total_points`. - fits : numpy.ndarray, shape (F,) - The array of indices indicating which x-values to fit. - skips : numpy.ndarray, shape (G, 2) - The array of left and right indices that define the windows for interpolation, - with length G being the number of interpolation segments. G is 0 if `fit_dx` is - <= 0. Indices are set such that `baseline[skips[i][0]:skips[i][1]]` will have - fitted values at the first and last indices and all other values (the slice [1:-1]) - will be calculated by interpolation. - - Notes - ----- - The dtype `np.intp` is used for `fits`, `skips`, and `windows` to be consistent with - numpy since numpy internally uses that type when referring to indices. - - """ - # faster to allocate array and return only filled in sections - # rather than constanly appending to a list - if delta > 0: - check_fits = True - fits = np.empty(num_x, dtype=np.intp) - fits[0] = 0 # always fit first item - skips = np.empty((num_x, 2), dtype=np.intp) - else: - # TODO maybe use another function when fitting all points in order - # to skip the if check_fits check for every x-value; does it affect - # calculation time that much? - check_fits = False - # TODO once numba minimum version is >= 0.47, can use dtype kwarg in np.arange - fits = np.arange(num_x).astype(np.intp) - # numba cannot compile in nopython mode when directly creating - # np.array([], dtype=np.intp), so work-around by creating np.array([[0, 0]]) - # and then index with [:total_skips], which becomes np.array([]) - # since total_skips is 0 when delta is <= 0. - skips = np.array([[0, 0]], dtype=np.intp) - - windows = np.empty((num_x, 2), dtype=np.intp) - windows[0] = (0, total_points) - total_fits = 1 - total_skips = 0 - skip_start = 0 - skip_range = x[0] + delta - left = 0 - right = total_points - for i in range(1, num_x - 1): - x_val = x[i] - if check_fits: - # use x[i+1] rather than x[i] since it ensures that the last value within - # the range x_last_fit + delta is used; x[i+1] is also guranteed to be >= x[i] - if x[i + 1] < skip_range: - if not skip_start: - skip_start = i - continue - else: - skip_range = x_val + delta - fits[total_fits] = i - if skip_start: - skips[total_skips] = (skip_start - 1, i + 1) - total_skips += 1 - skip_start = 0 - - while right < num_x and x_val - x[left] > x[right] - x_val: - left += 1 - right += 1 - window = windows[total_fits] - window[0] = left - window[1] = right - total_fits += 1 - - if skip_start: # fit second to last x-value - fits[total_fits] = num_x - 2 - if x[-1] - x[-2] < x[-2] - x[num_x - total_points]: - windows[total_fits] = (num_x - total_points, num_x) - else: - windows[total_fits] = (num_x - total_points - 1, num_x - 1) - total_fits += 1 - skips[total_skips] = (skip_start - 1, num_x - 1) - total_skips += 1 - - # always fit last item - fits[total_fits] = num_x - 1 - windows[total_fits] = (num_x - total_points, num_x) - total_fits += 1 - - return windows[:total_fits], fits[:total_fits], skips[:total_skips] - - -@_polynomial_wrapper -def loess(data, x_data=None, fraction=0.2, total_points=None, poly_order=1, scale=3.0, - tol=1e-3, max_iter=10, symmetric_weights=False, use_threshold=False, num_std=1, - use_original=False, weights=None, return_coef=False, conserve_memory=True, delta=0.0): - """ - Locally estimated scatterplot smoothing (LOESS). - - Performs polynomial regression at each data point using the nearest points. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - fraction : float, optional - The fraction of N data points to include for the fitting on each point. - Default is 0.2. Not used if `total_points` is not None. - total_points : int, optional - The total number of points to include for the fitting on each point. Default - is None, which will use `fraction` * N to determine the number of points. - scale : float, optional - A scale factor applied to the weighted residuals to control the robustness - of the fit. Default is 3.0, as used in [9]_. Note that the original loess - procedure in [10]_ used a `scale` of ~4.05. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 1. - tol : float, optional - The exit criteria. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations. Default is 10. - symmetric_weights : bool, optional - If False (default), will apply weighting asymmetrically, with residuals - < 0 having a weight of 1, according to [9]_. If True, will apply weighting - the same for both positive and negative residuals, which is regular LOESS. - If `use_threshold` is True, this parameter is ignored. - use_threshold : bool, optional - If False (default), will compute weights each iteration to perform the - robust fitting, which is regular LOESS. If True, will apply a threshold - on the data being fit each iteration, based on the maximum values of the - data and the fit baseline, as proposed by [11]_, similar to the modpoly - and imodpoly techniques. - num_std : float, optional - The number of standard deviations to include when thresholding. Default - is 1, which is the value used for the imodpoly technique. Only used if - `use_threshold` is True. - use_original : bool, optional - If False (default), will compare the baseline of each iteration with - the y-values of that iteration [12]_ when choosing minimum values for - thresholding. If True, will compare the baseline with the original - y-values given by `data` [13]_. Only used if `use_threshold` is True. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - conserve_memory : bool, optional - If False, will cache the distance-weighted kernels for each value - in `x_data` on the first iteration and reuse them on subsequent iterations to - save time. The shape of the array of kernels is (len(`x_data`), `total_points`). - If True (default), will recalculate the kernels each iteration, which uses very - little memory, but is slower. Can usually set to False unless `x_data` and`total_points` - are quite large and the function causes memory issues when cacheing the kernels. If - numba is installed, there is no significant time difference since the calculations are - sped up. - delta : float, optional - If `delta` is > 0, will skip all but the last x-value in the range x_last + `delta`, - where x_last is the last x-value to be fit using weighted least squares, and instead - use linear interpolation to calculate the fit for those x-values (same behavior as in - statsmodels [14]_ and Cleveland's original Fortran lowess implementation [15]_). - Fits all x-values if `delta` is <= 0. Default is 0.0. Note that `x_data` is scaled to - fit in the range [-1, 1], so `delta` should likewise be scaled. For example, if the - desired `delta` value was ``0.01 * (max(x_data) - min(x_data))``, then the - correctly scaled `delta` would be 0.02 (ie. ``0.01 * (1 - (-1))``). - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. Does NOT contain the - individual distance-weighted kernels for each x-value. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (N, poly_order + 1) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a polynomial - using numpy.polynomial.polynomial.Polynomial(). If `delta` is > 0, the - coefficients for any skipped x-value will all be 0. - - Raises - ------ - ValueError - Raised if the number of points per window for the fitting is less than - `poly_order` + 1 or greater than the total number of points. - - Notes - ----- - The iterative, robust, aspect of the fitting can be achieved either through - reweighting based on the residuals (the typical usage), or thresholding the - fit data based on the residuals, as proposed by [11]_, similar to the modpoly - and imodpoly techniques. - - In baseline literature, this procedure is sometimes called "rbe", meaning - "robust baseline estimate". - - References - ---------- - .. [9] Ruckstuhl, A.F., et al. Baseline subtraction using robust local - regression estimation. J. Quantitative Spectroscopy and Radiative - Transfer, 2001, 68, 179-193. - .. [10] Cleveland, W. Robust locally weighted regression and smoothing - scatterplots. Journal of the American Statistical Association, - 1979, 74(368), 829-836. - .. [11] Komsta, Ł. Comparison of Several Methods of Chromatographic - Baseline Removal with a New Approach Based on Quantile Regression. - Chromatographia, 2011, 73, 721-731. - .. [12] Gan, F., et al. Baseline correction by improved iterative polynomial - fitting with automatic threshold. Chemometrics and Intelligent - Laboratory Systems, 2006, 82, 59-65. - .. [13] Lieber, C., et al. Automated method for subtraction of fluorescence - from biological raman spectra. Applied Spectroscopy, 2003, 57(11), - 1363-1367. - .. [14] https://github.com/statsmodels/statsmodels. - .. [15] https://www.netlib.org/go (lowess.f is the file). - - """ - - -@_polynomial_wrapper -def quant_reg(data, x_data=None, poly_order=2, quantile=0.05, tol=1e-6, max_iter=250, - weights=None, eps=None, return_coef=False): - """ - Approximates the baseline of the data using quantile regression. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - quantile : float, optional - The quantile at which to fit the baseline. Default is 0.05. - tol : float, optional - The exit criteria. Default is 1e-6. For extreme quantiles (`quantile` < 0.01 - or `quantile` > 0.99), may need to use a lower value to get a good fit. - max_iter : int, optional - The maximum number of iterations. Default is 250. For extreme quantiles - (`quantile` < 0.01 or `quantile` > 0.99), may need to use a higher value to - ensure convergence. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - eps : float, optional - A small value added to the square of the residual to prevent dividing by 0. - Default is None, which uses the square of the maximum-absolute-value of the - fit each iteration multiplied by 1e-6. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input `x_data` and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `quantile` is not between 0 and 1. - - Notes - ----- - Application of quantile regression for baseline fitting as described in [16]_. - - Performs quantile regression using iteratively reweighted least squares (IRLS) - as described in [17]_. - - References - ---------- - .. [16] Komsta, Ł. Comparison of Several Methods of Chromatographic - Baseline Removal with a New Approach Based on Quantile Regression. - Chromatographia, 2011, 73, 721-731. - .. [17] Schnabel, S., et al. Simultaneous estimation of quantile curves using - quantile sheets. AStA Advances in Statistical Analysis, 2013, 97, 77-87. - - """ - - -@_polynomial_wrapper -def goldindec(data, x_data=None, poly_order=2, tol=1e-3, max_iter=250, weights=None, - cost_function='asymmetric_indec', peak_ratio=0.5, alpha_factor=0.99, - tol_2=1e-3, tol_3=1e-6, max_iter_2=100, return_coef=False): - """ - Fits a polynomial baseline using a non-quadratic cost function. - - The non-quadratic cost functions penalize residuals with larger values, - giving a more robust fit compared to normal least-squares. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - poly_order : int, optional - The polynomial order for fitting the baseline. Default is 2. - tol : float, optional - The exit criteria for the fitting with a given threshold value. Default is 1e-3. - max_iter : int, optional - The maximum number of iterations for fitting a threshold value. Default is 250. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then will be an array with - size equal to N and all values set to 1. - cost_function : str, optional - The non-quadratic cost function to minimize. Unlike :func:`.penalized_poly`, - this function only works with asymmetric cost functions, so the symmetry prefix - ('a' or 'asymmetric') is optional (eg. 'indec' and 'a_indec' are the same). Default - is 'asymmetric_indec'. Available methods, and their associated reference, are: - - * 'asymmetric_indec'[18]_ - * 'asymmetric_truncated_quadratic'[19]_ - * 'asymmetric_huber'[19]_ - - peak_ratio : float, optional - A value between 0 and 1 that designates how many points in the data belong - to peaks. Values are valid within ~10% of the actual peak ratio. Default is 0.5. - alpha_factor : float, optional - A value between 0 and 1 that controls the value of the penalty. Default is - 0.99. Typically should not need to change this value. - tol_2 : float, optional - The exit criteria for the difference between the optimal up-down ratio (number of - points above 0 in the residual compared to number of points below 0) and the up-down - ratio for a given threshold value. Default is 1e-3. - tol_3 : float, optional - The exit criteria for the relative change in the threshold value. Default is 1e-6. - max_iter_2 : float, optional - The number of iterations for iterating between different threshold values. - Default is 100. - return_coef : bool, optional - If True, will convert the polynomial coefficients for the fit baseline to - a form that fits the input x_data and return them in the params dictionary. - Default is False, since the conversion takes time. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray, shape (J, K) - An array containing the calculated tolerance values for each iteration - of both threshold values and fit values. Index 0 are the tolerence values - for the difference in up-down ratios, index 1 are the tolerance values for - the relative change in the threshold, and indices >= 2 are the tolerance values - for each fit. All values that were not used in fitting have values of 0. Shape J - is 2 plus the number of iterations for the threshold to converge (related to - `max_iter_2`, `tol_2`, `tol_3`), and shape K is the maximum of the number of - iterations for the threshold and the maximum number of iterations for all of - the fits of the various threshold values (related to `max_iter` and `tol`). - * 'threshold' : float - The optimal threshold value. Could be used in :func:`.penalized_poly` - for fitting other similar data. - * 'coef': numpy.ndarray, shape (poly_order + 1,) - Only if `return_coef` is True. The array of polynomial parameters - for the baseline, in increasing order. Can be used to create a - polynomial using numpy.polynomial.polynomial.Polynomial(). - - Raises - ------ - ValueError - Raised if `alpha_factor` or `peak_ratio` are not between 0 and 1, or if the - specified cost function is symmetric. - - References - ---------- - .. [18] Liu, J., et al. Goldindec: A Novel Algorithm for Raman Spectrum Baseline - Correction. Applied Spectroscopy, 2015, 69(7), 834-842. - .. [19] Mazet, V., et al. Background removal from spectra by designing and - minimising a non-quadratic cost function. Chemometrics and Intelligent - Laboratory Systems, 2005, 76(2), 121-133. - - """ diff --git a/GSASII/pybaselines/smooth.py b/GSASII/pybaselines/smooth.py deleted file mode 100644 index 9a4ff55e8..000000000 --- a/GSASII/pybaselines/smooth.py +++ /dev/null @@ -1,1012 +0,0 @@ -# -*- coding: utf-8 -*- -"""Smoothing-based techniques for fitting baselines to experimental data. - -Created on March 7, 2021 -@author: Donald Erb - -""" - -import warnings - -import numpy as np -from scipy.ndimage import median_filter, uniform_filter1d -from scipy.signal import savgol_coeffs - -from ._algorithm_setup import _Algorithm, _class_wrapper -from .utils import ( - ParameterWarning, _check_scalar, _get_edges, gaussian, gaussian_kernel, - optimize_window, pad_edges, padded_convolve, relative_difference -) - - -class _Smooth(_Algorithm): - """A base class for all smoothing algorithms.""" - - @_Algorithm._register - def noise_median(self, data, half_window=None, smooth_half_window=None, sigma=None, - **pad_kwargs): - """ - The noise-median method for baseline identification. - - Assumes the baseline can be considered as the median value within a moving - window, and the resulting baseline is then smoothed with a Gaussian kernel. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The index-based size to use for the median window. The total window - size will range from [-half_window, ..., half_window] with size - 2 * half_window + 1. Default is None, which will use twice the output from - :func:`.optimize_window`, which is an okay starting value. - smooth_half_window : int, optional - The half window to use for smoothing. Default is None, which will use - the same value as `half_window`. - sigma : float, optional - The standard deviation of the smoothing Gaussian kernel. Default is None, - which will use (2 * `smooth_half_window` + 1) / 6. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated and smoothed baseline. - dict - An empty dictionary, just to match the output of all other algorithms. - - References - ---------- - Friedrichs, M., A model-free algorithm for the removal of baseline - artifacts. J. Biomolecular NMR, 1995, 5, 147-153. - - """ - if half_window is None: - half_window = 2 * optimize_window(data) - window_size = 2 * half_window + 1 - median = median_filter( - self._setup_smooth(data, half_window, **pad_kwargs), - [window_size], mode='nearest' - ) - if smooth_half_window is None: - smooth_window = window_size - else: - smooth_window = 2 * smooth_half_window + 1 - if sigma is None: - # the gaussian kernel will includes +- 3 sigma - sigma = smooth_window / 6 - baseline = padded_convolve(median, gaussian_kernel(smooth_window, sigma)) - return baseline[half_window:-half_window], {} - - @_Algorithm._register - def snip(self, data, max_half_window=None, decreasing=False, smooth_half_window=None, - filter_order=2, **pad_kwargs): - """ - Statistics-sensitive Non-linear Iterative Peak-clipping (SNIP). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - max_half_window : int or Sequence(int, int), optional - The maximum number of iterations. Should be set such that - `max_half_window` is approxiamtely ``(w-1)/2``, where ``w`` is the index-based - width of a feature or peak. `max_half_window` can also be a sequence of - two integers for asymmetric peaks, with the first item corresponding to - the `max_half_window` of the peak's left edge, and the second item - for the peak's right edge [29]_. Default is None, which will use the output - from :func:`.optimize_window`, which is an okay starting value. - decreasing : bool, optional - If False (default), will iterate through window sizes from 1 to - `max_half_window`. If True, will reverse the order and iterate from - `max_half_window` to 1, which gives a smoother baseline according to [29]_ - and [30]_. - smooth_half_window : int, optional - The half window to use for smoothing the data. If `smooth_half_window` - is greater than 0, will perform a moving average smooth on the data for - each window, which gives better results for noisy data [29]_. Default is - None, which will not perform any smoothing. - filter_order : {2, 4, 6, 8}, optional - If the measured data has a more complicated baseline consisting of other - elements such as Compton edges, then a higher `filter_order` should be - selected [29]_. Default is 2, which works well for approximating a linear - baseline. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - An empty dictionary, just to match the output of all other algorithms. - - Raises - ------ - ValueError - Raised if `filter_order` is not 2, 4, 6, or 8. - - Warns - ----- - UserWarning - Raised if max_half_window is greater than (len(data) - 1) // 2. - - Notes - ----- - Algorithm initially developed by [27]_, and this specific version of the - algorithm is adapted from [28]_, [29]_, and [30]_. - - If data covers several orders of magnitude, better results can be obtained - by first transforming the data using log-log-square transform before - using SNIP [28]_:: - - transformed_data = np.log(np.log(np.sqrt(data + 1) + 1) + 1) - - and then baseline can then be reverted back to the original scale using inverse:: - - baseline = -1 + (np.exp(np.exp(snip(transformed_data)) - 1) - 1)**2 - - References - ---------- - .. [27] Ryan, C.G., et al. SNIP, A Statistics-Sensitive Background Treatment - For The Quantitative Analysis Of Pixe Spectra In Geoscience Applications. - Nuclear Instruments and Methods in Physics Research B, 1988, 934, 396-402. - .. [28] Morháč, M., et al. Background elimination methods for multidimensional - coincidence γ-ray spectra. Nuclear Instruments and Methods in Physics - Research A, 1997, 401, 113-132. - .. [29] Morháč, M., et al. Peak Clipping Algorithms for Background Estimation in - Spectroscopic Data. Applied Spectroscopy, 2008, 62(1), 91-106. - .. [30] Morháč, M. An algorithm for determination of peak regions and baseline - elimination in spectroscopic data. Nuclear Instruments and Methods in - Physics Research A, 2009, 60, 478-487. - - """ - # TODO potentially add adaptive window sizes from [30]_, or at least allow inputting - # an array of max_half_windows; would need to have a separate function for array - # windows since it would no longer be able to be vectorized - if filter_order not in {2, 4, 6, 8}: - raise ValueError('filter_order must be 2, 4, 6, or 8') - - if max_half_window is None: - max_half_window = optimize_window(data) - half_windows = _check_scalar(max_half_window, 2, True, dtype=int)[0] - for i, half_window in enumerate(half_windows): - if half_window > (self._len - 1) // 2: - warnings.warn( - 'max_half_window values greater than (len(data) - 1) / 2 have no effect.', - ParameterWarning - ) - half_windows[i] = (self._len - 1) // 2 - - max_of_half_windows = np.max(half_windows) - if decreasing: - range_args = (max_of_half_windows, 0, -1) - else: - range_args = (1, max_of_half_windows + 1, 1) - - y = self._setup_smooth(data, max_of_half_windows, **pad_kwargs) - num_y = self._len + 2 * max_of_half_windows - smooth = smooth_half_window is not None and smooth_half_window > 0 - baseline = y.copy() - for i in range(*range_args): - i_left = min(i, half_windows[0]) - i_right = min(i, half_windows[1]) - - filters = ( - baseline[i - i_left:num_y - i - i_left] + baseline[i + i_right:num_y - i + i_right] - ) / 2 - if filter_order > 2: - filters_new = ( - - ( - baseline[i - i_left:num_y - i - i_left] - + baseline[i + i_right:num_y - i + i_right] - ) - + 4 * ( - baseline[i - i_left // 2:-i - i_left // 2] - + baseline[i + i_right // 2:-i + i_right // 2] - ) - ) / 6 - filters = np.maximum(filters, filters_new) - if filter_order > 4: - filters_new = ( - baseline[i - i_left:num_y - i - i_left] - + baseline[i + i_right:num_y - i + i_right] - - 6 * ( - baseline[i - 2 * i_left // 3:-i - 2 * i_left // 3] - + baseline[i + 2 * i_right // 3:-i + 2 * i_right // 3] - ) - + 15 * ( - baseline[i - i_left // 3:-i - i_left // 3] - + baseline[i + i_right // 3:-i + i_right // 3] - ) - ) / 20 - filters = np.maximum(filters, filters_new) - if filter_order > 6: - filters_new = ( - - ( - baseline[i - i_left:num_y - i - i_left] - + baseline[i + i_right:num_y - i + i_right] - ) - + 8 * ( - baseline[i - 3 * i_left // 4:-i - 3 * i_left // 4] - + baseline[i + 3 * i_right // 4:-i + 3 * i_right // 4] - ) - - 28 * ( - baseline[i - i_left // 2:-i - i_left // 2] - + baseline[i + i_right // 2:-i + i_right // 2] - ) - + 56 * ( - baseline[i - i_left // 4:-i - i_left // 4] - + baseline[i + i_right // 4:-i + i_right // 4] - ) - ) / 70 - filters = np.maximum(filters, filters_new) - - if smooth: - previous_baseline = uniform_filter1d(baseline, 2 * smooth_half_window + 1)[i:-i] - else: - previous_baseline = baseline[i:-i] - baseline[i:-i] = np.where(baseline[i:-i] > filters, filters, previous_baseline) - - return baseline[max_of_half_windows:-max_of_half_windows], {} - - @_Algorithm._register - def swima(self, data, min_half_window=3, max_half_window=None, smooth_half_window=None, - **pad_kwargs): - """ - Small-window moving average (SWiMA) baseline. - - Computes an iterative moving average to smooth peaks and obtain the baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - min_half_window : int, optional - The minimum half window value that must be reached before the exit criteria - is considered. Can be increased to reduce the calculation time. Default is 3. - max_half_window : int, optional - The maximum number of iterations. Default is None, which will use - (N - 1) / 2. Typically does not need to be specified. - smooth_half_window : int, optional - The half window to use for smoothing the input data with a moving average. - Default is None, which will use N / 50. Use a value of 0 or less to not - smooth the data. See Notes below for more details. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': list(int) - A list of the half windows at which the exit criteria was reached. - Has a length of 1 if the main exit criteria was intially reached, - otherwise has a length of 2. - * 'converged': list(bool or None) - A list of the convergence status. Has a length of 1 if the main - exit criteria was intially reached, otherwise has a length of 2. - Each convergence status is True if the main exit criteria was - reached, False if the second exit criteria was reached, and None - if `max_half_window` is reached before either exit criteria. - - Notes - ----- - This algorithm requires the input data to be fairly smooth (noise-free), so it - is recommended to either smooth the data beforehand, or specify a - `smooth_half_window` value. Non-smooth data can cause the exit criteria to be - reached prematurely (can be avoided by setting a larger `min_half_window`), while - over-smoothed data can cause the exit criteria to be reached later than optimal. - - The half-window at which convergence occurs is roughly close to the index-based - full-width-at-half-maximum of a peak or feature, but can vary. Therfore, it is - better to set a `min_half_window` that is smaller than expected to not miss the - exit criteria. - - If the main exit criteria is not reached on the initial fit, a gaussian baseline - (which is well handled by this algorithm) is added to the data, and it is re-fit. - - References - ---------- - Schulze, H., et al. A Small-Window Moving Average-Based Fully Automated - Baseline Estimation Method for Raman Spectra. Applied Spectroscopy, 2012, - 66(7), 757-764. - - """ - if max_half_window is None: - max_half_window = (self._len - 1) // 2 - y = self._setup_smooth(data, max_half_window, **pad_kwargs) - len_y = self._len + 2 * max_half_window # includes padding of max_half_window at each side - data_slice = slice(max_half_window, -max_half_window) - if smooth_half_window is None: - smooth_half_window = max(1, (len_y - 2 * max_half_window) // 50) - if smooth_half_window > 0: - y = uniform_filter1d(y, 2 * smooth_half_window + 1) - - *_, pseudo_inverse = self._setup_polynomial( - y, None, poly_order=3, calc_vander=True, calc_pinv=True - ) - baseline, converged, half_window = _swima_loop( - y, self.vandermonde, pseudo_inverse, data_slice, max_half_window, min_half_window - ) - converges = [converged] - half_windows = [half_window] - if not converged: - residual = y - baseline - gaussian_bkg = gaussian( - np.arange(len_y), np.max(residual), len_y / 2, len_y / 6 - ) - baseline_2, converged, half_window = _swima_loop( - residual + gaussian_bkg, self.vandermonde, pseudo_inverse, data_slice, - max_half_window, 3 - ) - baseline += baseline_2 - gaussian_bkg - converges.append(converged) - half_windows.append(half_window) - - return baseline[data_slice], {'half_window': half_windows, 'converged': converges} - - @_Algorithm._register - def ipsa(self, data, half_window=None, max_iter=500, tol=None, roi=None, - original_criteria=False, **pad_kwargs): - """ - Iterative Polynomial Smoothing Algorithm (IPSA). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int - The half-window to use for the smoothing each iteration. Should be - approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use 4 times the output of - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - max_iter : int, optional - The maximum number of iterations. Default is 500. - tol : float, optional - The exit criteria. Default is None, which uses 1e-3 if `original_criteria` is - False, and ``1 / (max(data) - min(data))`` if `original_criteria` is True. - roi : slice or array-like, shape(N,) - The region of interest, such that ``np.asarray(data)[roi]`` gives the values - for calculating the tolerance if `original_criteria` is True. Not used if - `original_criteria` is True. Default is None, which uses all values in `data`. - original_criteria : bool, optional - Whether to use the original exit criteria from the reference, which is difficult - to use since it requires knowledge of how high the peaks should be after baseline - correction. If False (default), then compares ``norm(old, new) / norm(old)``, where - `old` is the previous iteration's baseline, and `new` is the current iteration's - baseline. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Wang, T., et al. Background Subtraction of Raman Spectra Based on Iterative - Polynomial Smoothing. Applied Spectroscopy. 71(6) (2017) 1169-1179. - - """ - # TODO should just move optimize window into _setup_smooth since all smooth functions - # could use it; maybe just add a multiplier constant; that way, snip and noise_median - # no longer require a half window parameter to at least get a guess - if half_window is None: - half_window = 4 * optimize_window(data) - window_size = 2 * half_window + 1 - y = self._setup_smooth(data, window_size, **pad_kwargs) - y0 = y - data_slice = slice(window_size, -window_size) - if original_criteria: - if roi is None: - roi = slice(None) - elif not isinstance(roi, slice): - roi = np.asarray(roi) - - if tol is None: - if original_criteria: - # guess what the desired height should be; not a great guess, but it's - # something - tol = 1 / np.ptp(y[data_slice][roi]) - else: - tol = 1e-3 - - savgol_coef = savgol_coeffs(window_size, 2) - tol_history = np.empty(max_iter + 1) - old_baseline = y[data_slice] - for i in range(max_iter + 1): - baseline = padded_convolve(y, savgol_coef, 'edge') - if original_criteria: - residual = (y0 - baseline)[data_slice][roi] - calc_tol = abs(residual.min() / residual.max()) - else: - calc_tol = relative_difference(old_baseline, baseline[data_slice]) - tol_history[i] = calc_tol - if calc_tol < tol: - break - y = np.minimum(y0, baseline) - old_baseline = baseline[data_slice] - - return baseline[data_slice], {'tol_history': tol_history[:i + 1]} - - @_Algorithm._register - def ria(self, data, half_window=None, max_iter=500, tol=1e-2, side='both', - width_scale=0.1, height_scale=1., sigma_scale=1. / 12., **pad_kwargs): - """ - Range Independent Algorithm (RIA). - - Adds additional data to the left and/or right of the input data, and then - iteratively smooths until the area of the additional data is removed. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The half-window to use for the smoothing each iteration. Should be - approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use the output of :func:`.optimize_window`, - which is not always a good value, but at least scales with the number of data points - and gives a starting point for tuning the parameter. - max_iter : int, optional - The maximum number of iterations. Default is 500. - tol : float, optional - The exit criteria. Default is 1e-2. - side : {'both', 'left', 'right'}, optional - The side of the measured data to extend. Default is 'both'. - width_scale : float, optional - The number of data points added to each side is `width_scale` * N. Default - is 0.1. - height_scale : float, optional - The height of the added Gaussian peak(s) is calculated as - `height_scale` * max(`data`). Default is 1. - sigma_scale : float, optional - The sigma value for the added Gaussian peak(s) is calculated as - `sigma_scale` * `width_scale` * N. Default is 1/12, which will make - the Gaussian span +- 6 sigma, making its total width about half of the - added length. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data when adding the extended left and/or right sections. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge (if the array length - is equal to `max_iter`) or the areas of the smoothed extended regions - exceeded their initial areas (if the array length is < `max_iter`). - - Raises - ------ - ValueError - Raised if `side` is not 'left', 'right', or 'both'. - - References - ---------- - Krishna, H., et al. Range-independent background subtraction algorithm for - recovery of Raman spectra of biological tissue. J Raman Spectroscopy. 43(12) - (2012) 1884-1894. - - """ - side = side.lower() - if side not in ('left', 'right', 'both'): - raise ValueError('side must be "left", "right", or "both"') - y = self._setup_smooth(data, 0) - if half_window is None: - half_window = optimize_window(y) - min_x = self.x_domain[0] - max_x = self.x_domain[1] - x_range = max_x - min_x - - added_window = int(self._len * width_scale) - lower_bound = 0 - upper_bound = 0 - known_area = 0. - - # TODO should make this a function that could be used by - # optimizers.optimize_extended_range too - added_left, added_right = _get_edges(y, added_window, **pad_kwargs) - added_gaussian = gaussian( - np.linspace(-added_window / 2, added_window / 2, added_window), - height_scale * abs(y.max()), 0, added_window * sigma_scale - ) - if side in ('left', 'both'): - added_x_left = np.linspace( - min_x - x_range * (width_scale / 2), min_x, added_window + 1 - )[:-1] - added_y_left = added_gaussian + added_left - lower_bound = added_window - known_area += np.trapz(added_gaussian, added_x_left) - else: - added_x_left = [] - added_y_left = [] - - if side in ('right', 'both'): - added_x_right = np.linspace( - max_x, max_x + x_range * (width_scale / 2), added_window + 1 - )[1:] - added_y_right = added_gaussian + added_right - upper_bound = added_window - known_area += np.trapz(added_gaussian, added_x_right) - else: - added_x_right = [] - added_y_right = [] - - fit_x_data = np.concatenate((added_x_left, self.x, added_x_right)) - fit_data = np.concatenate((added_y_left, y, added_y_right)) - - upper_max = fit_data.shape[0] - upper_bound - tol_history = np.empty(max_iter) - window_size = 2 * half_window + 1 - smoother_array = pad_edges(fit_data, window_size, extrapolate_window=2) - data_slice = slice(window_size, -window_size) - for i in range(max_iter): - # only smooth fit_data so that the outer section remains unchanged by - # smoothing and edge effects are ignored - smoother_array[data_slice] = uniform_filter1d(smoother_array, window_size)[data_slice] - residual = fit_data - smoother_array[data_slice] - calc_area = np.trapz(residual[:lower_bound], fit_x_data[:lower_bound]) - if upper_bound: - calc_area += np.trapz(residual[-upper_bound:], fit_x_data[-upper_bound:]) - calc_difference = relative_difference(known_area, calc_area) - tol_history[i] = calc_difference - if calc_difference < tol or calc_area > known_area: - break - smoother_array[data_slice] = np.minimum(fit_data, smoother_array[data_slice]) - - baseline = smoother_array[data_slice][lower_bound:upper_max] - - return baseline, {'tol_history': tol_history[:i + 1]} - - -_smooth_wrapper = _class_wrapper(_Smooth) - - -@_smooth_wrapper -def noise_median(data, half_window=None, smooth_half_window=None, sigma=None, x_data=None, - **pad_kwargs): - """ - The noise-median method for baseline identification. - - Assumes the baseline can be considered as the median value within a moving - window, and the resulting baseline is then smoothed with a Gaussian kernel. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int, optional - The index-based size to use for the median window. The total window - size will range from [-half_window, ..., half_window] with size - 2 * half_window + 1. Default is None, which will use twice the output from - :func:`.optimize_window`, which is an okay starting value. - smooth_half_window : int, optional - The half window to use for smoothing. Default is None, which will use - the same value as `half_window`. - sigma : float, optional - The standard deviation of the smoothing Gaussian kernel. Default is None, - which will use (2 * `smooth_half_window` + 1) / 6. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated and smoothed baseline. - dict - An empty dictionary, just to match the output of all other algorithms. - - References - ---------- - Friedrichs, M., A model-free algorithm for the removal of baseline - artifacts. J. Biomolecular NMR, 1995, 5, 147-153. - - """ - - -@_smooth_wrapper -def snip(data, max_half_window=None, decreasing=False, smooth_half_window=None, - filter_order=2, x_data=None, **pad_kwargs): - """ - Statistics-sensitive Non-linear Iterative Peak-clipping (SNIP). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - max_half_window : int or Sequence(int, int), optional - The maximum number of iterations. Should be set such that - `max_half_window` is approxiamtely ``(w-1)/2``, where ``w`` is the index-based - width of a feature or peak. `max_half_window` can also be a sequence of - two integers for asymmetric peaks, with the first item corresponding to - the `max_half_window` of the peak's left edge, and the second item - for the peak's right edge [3]_. Default is None, which will use the output - from :func:`.optimize_window`, which is an okay starting value. - decreasing : bool, optional - If False (default), will iterate through window sizes from 1 to - `max_half_window`. If True, will reverse the order and iterate from - `max_half_window` to 1, which gives a smoother baseline according to [3]_ - and [4]_. - smooth_half_window : int, optional - The half window to use for smoothing the data. If `smooth_half_window` - is greater than 0, will perform a moving average smooth on the data for - each window, which gives better results for noisy data [3]_. Default is - None, which will not perform any smoothing. - filter_order : {2, 4, 6, 8}, optional - If the measured data has a more complicated baseline consisting of other - elements such as Compton edges, then a higher `filter_order` should be - selected [3]_. Default is 2, which works well for approximating a linear - baseline. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - An empty dictionary, just to match the output of all other algorithms. - - Raises - ------ - ValueError - Raised if `filter_order` is not 2, 4, 6, or 8. - - Warns - ----- - UserWarning - Raised if max_half_window is greater than (len(data) - 1) // 2. - - Notes - ----- - Algorithm initially developed by [1]_, and this specific version of the - algorithm is adapted from [2]_, [3]_, and [4]_. - - If data covers several orders of magnitude, better results can be obtained - by first transforming the data using log-log-square transform before - using SNIP [2]_:: - - transformed_data = np.log(np.log(np.sqrt(data + 1) + 1) + 1) - - and then baseline can then be reverted back to the original scale using inverse:: - - baseline = -1 + (np.exp(np.exp(snip(transformed_data)) - 1) - 1)**2 - - References - ---------- - .. [1] Ryan, C.G., et al. SNIP, A Statistics-Sensitive Background Treatment - For The Quantitative Analysis Of Pixe Spectra In Geoscience Applications. - Nuclear Instruments and Methods in Physics Research B, 1988, 934, 396-402. - .. [2] Morháč, M., et al. Background elimination methods for multidimensional - coincidence γ-ray spectra. Nuclear Instruments and Methods in Physics - Research A, 1997, 401, 113-132. - .. [3] Morháč, M., et al. Peak Clipping Algorithms for Background Estimation in - Spectroscopic Data. Applied Spectroscopy, 2008, 62(1), 91-106. - .. [4] Morháč, M. An algorithm for determination of peak regions and baseline - elimination in spectroscopic data. Nuclear Instruments and Methods in - Physics Research A, 2009, 60, 478-487. - - """ - - -def _swima_loop(y, vander, pseudo_inverse, data_slice, max_half_window, min_half_window=3): - """ - Computes an iterative moving average to smooth peaks and obtain the baseline. - - The internal loop of the small-window moving average (SWiMA) algorithm. - - Parameters - ---------- - y : numpy.ndarray, shape (N + 2 * max_half_window,) - The array of the measured data with N data points padded at each edge with - `max_half_window` extra data points. - vander : numpy.ndarray, shape (N - 1, 4) - The Vandermonde matrix for computing the 3rd order polynomial fit of the - differential of the residual. Used for the alternate exit criteria. - pseudo_inverse : numpy.ndarray, shape (4, N - 1) - The pseudo-inverse of the Vandermonde matrix for computing the 3rd order - polynomial fit of the differential of the residual. Used for the alternate - exit criteria. - data_slice : slice - The slice used for separating the actual values of `y` from the extended y - array. - max_half_window : int - The maximum allowable half window. - min_half_window : int, optional - The minimum half window that must be reached before exit criteria are - considered. Default is 3. - - Returns - ------- - baseline : numpy.ndarray, shape (N + 2 * max_half_window,) - The baseline with the padded edges. - converged : bool or None - Whether the main exit criteria was achieved. True if it was, False - if the alternate exit criteria was achieved, and None if `max_half_window` - was reached before either exit criteria. - half_window : int - The half window at which the exit criteria was reached. - - Notes - ----- - Uses a moving average rather than a 0-degree Savitzky-Golay filter since - they are equivalent and the moving average is faster. - - The second exit criteria is based on Figure 2 in the reference, since the - slightly different definition of criteria two stated in the text was always - reached before the main exit criteria, which is not the desired outcome. - - References - ---------- - Schulze, H., et al. A Small-Window Moving Average-Based Fully Automated - Baseline Estimation Method for Raman Spectra. Applied Spectroscopy, 2012, - 66(7), 757-764. - - """ - actual_y = y[data_slice] - baseline = y - min_half_window_check = min_half_window - 2 - area_current = -1 - area_old = -1 - converged = None - for half_window in range(1, max_half_window + 1): - baseline_new = np.minimum(baseline, uniform_filter1d(baseline, 2 * half_window + 1)) - # only begin calculating the area when near the lowest allowed half window - if half_window > min_half_window_check: - area_new = np.trapz(baseline[data_slice] - baseline_new[data_slice]) - # exit criteria 1 - if area_new > area_current and area_current < area_old: - converged = True - # subtract 1 since minimum area was reached the previous iteration - half_window -= 1 - break - if half_window > min_half_window: - diff_current = np.gradient(actual_y - baseline_new[data_slice]) - poly_diff_current = np.trapz(abs(vander @ (pseudo_inverse @ diff_current))) - # exit criteria 2, means baseline is not well fit - if poly_diff_current > 0.15 * np.trapz(abs(diff_current)): - converged = False - break - area_old = area_current - area_current = area_new - baseline = baseline_new - - return baseline, converged, half_window - - -@_smooth_wrapper -def swima(data, min_half_window=3, max_half_window=None, smooth_half_window=None, - x_data=None, **pad_kwargs): - """ - Small-window moving average (SWiMA) baseline. - - Computes an iterative moving average to smooth peaks and obtain the baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - min_half_window : int, optional - The minimum half window value that must be reached before the exit criteria - is considered. Can be increased to reduce the calculation time. Default is 3. - max_half_window : int, optional - The maximum number of iterations. Default is None, which will use - (N - 1) / 2. Typically does not need to be specified. - smooth_half_window : int, optional - The half window to use for smoothing the input data with a moving average. - Default is None, which will use N / 50. Use a value of 0 or less to not - smooth the data. See Notes below for more details. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - A dictionary with the following items: - - * 'half_window': list(int) - A list of the half windows at which the exit criteria was reached. - Has a length of 1 if the main exit criteria was intially reached, - otherwise has a length of 2. - * 'converged': list(bool or None) - A list of the convergence status. Has a length of 1 if the main - exit criteria was intially reached, otherwise has a length of 2. - Each convergence status is True if the main exit criteria was - reached, False if the second exit criteria was reached, and None - if `max_half_window` is reached before either exit criteria. - - Notes - ----- - This algorithm requires the input data to be fairly smooth (noise-free), so it - is recommended to either smooth the data beforehand, or specify a - `smooth_half_window` value. Non-smooth data can cause the exit criteria to be - reached prematurely (can be avoided by setting a larger `min_half_window`), while - over-smoothed data can cause the exit criteria to be reached later than optimal. - - The half-window at which convergence occurs is roughly close to the index-based - full-width-at-half-maximum of a peak or feature, but can vary. Therfore, it is - better to set a `min_half_window` that is smaller than expected to not miss the - exit criteria. - - If the main exit criteria is not reached on the initial fit, a gaussian baseline - (which is well handled by this algorithm) is added to the data, and it is re-fit. - - References - ---------- - Schulze, H., et al. A Small-Window Moving Average-Based Fully Automated - Baseline Estimation Method for Raman Spectra. Applied Spectroscopy, 2012, - 66(7), 757-764. - - """ - - -@_smooth_wrapper -def ipsa(data, half_window=None, max_iter=500, tol=None, roi=None, - original_criteria=False, x_data=None, **pad_kwargs): - """ - Iterative Polynomial Smoothing Algorithm (IPSA). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - half_window : int - The half-window to use for the smoothing each iteration. Should be - approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use 4 times the output of - :func:`.optimize_window`, which is not always a good value, but at least scales - with the number of data points and gives a starting point for tuning the parameter. - max_iter : int, optional - The maximum number of iterations. Default is 500. - tol : float, optional - The exit criteria. Default is None, which uses 1e-3 if `original_criteria` is - False, and ``1 / (max(data) - min(data))`` if `original_criteria` is True. - roi : slice or array-like, shape(N,) - The region of interest, such that ``np.asarray(data)[roi]`` gives the values - for calculating the tolerance if `original_criteria` is True. Not used if - `original_criteria` is True. Default is None, which uses all values in `data`. - original_criteria : bool, optional - Whether to use the original exit criteria from the reference, which is difficult - to use since it requires knowledge of how high the peaks should be after baseline - correction. If False (default), then compares ``norm(old, new) / norm(old)``, where - `old` is the previous iteration's baseline, and `new` is the current iteration's - baseline. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from convolution. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Wang, T., et al. Background Subtraction of Raman Spectra Based on Iterative - Polynomial Smoothing. Applied Spectroscopy. 71(6) (2017) 1169-1179. - - """ - - -@_smooth_wrapper -def ria(data, x_data=None, half_window=None, max_iter=500, tol=1e-2, side='both', - width_scale=0.1, height_scale=1., sigma_scale=1. / 12., **pad_kwargs): - """ - Range Independent Algorithm (RIA). - - Adds additional data to the left and/or right of the input data, and then - iteratively smooths until the area of the additional data is removed. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - half_window : int, optional - The half-window to use for the smoothing each iteration. Should be - approximately equal to the full-width-at-half-maximum of the peaks or features - in the data. Default is None, which will use the output of :func:`.optimize_window`, - which is not always a good value, but at least scales with the number of data points - and gives a starting point for tuning the parameter. - max_iter : int, optional - The maximum number of iterations. Default is 500. - tol : float, optional - The exit criteria. Default is 1e-2. - side : {'both', 'left', 'right'}, optional - The side of the measured data to extend. Default is 'both'. - width_scale : float, optional - The number of data points added to each side is `width_scale` * N. Default - is 0.1. - height_scale : float, optional - The height of the added Gaussian peak(s) is calculated as - `height_scale` * max(`data`). Default is 1. - sigma_scale : float, optional - The sigma value for the added Gaussian peak(s) is calculated as - `sigma_scale` * `width_scale` * N. Default is 1/12, which will make - the Gaussian span +- 6 sigma, making its total width about half of the - added length. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data when adding the extended left and/or right sections. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge (if the array length - is equal to `max_iter`) or the areas of the smoothed extended regions - exceeded their initial areas (if the array length is < `max_iter`). - - Raises - ------ - ValueError - Raised if `side` is not 'left', 'right', or 'both'. - - References - ---------- - Krishna, H., et al. Range-independent background subtraction algorithm for - recovery of Raman spectra of biological tissue. J Raman Spectroscopy. 43(12) - (2012) 1884-1894. - - """ diff --git a/GSASII/pybaselines/spline.py b/GSASII/pybaselines/spline.py deleted file mode 100644 index 46cd4ae31..000000000 --- a/GSASII/pybaselines/spline.py +++ /dev/null @@ -1,2412 +0,0 @@ -# -*- coding: utf-8 -*- -"""Functions for fitting baselines using splines. - -Created on August 4, 2021 -@author: Donald Erb - -""" - -from functools import partial -from math import ceil -import warnings - -import numpy as np -from scipy.optimize import curve_fit -from scipy.sparse import spdiags - -from . import _weighting -from ._algorithm_setup import _Algorithm, _class_wrapper -from ._banded_utils import _add_diagonals, _shift_rows, diff_penalty_diagonals -from ._compat import _HAS_NUMBA, jit -from ._spline_utils import _basis_midpoints -from ._validation import _check_lam, _check_optional_array -from .utils import ( - _MIN_FLOAT, _mollifier_kernel, ParameterWarning, gaussian, pad_edges, padded_convolve, - relative_difference -) - - -class _Spline(_Algorithm): - """A base class for all spline algorithms.""" - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def mixture_model(self, data, lam=1e5, p=1e-2, num_knots=100, spline_degree=3, diff_order=3, - max_iter=50, tol=1e-3, weights=None, symmetric=False, num_bins=None): - """ - Considers the data as a mixture model composed of noise and peaks. - - Weights are iteratively assigned by calculating the probability each value in - the residual belongs to a normal distribution representing the noise. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Used to set the initial weights before performing - expectation-maximization. Default is 1e-2. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 3 - (third order differential matrix). Typical values are 2 or 3. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1, and then - two iterations of reweighted least-squares are performed to provide starting - weights for the expectation-maximization of the mixture model. - symmetric : bool, optional - If False (default), the total mixture model will be composed of one normal - distribution for the noise and one uniform distribution for positive non-noise - residuals. If True, an additional uniform distribution will be added to the - mixture model for negative non-noise residuals. Only need to set `symmetric` - to True when peaks are both positive and negative. - num_bins : int, optional - The number of bins to use when transforming the residuals into a probability - density distribution. Default is None, which uses ``ceil(sqrt(N))``. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if p is not between 0 and 1. - - References - ---------- - de Rooi, J., et al. Mixture models for baseline estimation. Chemometric and - Intelligent Laboratory Systems, 2012, 117, 56-60. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - # scale y between -1 and 1 so that the residual fit is more numerically stable - y_domain = np.polynomial.polyutils.getdomain(y) - y = np.polynomial.polyutils.mapdomain(y, y_domain, np.array([-1., 1.])) - - if weights is not None: - baseline = self.pspline.solve_pspline(y, weight_array) - else: - # perform 2 iterations: first is a least-squares fit and second is initial - # reweighted fit; 2 fits are needed to get weights to have a decent starting - # distribution for the expectation-maximization - if symmetric and not 0.2 < p < 0.8: - # p values far away from 0.5 with symmetric=True give bad initial weights - # for the expectation maximization - warnings.warn( - 'should use a p value closer to 0.5 when symmetric is True', - ParameterWarning, stacklevel=2 - ) - for _ in range(2): - baseline = self.pspline.solve_pspline(y, weight_array) - weight_array = _weighting._asls(y, baseline, p) - - # now perform the expectation-maximization - # TODO not sure if there is a better way to do this than transforming - # the residual into a histogram, fitting the histogram, and then assigning - # weights based on the bins; actual expectation-maximization uses log(probability) - # directly estimates sigma from that, and then calculates the percentages, maybe - # that would be faster/more stable? - if num_bins is None: - num_bins = ceil(np.sqrt(y.shape[0])) - - # uniform probability density distribution for positive residuals, constant - # from 0 to max(residual), and 0 for residuals < 0 - pos_uniform_pdf = np.empty(num_bins) - tol_history = np.empty(max_iter + 1) - residual = y - baseline - - # the 0.2 * std(residual) is an "okay" starting sigma estimate - fit_params = [0.5, np.log10(0.2 * np.std(residual))] - bounds = [[0, -np.inf], [1, np.inf]] - if symmetric: - fit_params.append(0.25) - bounds[0].append(0) - bounds[1].append(1) - # create a second uniform pdf for negative residual values - neg_uniform_pdf = np.empty(num_bins) - else: - neg_uniform_pdf = None - - # convert bounds to numpy array since curve_fit will use np.asarray each iteration - bounds = np.array(bounds) - for i in range(max_iter + 1): - residual_hist, bin_edges, bin_mapping = _mapped_histogram(residual, num_bins) - # average bin edges to get better x-values for fitting - bins = 0.5 * (bin_edges[:-1] + bin_edges[1:]) - pos_uniform_mask = bins < 0 - pos_uniform_pdf[~pos_uniform_mask] = 1 / max(abs(residual.max()), 1e-6) - pos_uniform_pdf[pos_uniform_mask] = 0 - if symmetric: - neg_uniform_mask = bins > 0 - neg_uniform_pdf[~neg_uniform_mask] = 1 / max(abs(residual.min()), 1e-6) - neg_uniform_pdf[neg_uniform_mask] = 0 - - fit_func = partial( - _mixture_pdf, pos_uniform=pos_uniform_pdf, neg_uniform=neg_uniform_pdf - ) - # use dogbox method since trf gives RuntimeWarnings from nans appearing - # somehow during optimization; trf is also prone to failure when symmetric=True - fit_params = curve_fit( - fit_func, bins, residual_hist, p0=fit_params, bounds=bounds, - check_finite=False, method='dogbox' - )[0] - sigma = 10**fit_params[1] - gaus_pdf = fit_params[0] * gaussian(bins, 1 / (sigma * np.sqrt(2 * np.pi)), 0, sigma) - posterior_prob = gaus_pdf / np.maximum(fit_func(bins, *fit_params), _MIN_FLOAT) - # need to clip since a bad initial start can erroneously set the sum of the fractions - # of each distribution to > 1 - np.clip(posterior_prob, 0, 1, out=posterior_prob) - new_weights = posterior_prob[bin_mapping] - - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - - weight_array = new_weights - baseline = self.pspline.solve_pspline(y, weight_array) - residual = y - baseline - - # TODO could potentially return a BSpline object from scipy.interpolate - # using knots, spline degree, and coef, but would need to allow user to - # input the x-values for it to be useful - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - baseline = np.polynomial.polyutils.mapdomain(baseline, np.array([-1., 1.]), y_domain) - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def irsqr(self, data, lam=100, quantile=0.05, num_knots=100, spline_degree=3, - diff_order=3, max_iter=100, tol=1e-6, weights=None, eps=None): - """ - Iterative Reweighted Spline Quantile Regression (IRSQR). - - Fits the baseline using quantile regression with penalized splines. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - quantile : float, optional - The quantile at which to fit the baseline. Default is 0.05. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 3 - (third order differential matrix). Typical values are 3, 2, or 1. - max_iter : int, optional - The max number of fit iterations. Default is 100. - tol : float, optional - The exit criteria. Default is 1e-6. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - eps : float, optional - A small value added to the square of the residual to prevent dividing by 0. - Default is None, which uses the square of the maximum-absolute-value of the - fit each iteration multiplied by 1e-6. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if quantile is not between 0 and 1. - - References - ---------- - Han, Q., et al. Iterative Reweighted Quantile Regression Using Augmented Lagrangian - Optimization for Baseline Correction. 2018 5th International Conference on Information - Science and Control Engineering (ICISCE), 2018, 280-284. - - """ - if not 0 < quantile < 1: - raise ValueError('quantile must be between 0 and 1') - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - old_coef = np.zeros(self.pspline._num_bases) - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline = self.pspline.solve_pspline(y, weight_array) - calc_difference = relative_difference(old_coef, self.pspline.coef) - tol_history[i] = calc_difference - if calc_difference < tol: - break - old_coef = self.pspline.coef - weight_array = _weighting._quantile(y, baseline, quantile, eps) - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register - def corner_cutting(self, data, max_iter=100): - """ - Iteratively removes corner points and creates a Bezier spline from the remaining points. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - max_iter : int, optional - The maximum number of iterations to try to remove corner points. Default is - 100. Typically all corner points are removed in 10 to 20 iterations. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - An empty dictionary, just to match the output of all other algorithms. - - References - ---------- - Liu, Y.J., et al. A Concise Iterative Method with Bezier Technique for Baseline - Construction. Analyst, 2015, 140(23), 7984-7996. - - """ - y, mask = self._setup_spline(data, make_basis=False) - mask = mask.astype(bool, copy=False) - - areas = np.zeros(max_iter) - kept_points = np.zeros(self._len, int) - old_area = np.trapz(y, self.x) - old_sum = self._len - ym = y - xm = self.x - for i in range(max_iter): - new_mask = mask[mask] - new_mask[1:-1] = ( - ym[1:-1] < ym[:-2] + (xm[1:-1] - xm[:-2]) * (ym[2:] - ym[:-2]) / (xm[2:] - xm[:-2]) - ) - mask[mask] = new_mask - - new_sum = mask.sum() - num_corners = old_sum - new_sum - if num_corners == 0: - i -= 1 # subtract 1 so that areas is correctly indexed - break - old_sum = new_sum - - kept_points[mask] += 1 - - xm = self.x[mask] - ym = y[mask] - - # TODO area calculation does not match reference values; need to recheck - # and figure out the correct criteria - # area = ( - # (xm[1:-1] - xm[:-2]) * (ym[1:-1] + ym[:-2]) - # + (xm[2:] - xm[1:-1]) * (ym[2:] + ym[1:-1]) - # - (xm[2:] - xm[:-2]) * (ym[2:] + ym[:-2]) - # ).sum() - area = np.trapz(ym, xm) - areas[i] = (old_area - area) / num_corners - old_area = area - - areas = areas[:i + 1] - - max_area = np.argmax(areas) - 1 # include points before largest area loss - mask = kept_points >= max_area - - baseline = _quadratic_bezier_spline(self.x, y, np.flatnonzero(mask)) - - # TODO maybe return areas and kept_points so that users can decide to use a - # different iteration to build the spline; need to figure out area calculation - # first, and then decide what to return; if so, need to make the bezier spline - # function public and do input validation - return baseline, {} - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_asls(self, data, lam=1e3, p=1e-2, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None): - """ - A penalized spline version of the asymmetric least squares (AsLS) algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - See Also - -------- - pybaselines.whittaker.asls - - References - ---------- - Eilers, P. A Perfect Smoother. Analytical Chemistry, 2003, 75(14), 3631-3636. - - Eilers, P., et al. Baseline correction with asymmetric least squares smoothing. - Leiden University Medical Centre Report, 2005, 1(1). - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline = self.pspline.solve_pspline(y, weight_array) - new_weights = _weighting._asls(y, baseline, p) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_iasls(self, data, lam=1e1, p=1e-2, lam_1=1e-4, num_knots=100, - spline_degree=3, max_iter=50, tol=1e-3, weights=None, diff_order=2): - """ - A penalized spline version of the IAsLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e1. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - lam_1 : float, optional - The smoothing parameter for the first derivative of the residual. Default is 1e-4. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - diff_order : int, optional - The order of the differential matrix. Must be greater than 1. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1 or if `diff_order` is less than 2. - - See Also - -------- - pybaselines.whittaker.iasls - - References - ---------- - He, S., et al. Baseline correction for raman spectra using an improved - asymmetric least squares method, Analytical Methods, 2014, 6(12), 4402-4407. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - elif diff_order < 2: - raise ValueError('diff_order must be 2 or greater') - - if weights is None: - _, _, pseudo_inverse = self._setup_polynomial( - data, weights=None, poly_order=2, calc_vander=True, calc_pinv=True - ) - baseline = self.vandermonde @ (pseudo_inverse @ data) - weights = _weighting._asls(data, baseline, p) - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - - # B.T @ D_1.T @ D_1 @ B and B.T @ D_1.T @ D_1 @ y - d1_penalty = _check_lam(lam_1) * diff_penalty_diagonals(self._len, 1, lower_only=False) - d1_penalty = ( - self.pspline.basis.T - @ spdiags(d1_penalty, np.array([1, 0, -1]), self._len, self._len, 'csr') - ) - partial_rhs = d1_penalty @ y - # now change d1_penalty back to banded array - d1_penalty = (d1_penalty @ self.pspline.basis).todia().data[::-1] - if self.pspline.lower: - d1_penalty = d1_penalty[len(d1_penalty) // 2:] - self.pspline.add_penalty(d1_penalty) - - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline = self.pspline.solve_pspline(y, weight_array**2, rhs_extra=partial_rhs) - new_weights = _weighting._asls(y, baseline, p) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_airpls(self, data, lam=1e3, num_knots=100, spline_degree=3, - diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - A penalized spline version of the airPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.airpls - - References - ---------- - Zhang, Z.M., et al. Baseline correction using adaptive iteratively - reweighted penalized least squares. Analyst, 2010, 135(5), 1138-1146. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam, copy_weights=True - ) - - y_l1_norm = np.abs(y).sum() - tol_history = np.empty(max_iter + 1) - for i in range(1, max_iter + 2): - try: - output = self.pspline.solve_pspline(y, weight_array) - except np.linalg.LinAlgError: - warnings.warn( - ('error occurred during fitting, indicating that "tol"' - ' is too low, "max_iter" is too high, or "lam" is too high'), - ParameterWarning - ) - i -= 1 # reduce i so that output tol_history indexing is correct - break - else: - baseline = output - - residual = y - baseline - neg_mask = residual < 0 - neg_residual = residual[neg_mask] - if len(neg_residual) < 2: - # exit if there are < 2 negative residuals since all points or all but one - # point would get a weight of 0, which fails the solver - warnings.warn( - ('almost all baseline points are below the data, indicating that "tol"' - ' is too low and/or "max_iter" is too high'), ParameterWarning - ) - i -= 1 # reduce i so that output tol_history indexing is correct - break - - residual_l1_norm = abs(neg_residual.sum()) - calc_difference = residual_l1_norm / y_l1_norm - tol_history[i - 1] = calc_difference - if calc_difference < tol: - break - # only use negative residual in exp to avoid exponential overflow warnings - # and accidently creating a weight of nan (inf * 0 = nan) - weight_array[neg_mask] = np.exp(i * neg_residual / residual_l1_norm) - weight_array[~neg_mask] = 0 - - params = {'weights': weight_array, 'tol_history': tol_history[:i]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_arpls(self, data, lam=1e3, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None): - """ - A penalized spline version of the arPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.arpls - - References - ---------- - Baek, S.J., et al. Baseline correction using asymmetrically reweighted - penalized least squares smoothing. Analyst, 2015, 140, 250-257. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline = self.pspline.solve_pspline(y, weight_array) - new_weights = _weighting._arpls(y, baseline) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_drpls(self, data, lam=1e3, eta=0.5, num_knots=100, spline_degree=3, - diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - A penalized spline version of the drPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - eta : float - A term for controlling the value of lam; should be between 0 and 1. - Low values will produce smoother baselines, while higher values will - more aggressively fit peaks. Default is 0.5. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `eta` is not between 0 and 1 or if `diff_order` is less than 2. - - See Also - -------- - pybaselines.whittaker.drpls - - References - ---------- - Xu, D. et al. Baseline correction method based on doubly reweighted - penalized least squares, Applied Optics, 2019, 58, 3913-3920. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - if not 0 <= eta <= 1: - raise ValueError('eta must be between 0 and 1') - elif diff_order < 2: - raise ValueError('diff_order must be 2 or greater') - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam, - allow_lower=False, reverse_diags=False - ) - # B.T @ W @ B + P_1 + (I - eta * W) @ P_n -> B.T @ W @ B + P_1 + P_n - eta * W @ P_n - # reverse P_n for the eta * W @ P_n calculation to match the original diagonal - # structure of the sparse matrix - diff_n_diagonals = -eta * self.pspline.penalty[::-1] - penalty_bands = self.pspline.num_bands - self.pspline.add_penalty(diff_penalty_diagonals(self.pspline._num_bases, 1, False)) - - interp_pts = _basis_midpoints(self.pspline.knots, self.pspline.spline_degree) - tol_history = np.empty(max_iter + 1) - for i in range(1, max_iter + 2): - diff_n_w_diagonals = _shift_rows( - diff_n_diagonals * np.interp(interp_pts, self.x, weight_array), - penalty_bands, penalty_bands - ) - baseline = self.pspline.solve_pspline( - y, weight_array, - penalty=_add_diagonals(self.pspline.penalty, diff_n_w_diagonals, lower_only=False) - ) - new_weights = _weighting._drpls(y, baseline, i) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i - 1] = calc_difference - if not np.isfinite(calc_difference): - # catches nan, inf and -inf due to exp(i) being too high or if there - # are too few negative residuals; no way to catch both conditions before - # new_weights calculation since it is hard to estimate if - # (exp(i) / std) * residual will overflow; check calc_difference rather - # than checking new_weights since non-finite values rarely occur and - # checking a scalar is faster; cannot use np.errstate since it is not 100% reliable - warnings.warn( - ('nan and/or +/- inf occurred in weighting calculation, likely meaning ' - '"tol" is too low and/or "max_iter" is too high'), ParameterWarning - ) - break - elif calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_iarpls(self, data, lam=1e3, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None): - """ - A penalized spline version of the IarPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.iarpls - - References - ---------- - Ye, J., et al. Baseline correction method based on improved asymmetrically - reweighted penalized least squares for Raman spectrum. Applied Optics, 2020, - 59, 10933-10943. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - tol_history = np.empty(max_iter + 1) - for i in range(1, max_iter + 2): - baseline = self.pspline.solve_pspline(y, weight_array) - new_weights = _weighting._iarpls(y, baseline, i) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i - 1] = calc_difference - if not np.isfinite(calc_difference): - # catches nan, inf and -inf due to exp(i) being too high or if there - # are too few negative residuals; no way to catch both conditions before - # new_weights calculation since it is hard to estimate if - # (exp(i) / std) * residual will overflow; check calc_difference rather - # than checking new_weights since non-finite values rarely occur and - # checking a scalar is faster; cannot use np.errstate since it is not 100% reliable - warnings.warn( - ('nan and/or +/- inf occurred in weighting calculation, likely meaning ' - '"tol" is too low and/or "max_iter" is too high'), ParameterWarning - ) - break - elif calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights', 'alpha'), dtype=float, order='C') - def pspline_aspls(self, data, lam=1e4, num_knots=100, spline_degree=3, diff_order=2, - max_iter=100, tol=1e-3, weights=None, alpha=None): - """ - A penalized spline version of the asPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - alpha : array-like, shape (N,), optional - An array of values that control the local value of `lam` to better - fit peak and non-peak regions. If None (default), then the initial values - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'alpha': numpy.ndarray, shape (N,) - The array of alpha values used for fitting the data in the final iteration. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.aspls - - Notes - ----- - The weighting uses an asymmetric coefficient (`k` in the asPLS paper) of 0.5 instead - of the 2 listed in the asPLS paper. pybaselines uses the factor of 0.5 since it - matches the results in Table 2 and Figure 5 of the asPLS paper closer than the - factor of 2 and fits noisy data much better. - - References - ---------- - Zhang, F., et al. Baseline correction for infrared spectra using - adaptive smoothness parameter penalized least squares method. - Spectroscopy Letters, 2020, 53(3), 222-233. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam, - allow_lower=False, reverse_diags=True - ) - alpha_array = _check_optional_array( - self._len, alpha, check_finite=self._check_finite, name='alpha' - ) - if self._sort_order is not None and alpha is not None: - alpha_array = alpha_array[self._sort_order] - - interp_pts = _basis_midpoints(self.pspline.knots, self.pspline.spline_degree) - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - # convert alpha_array from len(y) to basis.shape[1] - alpha_penalty = _shift_rows( - self.pspline.penalty * np.interp(interp_pts, self.x, alpha_array), - self.pspline.num_bands, self.pspline.num_bands - ) - baseline = self.pspline.solve_pspline(y, weight_array, alpha_penalty) - new_weights, residual = _weighting._aspls(y, baseline) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - abs_d = np.abs(residual) - alpha_array = abs_d / abs_d.max() - - params = { - 'weights': weight_array, 'alpha': alpha_array, 'tol_history': tol_history[:i + 1] - } - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_psalsa(self, data, lam=1e3, p=0.5, k=None, num_knots=100, spline_degree=3, - diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - A penalized spline version of the psalsa algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 0.5. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - See Also - -------- - pybaselines.whittaker.psalsa - - References - ---------- - Oller-Moreno, S., et al. Adaptive Asymmetric Least Squares baseline estimation - for analytical instruments. 2014 IEEE 11th International Multi-Conference on - Systems, Signals, and Devices, 2014, 1-5. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - if k is None: - k = np.std(y) / 10 - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline = self.pspline.solve_pspline(y, weight_array) - new_weights = _weighting._psalsa(y, baseline, p, k, self._len) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',), dtype=float, order='C') - def pspline_derpsalsa(self, data, lam=1e2, p=1e-2, k=None, num_knots=100, spline_degree=3, - diff_order=2, max_iter=50, tol=1e-3, weights=None, - smooth_half_window=None, num_smooths=16, **pad_kwargs): - """ - A penalized spline version of the derpsalsa algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e2. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - smooth_half_window : int, optional - The half-window to use for smoothing the data before computing the first - and second derivatives. Default is None, which will use ``len(data) / 200``. - num_smooths : int, optional - The number of times to smooth the data before computing the first - and second derivatives. Default is 16. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - See Also - -------- - pybaselines.whittaker.derpsalsa - - References - ---------- - Korepanov, V. Asymmetric least-squares baseline algorithm with peak screening for - automatic processing of the Raman spectra. Journal of Raman Spectroscopy. 2020, - 51(10), 2061-2065. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - y, weight_array = self._setup_spline( - data, weights, spline_degree, num_knots, True, diff_order, lam - ) - if k is None: - k = np.std(y) / 10 - - if smooth_half_window is None: - smooth_half_window = self._len // 200 - # could pad the data every iteration, but it is ~2-3 times slower and only affects - # the edges, so it's not worth it - y_smooth = pad_edges(y, smooth_half_window, **pad_kwargs) - if smooth_half_window > 0: - smooth_kernel = _mollifier_kernel(smooth_half_window) - for _ in range(num_smooths): - y_smooth = padded_convolve(y_smooth, smooth_kernel) - y_smooth = y_smooth[smooth_half_window:self._len + smooth_half_window] - - diff_y_1 = np.gradient(y_smooth) - diff_y_2 = np.gradient(diff_y_1) - # x.dot(x) is same as (x**2).sum() but faster - rms_diff_1 = np.sqrt(diff_y_1.dot(diff_y_1) / self._len) - rms_diff_2 = np.sqrt(diff_y_2.dot(diff_y_2) / self._len) - - diff_1_weights = np.exp(-((diff_y_1 / rms_diff_1)**2) / 2) - diff_2_weights = np.exp(-((diff_y_2 / rms_diff_2)**2) / 2) - partial_weights = diff_1_weights * diff_2_weights - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - baseline = self.pspline.solve_pspline(y, weight_array) - new_weights = _weighting._derpsalsa(y, baseline, p, k, self._len, partial_weights) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - -_spline_wrapper = _class_wrapper(_Spline) - - -@jit(nopython=True, cache=True) -def _numba_mapped_histogram(data, num_bins, histogram): - """ - Creates a normalized histogram of the data and a mapping of the indices, using one pass. - - Parameters - ---------- - data : numpy.ndarray, shape (N,) - The data to be made into a histogram. - num_bins : int - The number of bins for the histogram. - histogram : numpy.ndarray - An array of zeros that will be modified inplace into the histogram. - - Returns - ------- - bins : numpy.ndarray, shape (`num_bins` + 1) - The bin edges for the histogram. Follows numpy's implementation such that - each bin is inclusive on the left edge and exclusive on the right edge, except - for the last bin which is inclusive on both edges. - bin_mapping : numpy.ndarray, shape (N,) - An array of integers that maps each item in `data` to its index within `histogram`. - - Notes - ----- - `histogram` is modified inplace and converted to a probability density function - (total area = 1) after the counting. - - """ - num_data = data.shape[0] - bins = np.linspace(data.min(), data.max(), num_bins + 1) - bin_mapping = np.empty(num_data, dtype=np.intp) - bin_frequency = num_bins / (bins[-1] - bins[0]) - bin_0 = bins[0] - last_index = num_bins - 1 - # TODO this seems like it would work in parallel, but it instead slows down - for i in range(num_data): - index = int((data[i] - bin_0) * bin_frequency) - if index == num_bins: - histogram[last_index] += 1 - bin_mapping[i] = last_index - else: - histogram[index] += 1 - bin_mapping[i] = index - - # normalize histogram such that area=1 so that it is a probability density function - histogram /= (num_data * (bins[1] - bins[0])) - - return bins, bin_mapping - - -def _mapped_histogram(data, num_bins): - """ - Creates a histogram of the data and a mapping of the indices. - - Parameters - ---------- - data : numpy.ndarray, shape (N,) - The data to be made into a histogram. - num_bins : int - The number of bins for the histogram. - - Returns - ------- - histogram : numpy.ndarray, shape (`num_bins`) - The histogram of the data, normalized so that its area is 1. - bins : numpy.ndarray, shape (`num_bins` + 1) - The bin edges for the histogram. Follows numpy's implementation such that - each bin is inclusive on the left edge and exclusive on the right edge, except - for the last bin which is inclusive on both edges. - bin_mapping : numpy.ndarray, shape (N,) - An array of integers that maps each item in `data` to its index within `histogram`. - - Notes - ----- - If numba is installed, the histogram and bin mapping can both be created in - one pass, which is faster. - - """ - if _HAS_NUMBA: - # create zeros array outside of numba function since numba's implementation - # of np.zeros is much slower than numpy's (https://github.com/numba/numba/issues/7259) - histogram = np.zeros(num_bins) - bins, bin_mapping = _numba_mapped_histogram(data, num_bins, histogram) - else: - histogram, bins = np.histogram(data, num_bins, density=True) - # leave out last bin edge to account for extra index; leave out first - # bin edge since np.searchsorted finds indices where bin[i-1] <= val < bin[i] - # while the desired indices are bin[i] <= val < bin[i + 1] - bin_mapping = np.searchsorted(bins[1:-1], data, 'right') - - return histogram, bins, bin_mapping - - -def _mixture_pdf(x, n, sigma, n_2=0, pos_uniform=None, neg_uniform=None): - """ - The probability density function of a Gaussian and one or two uniform distributions. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values of the distribution. - n : float - The fraction of the distribution belonging to the Gaussian. - sigma : float - Log10 of the standard deviation of the Gaussian distribution. - n_2 : float, optional - If `neg_uniform` or `pos_uniform` is None, then `n_2` is just an unused input. - Otherwise, it is the fraction of the distribution belonging to the positive - uniform distribution. Default is 0. - pos_uniform : numpy.ndarray, shape (N,), optional - The array of the positive uniform distributtion. Default is None. - neg_uniform : numpy.ndarray, shape (N,), optional - The array of the negative uniform distribution. Default is None. - - Returns - ------- - numpy.ndarray - The total probability density function for the mixture model. - - Notes - ----- - Defining `sigma` as ``log10(actual sigma)`` allows not bounding `sigma` during - optimization and allows it to more easily fit different scales. - - References - ---------- - de Rooi, J., et al. Mixture models for baseline estimation. Chemometric and - Intelligent Laboratory Systems, 2012, 117, 56-60. - - """ - # no error handling for if both pos_uniform and neg_uniform are None since this - # is an internal function - if neg_uniform is None: - n1 = n - n2 = 1 - n - n3 = 0 - neg_uniform = 0 - elif pos_uniform is None: # never actually used, but nice to have for the future - n1 = n - n2 = 0 - n3 = 1 - n - pos_uniform = 0 - else: - n1 = n - n2 = n_2 - n3 = 1 - n - n_2 - - actual_sigma = 10**sigma - # the gaussian should be area-normalized, so set height accordingly - height = 1 / max(actual_sigma * np.sqrt(2 * np.pi), _MIN_FLOAT) - - return n1 * gaussian(x, height, 0, actual_sigma) + n2 * pos_uniform + n3 * neg_uniform - - -@_spline_wrapper -def mixture_model(data, lam=1e5, p=1e-2, num_knots=100, spline_degree=3, diff_order=3, - max_iter=50, tol=1e-3, weights=None, symmetric=False, num_bins=None, - x_data=None): - """ - Considers the data as a mixture model composed of noise and peaks. - - Weights are iteratively assigned by calculating the probability each value in - the residual belongs to a normal distribution representing the noise. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Used to set the initial weights before performing - expectation-maximization. Default is 1e-2. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 3 - (third order differential matrix). Typical values are 2 or 3. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1, and then - two iterations of reweighted least-squares are performed to provide starting - weights for the expectation-maximization of the mixture model. - symmetric : bool, optional - If False (default), the total mixture model will be composed of one normal - distribution for the noise and one uniform distribution for positive non-noise - residuals. If True, an additional uniform distribution will be added to the - mixture model for negative non-noise residuals. Only need to set `symmetric` - to True when peaks are both positive and negative. - num_bins : int, optional - The number of bins to use when transforming the residuals into a probability - density distribution. Default is None, which uses ``ceil(sqrt(N))``. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if p is not between 0 and 1. - - References - ---------- - de Rooi, J., et al. Mixture models for baseline estimation. Chemometric and - Intelligent Laboratory Systems, 2012, 117, 56-60. - - """ - - -@_spline_wrapper -def irsqr(data, lam=100, quantile=0.05, num_knots=100, spline_degree=3, diff_order=3, - max_iter=100, tol=1e-6, weights=None, eps=None, x_data=None): - """ - Iterative Reweighted Spline Quantile Regression (IRSQR). - - Fits the baseline using quantile regression with penalized splines. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - quantile : float, optional - The quantile at which to fit the baseline. Default is 0.05. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 3 - (third order differential matrix). Typical values are 3, 2, or 1. - max_iter : int, optional - The max number of fit iterations. Default is 100. - tol : float, optional - The exit criteria. Default is 1e-6. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - eps : float, optional - A small value added to the square of the residual to prevent dividing by 0. - Default is None, which uses the square of the maximum-absolute-value of the - fit each iteration multiplied by 1e-6. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if quantile is not between 0 and 1. - - References - ---------- - Han, Q., et al. Iterative Reweighted Quantile Regression Using Augmented Lagrangian - Optimization for Baseline Correction. 2018 5th International Conference on Information - Science and Control Engineering (ICISCE), 2018, 280-284. - - """ - - -@jit(nopython=True, cache=True) -def _quadratic_bezier(y_points, t): - """ - Makes a single quadratic Bezier curve weighted by y-values. - - Parameters - ---------- - y_points : Container - A container of the three y-values that define the y-values of the - Bezier curve. - t : numpy.ndarray, shape (N,) - The array of values between 0 and 1 defining the Bezier curve. - - Returns - ------- - output : numpy.ndarray, shape (N,) - The Bezier curve for `t`, using the three points in `y_points` as weights - in order to shift the curve to match the desired y-values. - - References - ---------- - https://pomax.github.io/bezierinfo (if the link is dead, the GitHub repo for the - website is https://github.com/Pomax/BezierInfo-2). - - """ - one_minus_t = 1 - t - output = ( - y_points[0] * one_minus_t**2 + y_points[1] * 2 * one_minus_t * t + y_points[2] * t**2 - ) - return output - - -@jit(nopython=True, cache=True) -def _quadratic_bezier_spline(x, y, indices): - """ - Creates a spline from multiple quadratic Bezier curves. - - Parameters - ---------- - x : numpy.ndarray, shape (N,) - The x-values for the spline. - y : numpy.ndarray, shape (N,) - The y-values for the spline. - indices : numpy.ndarray, shape (M,) - An array of the indices of the control points for the Bezier - spline within `x` and `y`. - - Returns - ------- - output : numpy.ndarray, shape (N,) - The curve constructed from the control points. - - Raises - ------ - ValueError - Raised if the number of indices, `M`, is less than 2, or if `x` and `y` do not - have the same number of points. - - Notes - ----- - If `M` is 2, then the output is a linear interpolation. If `M` is 3, the output is - a single Bezier curve using the three control points. Otherwise, a Bezier spline - is constructed by inserting additional control points between each index in - `indices[2:-2]` to join the individual Bezier curves, following the reference. - - The resulting spline is only guaranteed to be C0 continuous (ie. no discontinuities). - Any derivatives are not guaranteed to be continuous. - - References - ---------- - Liu, Y.J., et al. A Concise Iterative Method with Bezier Technique for Baseline - Construction. Analyst, 2015, 140(23), 7984-7996. - - """ - num_indices = indices.shape[0] - num_x = x.shape[0] - if num_x != y.shape[0]: - raise ValueError('x and y must have the same number of points') - if num_indices < 2: - raise ValueError('indices must have at least two points for a Bezier curve') - elif num_indices < 4: - left_idx = indices[0] - right_idx = indices[-1] - left_x = x[left_idx] - right_x = x[right_idx] - left_y = y[left_idx] - right_y = y[right_idx] - if num_indices == 2: # perform linear interpolation - output = left_y + (x - left_x) * (right_y - left_y) / (right_x - left_x) - else: # create a single Bezier curve from the three points - center_y = y[indices[1]] - y_points = [left_y, center_y, right_y] - t = (x - left_x) / (right_x - left_x) - output = _quadratic_bezier(y_points, t) - - return output - - output = np.empty(num_x) - - # first segment uses first two indices and an added halfway-point - center_idx = indices[1] - next_idx = indices[2] - left_x = x[indices[0]] - center_x = x[center_idx] - right_idx = ( - center_idx + np.argmin(np.abs(x[center_idx:next_idx + 1] - 0.5 * (center_x + x[next_idx]))) - ) - right_x = x[right_idx] - - left_y = y[indices[0]] - center_y = y[center_idx] - right_y = center_y + (right_x - center_x) * (y[next_idx] - center_y) / (x[next_idx] - center_x) - y_points = [left_y, center_y, right_y] - t = (x[:next_idx + 1] - left_x) / (right_x - left_x) - output[:next_idx + 1] = _quadratic_bezier(y_points, t) - - for i, center_idx in enumerate(indices[2:-2], 2): - left_idx = right_idx - left_x = right_x - next_idx = indices[i + 1] - center_x = x[center_idx] - right_idx = ( - center_idx - + np.argmin(np.abs(x[center_idx:next_idx + 1] - 0.5 * (center_x + x[next_idx]))) - ) - right_x = x[right_idx] - if right_x - left_x == 0: - continue - - left_y = right_y - center_y = y[center_idx] - right_y = ( - center_y + (right_x - center_x) * (y[next_idx] - center_y) / (x[next_idx] - center_x) - ) - y_points = [left_y, center_y, right_y] - - t = (x[left_idx:right_idx + 1] - left_x) / (right_x - left_x) - output[left_idx:right_idx + 1] = _quadratic_bezier(y_points, t) - - # last segment uses last two indices and the last added halfway-point - y_points = [right_y, y[indices[-2]], y[indices[-1]]] - t = (x[right_idx:] - right_x) / (x[indices[-1]] - right_x) - output[right_idx:] = _quadratic_bezier(y_points, t) - - return output - - -@_spline_wrapper -def corner_cutting(data, x_data=None, max_iter=100): - """ - Iteratively removes corner points and creates a Bezier spline from the remaining points. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - max_iter : int, optional - The maximum number of iterations to try to remove corner points. Default is - 100. Typically all corner points are removed in 10 to 20 iterations. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - dict - An empty dictionary, just to match the output of all other algorithms. - - References - ---------- - Liu, Y.J., et al. A Concise Iterative Method with Bezier Technique for Baseline - Construction. Analyst, 2015, 140(23), 7984-7996. - - """ - - -@_spline_wrapper -def pspline_asls(data, lam=1e3, p=1e-2, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - A penalized spline version of the asymmetric least squares (AsLS) algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - See Also - -------- - pybaselines.whittaker.asls - - References - ---------- - Eilers, P. A Perfect Smoother. Analytical Chemistry, 2003, 75(14), 3631-3636. - - Eilers, P., et al. Baseline correction with asymmetric least squares smoothing. - Leiden University Medical Centre Report, 2005, 1(1). - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_iasls(data, x_data=None, lam=1e1, p=1e-2, lam_1=1e-4, num_knots=100, - spline_degree=3, max_iter=50, tol=1e-3, weights=None, diff_order=2): - """ - A penalized spline version of the IAsLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e1. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - lam_1 : float, optional - The smoothing parameter for the first derivative of the residual. Default is 1e-4. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - diff_order : int, optional - The order of the differential matrix. Must be greater than 1. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1 or if `diff_order` is less than 2. - - See Also - -------- - pybaselines.whittaker.iasls - - References - ---------- - He, S., et al. Baseline correction for raman spectra using an improved - asymmetric least squares method, Analytical Methods, 2014, 6(12), 4402-4407. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_airpls(data, lam=1e3, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - A penalized spline version of the airPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.airpls - - References - ---------- - Zhang, Z.M., et al. Baseline correction using adaptive iteratively - reweighted penalized least squares. Analyst, 2010, 135(5), 1138-1146. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_arpls(data, lam=1e3, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - A penalized spline version of the arPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.arpls - - References - ---------- - Baek, S.J., et al. Baseline correction using asymmetrically reweighted - penalized least squares smoothing. Analyst, 2015, 140, 250-257. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_drpls(data, lam=1e3, eta=0.5, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - A penalized spline version of the drPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - eta : float - A term for controlling the value of lam; should be between 0 and 1. - Low values will produce smoother baselines, while higher values will - more aggressively fit peaks. Default is 0.5. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `eta` is not between 0 and 1 or if `diff_order` is less than 2. - - See Also - -------- - pybaselines.whittaker.drpls - - References - ---------- - Xu, D. et al. Baseline correction method based on doubly reweighted - penalized least squares, Applied Optics, 2019, 58, 3913-3920. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_iarpls(data, lam=1e3, num_knots=100, spline_degree=3, diff_order=2, - max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - A penalized spline version of the IarPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.iarpls - - References - ---------- - Ye, J., et al. Baseline correction method based on improved asymmetrically - reweighted penalized least squares for Raman spectrum. Applied Optics, 2020, - 59, 10933-10943. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_aspls(data, lam=1e4, num_knots=100, spline_degree=3, diff_order=2, - max_iter=100, tol=1e-3, weights=None, alpha=None, x_data=None): - """ - A penalized spline version of the asPLS algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - alpha : array-like, shape (N,), optional - An array of values that control the local value of `lam` to better - fit peak and non-peak regions. If None (default), then the initial values - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'alpha': numpy.ndarray, shape (N,) - The array of alpha values used for fitting the data in the final iteration. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - See Also - -------- - pybaselines.whittaker.aspls - - Notes - ----- - The weighting uses an asymmetric coefficient (`k` in the asPLS paper) of 0.5 instead - of the 2 listed in the asPLS paper. pybaselines uses the factor of 0.5 since it - matches the results in Table 2 and Figure 5 of the asPLS paper closer than the - factor of 2 and fits noisy data much better. - - References - ---------- - Zhang, F., et al. Baseline correction for infrared spectra using - adaptive smoothness parameter penalized least squares method. - Spectroscopy Letters, 2020, 53(3), 222-233. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_psalsa(data, lam=1e3, p=0.5, k=None, num_knots=100, spline_degree=3, - diff_order=2, max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - A penalized spline version of the psalsa algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e3. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 0.5. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - See Also - -------- - pybaselines.whittaker.psalsa - - References - ---------- - Oller-Moreno, S., et al. Adaptive Asymmetric Least Squares baseline estimation - for analytical instruments. 2014 IEEE 11th International Multi-Conference on - Systems, Signals, and Devices, 2014, 1-5. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ - - -@_spline_wrapper -def pspline_derpsalsa(data, lam=1e2, p=1e-2, k=None, num_knots=100, spline_degree=3, - diff_order=2, max_iter=50, tol=1e-3, weights=None, - smooth_half_window=None, num_smooths=16, x_data=None, **pad_kwargs): - """ - A penalized spline version of the derpsalsa algorithm. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e2. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - num_knots : int, optional - The number of knots for the spline. Default is 100. - spline_degree : int, optional - The degree of the spline. Default is 3, which is a cubic spline. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - smooth_half_window : int, optional - The half-window to use for smoothing the data before computing the first - and second derivatives. Default is None, which will use ``len(data) / 200``. - num_smooths : int, optional - The number of times to smooth the data before computing the first - and second derivatives. Default is 16. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - See Also - -------- - pybaselines.whittaker.derpsalsa - - References - ---------- - Korepanov, V. Asymmetric least-squares baseline algorithm with peak screening for - automatic processing of the Raman spectra. Journal of Raman Spectroscopy. 2020, - 51(10), 2061-2065. - - Eilers, P., et al. Splines, knots, and penalties. Wiley Interdisciplinary - Reviews: Computational Statistics, 2010, 2(6), 637-653. - - """ diff --git a/GSASII/pybaselines/utils.py b/GSASII/pybaselines/utils.py deleted file mode 100644 index f91b34162..000000000 --- a/GSASII/pybaselines/utils.py +++ /dev/null @@ -1,577 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper functions for pybaselines. - -Created on March 5, 2021 -@author: Donald Erb - -""" - -from math import ceil - -import numpy as np -from scipy.ndimage import grey_opening -from scipy.signal import convolve - -from ._banded_utils import PenalizedSystem, difference_matrix as _difference_matrix -from ._compat import jit -from ._validation import _check_array, _check_scalar, _check_optional_array - - -# the minimum positive float values such that a + _MIN_FLOAT != a -# TODO this is mostly used to prevent dividing by 0; is there a better way to do that? -# especially since it is usually max(value, _MIN_FLOAT) and in some cases value could be -# < _MIN_FLOAT but still > 0 and useful; think about it -_MIN_FLOAT = np.finfo(float).eps - - -class ParameterWarning(UserWarning): - """ - Warning issued when a parameter value is outside of the recommended range. - - For cases where a parameter value is valid and will not cause errors, but is - outside of the recommended range of values and as a result may cause issues - such as numerical instability that would otherwise be hard to diagnose. - """ - - -def relative_difference(old, new, norm_order=None): - """ - Calculates the relative difference, ``(norm(new-old) / norm(old))``, of two values. - - Used as an exit criteria in many baseline algorithms. - - Parameters - ---------- - old : numpy.ndarray or float - The array or single value from the previous iteration. - new : numpy.ndarray or float - The array or single value from the current iteration. - norm_order : int, optional - The type of norm to calculate. Default is None, which is l2 - norm for arrays, abs for scalars. - - Returns - ------- - float - The relative difference between the old and new values. - - """ - numerator = np.linalg.norm(new - old, norm_order) - denominator = np.maximum(np.linalg.norm(old, norm_order), _MIN_FLOAT) - return numerator / denominator - - -def gaussian(x, height=1.0, center=0.0, sigma=1.0): - """ - Generates a gaussian distribution based on height, center, and sigma. - - Parameters - ---------- - x : numpy.ndarray - The x-values at which to evaluate the distribution. - height : float, optional - The maximum height of the distribution. Default is 1.0. - center : float, optional - The center of the distribution. Default is 0.0. - sigma : float, optional - The standard deviation of the distribution. Default is 1.0. - - Returns - ------- - numpy.ndarray - The gaussian distribution evaluated with x. - - """ - return height * np.exp(-0.5 * ((x - center)**2) / max(sigma, _MIN_FLOAT)**2) - - -def gaussian_kernel(window_size, sigma=1.0): - """ - Creates an area-normalized gaussian kernel for convolution. - - Parameters - ---------- - window_size : int - The number of points for the entire kernel. - sigma : float, optional - The standard deviation of the gaussian model. - - Returns - ------- - numpy.ndarray, shape (window_size,) - The area-normalized gaussian kernel. - - Notes - ----- - Return gaus/sum(gaus) rather than creating a unit-area gaussian - since the unit-area gaussian would have an area smaller than 1 - for window_size < ~ 6 * sigma. - - """ - # centers distribution from -half_window to half_window - window_size = max(1, window_size) - x = np.arange(window_size) - (window_size - 1) / 2 - gaus = gaussian(x, 1, 0, sigma) - return gaus / np.sum(gaus) - - -def _mollifier_kernel(window_size): - """ - A kernel for smoothing/mollification. - - Parameters - ---------- - window_size : int - The number of points for the entire kernel. - - Returns - ------- - numpy.ndarray, shape (2 * window_size + 1,) - The area normalized kernel. - - References - ---------- - Chen, H., et al. An Adaptive and Fully Automated Baseline Correction - Method for Raman Spectroscopy Based on Morphological Operations and - Mollifications. Applied Spectroscopy, 2019, 73(3), 284-293. - - """ - x = (np.arange(0, 2 * window_size + 1) - window_size) / window_size - kernel = np.zeros_like(x) - # x[1:-1] is same as x[abs(x) < 1] - kernel[1:-1] = np.exp(-1 / (1 - (x[1:-1])**2)) - return kernel / kernel.sum() - - -def _get_edges(data, pad_length, mode='extrapolate', extrapolate_window=None, **pad_kwargs): - """ - Provides the left and right edges for padding data. - - Parameters - ---------- - data : array-like - The array of the data. - pad_length : int - The number of points to add to the left and right edges. - mode : str or Callable, optional - The method for padding. Default is 'extrapolate'. Any method other than - 'extrapolate' will use numpy.pad. - extrapolate_window : int, optional - The number of values to use for linear fitting on the left and right - edges. Default is None, which will set the extrapolate window size equal - to `pad_length`. - **pad_kwargs - Any keyword arguments to pass to numpy.pad, which will be used if `mode` - is not 'extrapolate'. - - Returns - ------- - left_edge : numpy.ndarray, shape(pad_length,) - The array of data for the left padding. - right_edge : numpy.ndarray, shape(pad_length,) - The array of data for the right padding. - - Raises - ------ - ValueError - Raised if `pad_length` is < 0, or if `extrapolate_window` is <= 0 and - `mode` is `extrapolate`. - - Notes - ----- - If mode is 'extrapolate', then the left and right edges will be fit with - a first order polynomial and then extrapolated. Otherwise, uses :func:`numpy.pad`. - - """ - y = np.asarray(data) - if pad_length == 0: - return np.array([]), np.array([]) - elif pad_length < 0: - raise ValueError('pad length must be greater or equal to 0') - - if isinstance(mode, str): - mode = mode.lower() - if mode == 'extrapolate': - if extrapolate_window is None: - extrapolate_window = pad_length - extrapolate_windows = _check_scalar(extrapolate_window, 2, True, dtype=int)[0] - - if np.any(extrapolate_windows <= 0): - raise ValueError('extrapolate_window must be greater than 0') - left_edge = np.empty(pad_length) - right_edge = np.empty(pad_length) - # use x[pad_length:-pad_length] for fitting to ensure x and y are - # same shape regardless of extrapolate window value - x = np.arange(len(y) + 2 * pad_length) - for i, array in enumerate((left_edge, right_edge)): - extrapolate_window_i = extrapolate_windows[i] - if extrapolate_window_i == 1: - # just use the edges rather than trying to fit a line - array[:] = y[0] if i == 0 else y[-1] - elif i == 0: - poly = np.polynomial.Polynomial.fit( - x[pad_length:-pad_length][:extrapolate_window_i], - y[:extrapolate_window_i], 1 - ) - array[:] = poly(x[:pad_length]) - else: - poly = np.polynomial.Polynomial.fit( - x[pad_length:-pad_length][-extrapolate_window_i:], - y[-extrapolate_window_i:], 1 - ) - array[:] = poly(x[-pad_length:]) - else: - padded_data = np.pad(y, pad_length, mode, **pad_kwargs) - left_edge = padded_data[:pad_length] - right_edge = padded_data[-pad_length:] - - return left_edge, right_edge - - -def pad_edges(data, pad_length, mode='extrapolate', - extrapolate_window=None, **pad_kwargs): - """ - Adds left and right edges to the data. - - Parameters - ---------- - data : array-like - The array of the data. - pad_length : int - The number of points to add to the left and right edges. - mode : str or Callable, optional - The method for padding. Default is 'extrapolate'. Any method other than - 'extrapolate' will use :func:`numpy.pad`. - extrapolate_window : int, optional - The number of values to use for linear fitting on the left and right - edges. Default is None, which will set the extrapolate window size equal - to `pad_length`. - **pad_kwargs - Any keyword arguments to pass to :func:`numpy.pad`, which will be used if `mode` - is not 'extrapolate'. - - Returns - ------- - padded_data : numpy.ndarray, shape (N + 2 * half_window,) - The data with padding on the left and right edges. - - Notes - ----- - If mode is 'extrapolate', then the left and right edges will be fit with - a first order polynomial and then extrapolated. Otherwise, uses :func:`numpy.pad`. - - """ - y = np.asarray(data) - if pad_length == 0: - return y - - if isinstance(mode, str): - mode = mode.lower() - if mode == 'extrapolate': - left_edge, right_edge = _get_edges(y, pad_length, mode, extrapolate_window) - padded_data = np.concatenate((left_edge, y, right_edge)) - else: - padded_data = np.pad(y, pad_length, mode, **pad_kwargs) - - return padded_data - - -def padded_convolve(data, kernel, mode='reflect', **pad_kwargs): - """ - Pads data before convolving to reduce edge effects. - - Parameters - ---------- - data : array-like, shape (N,) - The data to convolve. - kernel : array-like, shape (M,) - The convolution kernel. - mode : str or Callable, optional - The method for padding to pass to :func:`.pad_edges`. Default is 'reflect'. - **pad_kwargs - Any additional keyword arguments to pass to :func:`.pad_edges`. - - Returns - ------- - convolution : numpy.ndarray, shape (N,) - The convolution output. - - """ - # TODO need to revisit this and ensure everything is correct - # TODO look at using scipy.ndimage.convolve1d instead, or at least - # comparing the output in tests; that function should have a similar usage - padding = ceil(min(len(data), len(kernel)) / 2) - convolution = convolve( - pad_edges(data, padding, mode, **pad_kwargs), kernel, mode='same' - ) - return convolution[padding:-padding] - - -@jit(nopython=True, cache=True) -def _interp_inplace(x, y, y_start, y_end): - """ - Interpolates values inplace between the two ends of an array. - - Parameters - ---------- - x : numpy.ndarray - The x-values for interpolation. All values are assumed to be valid. - y : numpy.ndarray - The y-values. The two endpoints, y[0] and y[-1] are assumed to be valid, - and all values inbetween (ie. y[1:-1]) will be replaced by interpolation. - y_start : float, optional - The initial y-value for interpolation. - y_end : float, optional - The end y-value for interpolation. - - Returns - ------- - y : numpy.ndarray - The input `y` array, with the interpolation performed inplace. - - """ - y[1:-1] = y_start + (x[1:-1] - x[0]) * ((y_end - y_start) / (x[-1] - x[0])) - - return y - - -def _convert_coef(coef, original_domain): - """ - Scales the polynomial coefficients back to the original domain of the data. - - For fitting, the x-values are scaled from their original domain, [min(x), - max(x)], to [-1, 1] in order to improve the numerical stability of fitting. - This function rescales the retrieved polynomial coefficients for the fit - x-values back to the original domain. - - Parameters - ---------- - coef : array-like - The array of coefficients for the polynomial. Should increase in - order, for example (c0, c1, c2) from `y = c0 + c1 * x + c2 * x**2`. - original_domain : array-like, shape (2,) - The domain, [min(x), max(x)], of the original data used for fitting. - - Returns - ------- - output_coefs : numpy.ndarray - The array of coefficients scaled for the original domain. - - """ - zeros_mask = np.equal(coef, 0) - if zeros_mask.any(): - # coefficients with one or several zeros sometimes get compressed - # to leave out some of the coefficients, so replace zero with another value - # and then fill in later - coef = coef.copy() - coef[zeros_mask] = _MIN_FLOAT # could probably fill it with any non-zero value - - fit_polynomial = np.polynomial.Polynomial(coef, domain=original_domain) - output_coefs = fit_polynomial.convert().coef - output_coefs[zeros_mask] = 0 - - return output_coefs - - -def difference_matrix(data_size, diff_order=2, diff_format=None): - """ - Creates an n-order finite-difference matrix. - - Parameters - ---------- - data_size : int - The number of data points. - diff_order : int, optional - The integer differential order; must be >= 0. Default is 2. - diff_format : str or None, optional - The sparse format to use for the difference matrix. Default is None, - which will use the default specified in :func:`scipy.sparse.diags`. - - Returns - ------- - diff_matrix : scipy.sparse.base.spmatrix - The sparse difference matrix. - - Raises - ------ - ValueError - Raised if `diff_order` or `data_size` is negative. - - Notes - ----- - The resulting matrices are sparse versions of:: - - import numpy as np - np.diff(np.eye(data_size), diff_order, axis=0) - - This implementation allows using the differential matrices are they - are written in various publications, ie. ``D.T @ D``. - - Most baseline algorithms use 2nd order differential matrices when - doing penalized least squared fitting or Whittaker-smoothing-based fitting. - - """ - # difference_matrix moved to pybaselines._banded_utils in version 1.0.0 in order to more - # easily use it in other modules without creating circular imports; this function - # exposes it through pybaselines.utils for backwards compatibility in user code - return _difference_matrix(data_size, diff_order=diff_order, diff_format=diff_format) - - -def optimize_window(data, increment=1, max_hits=3, window_tol=1e-6, - max_half_window=None, min_half_window=None): - """ - Optimizes the morphological half-window size. - - Parameters - ---------- - data : array-like, shape (N,) - The measured data values. - increment : int, optional - The step size for iterating half windows. Default is 1. - max_hits : int, optional - The number of consecutive half windows that must produce the same - morphological opening before accepting the half window as the optimum - value. Default is 3. - window_tol : float, optional - The tolerance value for considering two morphological openings as - equivalent. Default is 1e-6. - max_half_window : int, optional - The maximum allowable half-window size. If None (default), will be set - to (len(data) - 1) / 2. - min_half_window : int, optional - The minimum half-window size. If None (default), will be set to 1. - - Returns - ------- - half_window : int - The optimized half window size. - - Notes - ----- - May only provide good results for some morphological algorithms, so use with - caution. - - References - ---------- - Perez-Pueyo, R., et al. Morphology-Based Automated Baseline Removal for - Raman Spectra of Artistic Pigments. Applied Spectroscopy, 2010, 64, 595-600. - - """ - y = np.asarray(data) - if max_half_window is None: - max_half_window = (y.shape[0] - 1) // 2 - if min_half_window is None: - min_half_window = 1 - - # TODO would it be better to allow padding the data? - opening = grey_opening(y, [2 * min_half_window + 1]) - hits = 0 - best_half_window = min_half_window - for half_window in range(min_half_window + increment, max_half_window, increment): - new_opening = grey_opening(y, [half_window * 2 + 1]) - if relative_difference(opening, new_opening) < window_tol: - if hits == 0: - # keep just the first window that fits tolerance - best_half_window = half_window - increment - hits += 1 - if hits >= max_hits: - half_window = best_half_window - break - elif hits: - hits = 0 - opening = new_opening - - return max(half_window, 1) # ensure half window is at least 1 - - -def _inverted_sort(sort_order): - """ - Finds the indices that invert a sorting. - - Given an array `a`, and the indices that sort the array, `sort_order`, the - inverted sort is defined such that it gives the original index order of `a`, - ie. ``a == a[sort_order][inverted_order]``. - - Parameters - ---------- - sort_order : numpy.ndarray, shape (N,) - The original index array for sorting. - - Returns - ------- - inverted_order : numpy.ndarray, shape (N,) - The array that inverts the sort given by `sort_order`. - - Notes - ----- - This function is equivalent to doing:: - - inverted_order = sort_order.argsort() - - but is faster for large arrays since no additional sorting is performed. - - """ - num_points = len(sort_order) - inverted_order = np.empty(num_points, dtype=np.intp) - inverted_order[sort_order] = np.arange(num_points, dtype=np.intp) - - return inverted_order - - -def whittaker_smooth(data, lam=1e6, diff_order=2, weights=None, check_finite=True, - penalized_system=None): - """ - Smooths the input data using Whittaker smoothing. - - The input is smoothed by solving the equation ``(W + lam * D.T @ D) y_smooth = W @ y``, - where `W` is a matrix with `weights` on the diagonals and `D` is the finite difference - matrix. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - diff_order : int, optional - The order of the finite difference matrix. Must be greater than or equal to 0. - Default is 2 (second order differential matrix). Typical values are 2 or 1. - weights : array-like, shape (N,), optional - The weighting array, used to override the function's baseline identification - to designate peak points. Only elements with 0 or False values will have - an effect; all non-zero values are considered baseline points. If None - (default), then will be an array with size equal to N and all values set to 1. - check_finite : bool, optional - If True, will raise an error if any values if `data` or `weights` are not finite. - Default is False, which skips the check. - penalized_system : pybaselines._banded_utils.PenalizedSystem, optional - If None (default), will create a new PenalizedSystem object for solving the equation. - If not None, will use the object's `reset_diagonals` method and then solve. - - Returns - ------- - smooth_y : numpy.ndarray, shape (N,) - The smoothed data. - - References - ---------- - Eilers, P. A Perfect Smoother. Analytical Chemistry, 2003, 75(14), 3631-3636. - - """ - y = _check_array(data, check_finite=check_finite, ensure_1d=True) - len_y = len(y) - if penalized_system is not None: - penalized_system.reset_diagonals(lam=lam, diff_order=diff_order) - else: - penalized_system = PenalizedSystem(len_y, lam=lam, diff_order=diff_order) - weight_array = _check_optional_array(len_y, weights, check_finite=check_finite) - - penalized_system.penalty[penalized_system.main_diagonal_index] = ( - penalized_system.penalty[penalized_system.main_diagonal_index] + weight_array - ) - smooth_y = penalized_system.solve( - penalized_system.penalty, weight_array * y, overwrite_ab=True, overwrite_b=True - ) - - return smooth_y diff --git a/GSASII/pybaselines/whittaker.py b/GSASII/pybaselines/whittaker.py deleted file mode 100644 index 60bba6d73..000000000 --- a/GSASII/pybaselines/whittaker.py +++ /dev/null @@ -1,1424 +0,0 @@ -# -*- coding: utf-8 -*- -"""Whittaker-smoothing-based techniques for fitting baselines to experimental data. - -Created on Sept. 13, 2019 -@author: Donald Erb - -""" - -import warnings - -import numpy as np - -from . import _weighting -from ._algorithm_setup import _Algorithm, _class_wrapper -from ._banded_utils import _shift_rows, diff_penalty_diagonals -from ._validation import _check_lam, _check_optional_array -from .utils import ( - ParameterWarning, _mollifier_kernel, pad_edges, padded_convolve, relative_difference -) - - -class _Whittaker(_Algorithm): - """A base class for all Whittaker-smoothing-based algorithms.""" - - @_Algorithm._register(sort_keys=('weights',)) - def asls(self, data, lam=1e6, p=1e-2, diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - Fits the baseline using asymmetric least squares (AsLS) fitting. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - References - ---------- - Eilers, P. A Perfect Smoother. Analytical Chemistry, 2003, 75(14), 3631-3636. - - Eilers, P., et al. Baseline correction with asymmetric least squares smoothing. - Leiden University Medical Centre Report, 2005, 1(1). - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - y, weight_array = self._setup_whittaker(data, lam, diff_order, weights) - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_array - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_array * y, overwrite_b=True - ) - new_weights = _weighting._asls(y, baseline, p) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def iasls(self, data, lam=1e6, p=1e-2, lam_1=1e-4, max_iter=50, tol=1e-3, - weights=None, diff_order=2): - """ - Fits the baseline using the improved asymmetric least squares (IAsLS) algorithm. - - The algorithm consideres both the first and second derivatives of the residual. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with `N` data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - lam_1 : float, optional - The smoothing parameter for the first derivative of the residual. Default is 1e-4. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be set by fitting the data with a second order polynomial. - diff_order : int, optional - The order of the differential matrix. Must be greater than 1. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1 or if `diff_order` is less than 2. - - References - ---------- - He, S., et al. Baseline correction for raman spectra using an improved - asymmetric least squares method, Analytical Methods, 2014, 6(12), 4402-4407. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - elif diff_order < 2: - raise ValueError('diff_order must be 2 or greater') - - if weights is None: - _, _, pseudo_inverse = self._setup_polynomial( - data, weights=None, poly_order=2, calc_vander=True, calc_pinv=True - ) - baseline = self.vandermonde @ (pseudo_inverse @ data) - weights = _weighting._asls(data, baseline, p) - - y, weight_array = self._setup_whittaker(data, lam, diff_order, weights) - lambda_1 = _check_lam(lam_1) - diff_1_diags = diff_penalty_diagonals(self._len, 1, self.whittaker_system.lower, 1) - if self.whittaker_system.using_pentapy: - diff_1_diags = diff_1_diags[::-1] - self.whittaker_system.add_penalty(lambda_1 * diff_1_diags) - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - - # fast calculation of lam_1 * (D_1.T @ D_1) @ y - d1_y = y.copy() - d1_y[0] = y[0] - y[1] - d1_y[-1] = y[-1] - y[-2] - d1_y[1:-1] = 2 * y[1:-1] - y[:-2] - y[2:] - d1_y = lambda_1 * d1_y - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - weight_squared = weight_array * weight_array - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_squared - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_squared * y + d1_y, overwrite_b=True - ) - new_weights = _weighting._asls(y, baseline, p) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def airpls(self, data, lam=1e6, diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - Adaptive iteratively reweighted penalized least squares (airPLS) baseline. - - Parameters - ---------- - data : array-like - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Zhang, Z.M., et al. Baseline correction using adaptive iteratively - reweighted penalized least squares. Analyst, 2010, 135(5), 1138-1146. - - """ - y, weight_array = self._setup_whittaker( - data, lam, diff_order, weights, copy_weights=True - ) - y_l1_norm = np.abs(y).sum() - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - tol_history = np.empty(max_iter + 1) - # Have to have extensive error handling since the weights can all become - # very small due to the exp(i) term if too many iterations are performed; - # checking the negative residual length usually prevents any errors, but - # sometimes not so have to also catch any errors from the solvers - for i in range(1, max_iter + 2): - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_array - try: - output = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_array * y, overwrite_b=True, - check_output=True - ) - except np.linalg.LinAlgError: - warnings.warn( - ('error occurred during fitting, indicating that "tol"' - ' is too low, "max_iter" is too high, or "lam" is too high'), - ParameterWarning - ) - i -= 1 # reduce i so that output tol_history indexing is correct - break - else: - baseline = output - residual = y - baseline - neg_mask = residual < 0 - neg_residual = residual[neg_mask] - if len(neg_residual) < 2: - # exit if there are < 2 negative residuals since all points or all but one - # point would get a weight of 0, which fails the solver - warnings.warn( - ('almost all baseline points are below the data, indicating that "tol"' - ' is too low and/or "max_iter" is too high'), ParameterWarning - ) - i -= 1 # reduce i so that output tol_history indexing is correct - break - - residual_l1_norm = abs(neg_residual.sum()) - calc_difference = residual_l1_norm / y_l1_norm - tol_history[i - 1] = calc_difference - if calc_difference < tol: - break - # only use negative residual in exp to avoid exponential overflow warnings - # and accidently creating a weight of nan (inf * 0 = nan) - weight_array[neg_mask] = np.exp(i * neg_residual / residual_l1_norm) - weight_array[~neg_mask] = 0 - - params = {'weights': weight_array, 'tol_history': tol_history[:i]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def arpls(self, data, lam=1e5, diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - Asymmetrically reweighted penalized least squares smoothing (arPLS). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Baek, S.J., et al. Baseline correction using asymmetrically reweighted - penalized least squares smoothing. Analyst, 2015, 140, 250-257. - - """ - y, weight_array = self._setup_whittaker(data, lam, diff_order, weights) - tol_history = np.empty(max_iter + 1) - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_array - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_array * y, overwrite_b=True - ) - new_weights = _weighting._arpls(y, baseline) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def drpls(self, data, lam=1e5, eta=0.5, max_iter=50, tol=1e-3, weights=None, diff_order=2): - """ - Doubly reweighted penalized least squares (drPLS) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - eta : float - A term for controlling the value of lam; should be between 0 and 1. - Low values will produce smoother baselines, while higher values will - more aggressively fit peaks. Default is 0.5. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - diff_order : int, optional - The order of the differential matrix. Must be greater than 1. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `eta` is not between 0 and 1 or if `diff_order` is less than 2. - - References - ---------- - Xu, D. et al. Baseline correction method based on doubly reweighted - penalized least squares, Applied Optics, 2019, 58, 3913-3920. - - """ - if not 0 <= eta <= 1: - raise ValueError('eta must be between 0 and 1') - elif diff_order < 2: - raise ValueError('diff_order must be 2 or greater') - - y, weight_array = self._setup_whittaker( - data, lam, diff_order, weights, allow_lower=False, reverse_diags=False - ) - # W + P_1 + (I - eta * W) @ P_n -> P_1 + P_n + W @ (I - eta * P_n) - diff_n_diagonals = -eta * self.whittaker_system.penalty[::-1] - diff_n_diagonals[self.whittaker_system.main_diagonal_index] += 1 - - diff_1_diagonals = diff_penalty_diagonals(self._len, 1, False, padding=diff_order - 1) - self.whittaker_system.add_penalty(diff_1_diagonals) - if self.whittaker_system.using_pentapy: - self.whittaker_system.reverse_penalty() - - tol_history = np.empty(max_iter + 1) - lower_upper_bands = (diff_order, diff_order) - for i in range(1, max_iter + 2): - penalty_with_weights = diff_n_diagonals * weight_array - if not self.whittaker_system.using_pentapy: - penalty_with_weights = _shift_rows(penalty_with_weights, diff_order, diff_order) - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty + penalty_with_weights, weight_array * y, - overwrite_ab=True, overwrite_b=True, l_and_u=lower_upper_bands - ) - new_weights = _weighting._drpls(y, baseline, i) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i - 1] = calc_difference - if not np.isfinite(calc_difference): - # catches nan, inf and -inf due to exp(i) being too high or if there - # are too few negative residuals; no way to catch both conditions before - # new_weights calculation since it is hard to estimate if - # (exp(i) / std) * residual will overflow; check calc_difference rather - # than checking new_weights since non-finite values rarely occur and - # checking a scalar is faster; cannot use np.errstate since it is not 100% reliable - warnings.warn( - ('nan and/or +/- inf occurred in weighting calculation, likely meaning ' - '"tol" is too low and/or "max_iter" is too high'), ParameterWarning - ) - break - elif calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def iarpls(self, data, lam=1e5, diff_order=2, max_iter=50, tol=1e-3, weights=None): - """ - Improved asymmetrically reweighted penalized least squares smoothing (IarPLS). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Ye, J., et al. Baseline correction method based on improved asymmetrically - reweighted penalized least squares for Raman spectrum. Applied Optics, 2020, - 59, 10933-10943. - - """ - y, weight_array = self._setup_whittaker(data, lam, diff_order, weights) - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - tol_history = np.empty(max_iter + 1) - for i in range(1, max_iter + 2): - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_array - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_array * y, overwrite_b=True - ) - new_weights = _weighting._iarpls(y, baseline, i) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i - 1] = calc_difference - if not np.isfinite(calc_difference): - # catches nan, inf and -inf due to exp(i) being too high or if there - # are too few negative residuals; no way to catch both conditions before - # new_weights calculation since it is hard to estimate if - # (exp(i) / std) * residual will overflow; check calc_difference rather - # than checking new_weights since non-finite values rarely occur and - # checking a scalar is faster; cannot use np.errstate since it is not 100% reliable - warnings.warn( - ('nan and/or +/- inf occurred in weighting calculation, likely meaning ' - '"tol" is too low and/or "max_iter" is too high'), ParameterWarning - ) - break - elif calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights', 'alpha')) - def aspls(self, data, lam=1e5, diff_order=2, max_iter=100, tol=1e-3, - weights=None, alpha=None): - """ - Adaptive smoothness penalized least squares smoothing (asPLS). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - alpha : array-like, shape (N,), optional - An array of values that control the local value of `lam` to better - fit peak and non-peak regions. If None (default), then the initial values - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'alpha': numpy.ndarray, shape (N,) - The array of alpha values used for fitting the data in the final iteration. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Notes - ----- - The weighting uses an asymmetric coefficient (`k` in the asPLS paper) of 0.5 instead - of the 2 listed in the asPLS paper. pybaselines uses the factor of 0.5 since it - matches the results in Table 2 and Figure 5 of the asPLS paper closer than the - factor of 2 and fits noisy data much better. - - References - ---------- - Zhang, F., et al. Baseline correction for infrared spectra using - adaptive smoothness parameter penalized least squares method. - Spectroscopy Letters, 2020, 53(3), 222-233. - - """ - y, weight_array = self._setup_whittaker( - data, lam, diff_order, weights, allow_lower=False, reverse_diags=True - ) - alpha_array = _check_optional_array( - self._len, alpha, check_finite=self._check_finite, name='alpha' - ) - if self._sort_order is not None and alpha is not None: - alpha_array = alpha_array[self._sort_order] - - main_diag_idx = self.whittaker_system.main_diagonal_index - lower_upper_bands = (diff_order, diff_order) - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - lhs = self.whittaker_system.penalty * alpha_array - lhs[main_diag_idx] = lhs[main_diag_idx] + weight_array - if not self.whittaker_system.using_pentapy: - lhs = _shift_rows(lhs, diff_order, diff_order) - baseline = self.whittaker_system.solve( - lhs, weight_array * y, overwrite_ab=True, overwrite_b=True, - l_and_u=lower_upper_bands - ) - new_weights, residual = _weighting._aspls(y, baseline) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - abs_d = np.abs(residual) - alpha_array = abs_d / abs_d.max() - - params = { - 'weights': weight_array, 'alpha': alpha_array, 'tol_history': tol_history[:i + 1] - } - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def psalsa(self, data, lam=1e5, p=0.5, k=None, diff_order=2, max_iter=50, tol=1e-3, - weights=None): - """ - Peaked Signal's Asymmetric Least Squares Algorithm (psalsa). - - Similar to the asymmetric least squares (AsLS) algorithm, but applies an - exponential decay weighting to values greater than the baseline to allow - using a higher `p` value to better fit noisy data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 0.5. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - Notes - ----- - The exit criteria for the original algorithm was to check whether the signs - of the residuals do not change between two iterations, but the comparison of - the l2 norms of the weight arrays between iterations is used instead to be - more comparable to other Whittaker-smoothing-based algorithms. - - References - ---------- - Oller-Moreno, S., et al. Adaptive Asymmetric Least Squares baseline estimation - for analytical instruments. 2014 IEEE 11th International Multi-Conference on - Systems, Signals, and Devices, 2014, 1-5. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - y, weight_array = self._setup_whittaker(data, lam, diff_order, weights) - if k is None: - k = np.std(y) / 10 - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_array - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_array * y, overwrite_b=True - ) - new_weights = _weighting._psalsa(y, baseline, p, k, self._len) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - @_Algorithm._register(sort_keys=('weights',)) - def derpsalsa(self, data, lam=1e6, p=0.01, k=None, diff_order=2, max_iter=50, tol=1e-3, - weights=None, smooth_half_window=None, num_smooths=16, **pad_kwargs): - """ - Derivative Peak-Screening Asymmetric Least Squares Algorithm (derpsalsa). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - smooth_half_window : int, optional - The half-window to use for smoothing the data before computing the first - and second derivatives. Default is None, which will use ``len(data) / 200``. - num_smooths : int, optional - The number of times to smooth the data before computing the first - and second derivatives. Default is 16. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - References - ---------- - Korepanov, V. Asymmetric least-squares baseline algorithm with peak screening for - automatic processing of the Raman spectra. Journal of Raman Spectroscopy. 2020, - 51(10), 2061-2065. - - """ - if not 0 < p < 1: - raise ValueError('p must be between 0 and 1') - y, weight_array = self._setup_whittaker(data, lam, diff_order, weights) - if k is None: - k = np.std(y) / 10 - if smooth_half_window is None: - smooth_half_window = self._len // 200 - # could pad the data every iteration, but it is ~2-3 times slower and only affects - # the edges, so it's not worth it - y_smooth = pad_edges(y, smooth_half_window, **pad_kwargs) - if smooth_half_window > 0: - smooth_kernel = _mollifier_kernel(smooth_half_window) - for _ in range(num_smooths): - y_smooth = padded_convolve(y_smooth, smooth_kernel) - y_smooth = y_smooth[smooth_half_window:self._len + smooth_half_window] - - diff_y_1 = np.gradient(y_smooth) - diff_y_2 = np.gradient(diff_y_1) - # x.dot(x) is same as (x**2).sum() but faster - rms_diff_1 = np.sqrt(diff_y_1.dot(diff_y_1) / self._len) - rms_diff_2 = np.sqrt(diff_y_2.dot(diff_y_2) / self._len) - - diff_1_weights = np.exp(-((diff_y_1 / rms_diff_1)**2) / 2) - diff_2_weights = np.exp(-((diff_y_2 / rms_diff_2)**2) / 2) - partial_weights = diff_1_weights * diff_2_weights - - main_diag_idx = self.whittaker_system.main_diagonal_index - main_diagonal = self.whittaker_system.penalty[main_diag_idx].copy() - tol_history = np.empty(max_iter + 1) - for i in range(max_iter + 1): - self.whittaker_system.penalty[main_diag_idx] = main_diagonal + weight_array - baseline = self.whittaker_system.solve( - self.whittaker_system.penalty, weight_array * y, overwrite_b=True - ) - new_weights = _weighting._derpsalsa(y, baseline, p, k, self._len, partial_weights) - calc_difference = relative_difference(weight_array, new_weights) - tol_history[i] = calc_difference - if calc_difference < tol: - break - weight_array = new_weights - - params = {'weights': weight_array, 'tol_history': tol_history[:i + 1]} - - return baseline, params - - -_whittaker_wrapper = _class_wrapper(_Whittaker) - - -@_whittaker_wrapper -def asls(data, lam=1e6, p=1e-2, diff_order=2, max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - Fits the baseline using asymmetric least squares (AsLS) fitting. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - References - ---------- - Eilers, P. A Perfect Smoother. Analytical Chemistry, 2003, 75(14), 3631-3636. - - Eilers, P., et al. Baseline correction with asymmetric least squares smoothing. - Leiden University Medical Centre Report, 2005, 1(1). - - """ - - -@_whittaker_wrapper -def iasls(data, x_data=None, lam=1e6, p=1e-2, lam_1=1e-4, max_iter=50, tol=1e-3, - weights=None, diff_order=2): - """ - Fits the baseline using the improved asymmetric least squares (IAsLS) algorithm. - - The algorithm consideres both the first and second derivatives of the residual. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with `N` data points. Must not - contain missing data (NaN) or Inf. - x_data : array-like, shape (N,), optional - The x-values of the measured data. Default is None, which will create an - array from -1 to 1 with N points. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - lam_1 : float, optional - The smoothing parameter for the first derivative of the residual. Default is 1e-4. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be set by fitting the data with a second order polynomial. - diff_order : int, optional - The order of the differential matrix. Must be greater than 1. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1 or if `diff_order` is less than 2. - - References - ---------- - He, S., et al. Baseline correction for raman spectra using an improved - asymmetric least squares method, Analytical Methods, 2014, 6(12), 4402-4407. - - """ - - -@_whittaker_wrapper -def airpls(data, lam=1e6, diff_order=2, max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - Adaptive iteratively reweighted penalized least squares (airPLS) baseline. - - Parameters - ---------- - data : array-like - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Zhang, Z.M., et al. Baseline correction using adaptive iteratively - reweighted penalized least squares. Analyst, 2010, 135(5), 1138-1146. - - """ - - -@_whittaker_wrapper -def arpls(data, lam=1e5, diff_order=2, max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - Asymmetrically reweighted penalized least squares smoothing (arPLS). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Baek, S.J., et al. Baseline correction using asymmetrically reweighted - penalized least squares smoothing. Analyst, 2015, 140, 250-257. - - """ - - -@_whittaker_wrapper -def drpls(data, lam=1e5, eta=0.5, max_iter=50, tol=1e-3, weights=None, diff_order=2, x_data=None): - """ - Doubly reweighted penalized least squares (drPLS) baseline. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - eta : float - A term for controlling the value of lam; should be between 0 and 1. - Low values will produce smoother baselines, while higher values will - more aggressively fit peaks. Default is 0.5. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - diff_order : int, optional - The order of the differential matrix. Must be greater than 1. Default is 2 - (second order differential matrix). Typical values are 2 or 3. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `eta` is not between 0 and 1 or if `diff_order` is less than 2. - - References - ---------- - Xu, D. et al. Baseline correction method based on doubly reweighted - penalized least squares, Applied Optics, 2019, 58, 3913-3920. - - """ - - -@_whittaker_wrapper -def iarpls(data, lam=1e5, diff_order=2, max_iter=50, tol=1e-3, weights=None, x_data=None): - """ - Improved asymmetrically reweighted penalized least squares smoothing (IarPLS). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - References - ---------- - Ye, J., et al. Baseline correction method based on improved asymmetrically - reweighted penalized least squares for Raman spectrum. Applied Optics, 2020, - 59, 10933-10943. - - """ - - -@_whittaker_wrapper -def aspls(data, lam=1e5, diff_order=2, max_iter=100, tol=1e-3, weights=None, - alpha=None, x_data=None): - """ - Adaptive smoothness penalized least squares smoothing (asPLS). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e5. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - alpha : array-like, shape (N,), optional - An array of values that control the local value of `lam` to better - fit peak and non-peak regions. If None (default), then the initial values - will be an array with size equal to N and all values set to 1. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'alpha': numpy.ndarray, shape (N,) - The array of alpha values used for fitting the data in the final iteration. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `alpha` and `data` do not have the same shape. - - Notes - ----- - The weighting uses an asymmetric coefficient (`k` in the asPLS paper) of 0.5 instead - of the 2 listed in the asPLS paper. pybaselines uses the factor of 0.5 since it - matches the results in Table 2 and Figure 5 of the asPLS paper closer than the - factor of 2 and fits noisy data much better. - - References - ---------- - Zhang, F., et al. Baseline correction for infrared spectra using - adaptive smoothness parameter penalized least squares method. - Spectroscopy Letters, 2020, 53(3), 222-233. - - """ - - -@_whittaker_wrapper -def psalsa(data, lam=1e5, p=0.5, k=None, diff_order=2, max_iter=50, tol=1e-3, - weights=None, x_data=None): - """ - Peaked Signal's Asymmetric Least Squares Algorithm (psalsa). - - Similar to the asymmetric least squares (AsLS) algorithm, but applies an - exponential decay weighting to values greater than the baseline to allow - using a higher `p` value to better fit noisy data. - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 0.5. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - Notes - ----- - The exit criteria for the original algorithm was to check whether the signs - of the residuals do not change between two iterations, but the comparison of - the l2 norms of the weight arrays between iterations is used instead to be - more comparable to other Whittaker-smoothing-based algorithms. - - References - ---------- - Oller-Moreno, S., et al. Adaptive Asymmetric Least Squares baseline estimation - for analytical instruments. 2014 IEEE 11th International Multi-Conference on - Systems, Signals, and Devices, 2014, 1-5. - - """ - - -@_whittaker_wrapper -def derpsalsa(data, lam=1e6, p=0.01, k=None, diff_order=2, max_iter=50, tol=1e-3, weights=None, - smooth_half_window=None, num_smooths=16, x_data=None, **pad_kwargs): - """ - Derivative Peak-Screening Asymmetric Least Squares Algorithm (derpsalsa). - - Parameters - ---------- - data : array-like, shape (N,) - The y-values of the measured data, with N data points. Must not - contain missing data (NaN) or Inf. - lam : float, optional - The smoothing parameter. Larger values will create smoother baselines. - Default is 1e6. - p : float, optional - The penalizing weighting factor. Must be between 0 and 1. Values greater - than the baseline will be given `p` weight, and values less than the baseline - will be given `p - 1` weight. Default is 1e-2. - k : float, optional - A factor that controls the exponential decay of the weights for baseline - values greater than the data. Should be approximately the height at which - a value could be considered a peak. Default is None, which sets `k` to - one-tenth of the standard deviation of the input data. A large k value - will produce similar results to :meth:`.asls`. - diff_order : int, optional - The order of the differential matrix. Must be greater than 0. Default is 2 - (second order differential matrix). Typical values are 2 or 1. - max_iter : int, optional - The max number of fit iterations. Default is 50. - tol : float, optional - The exit criteria. Default is 1e-3. - weights : array-like, shape (N,), optional - The weighting array. If None (default), then the initial weights - will be an array with size equal to N and all values set to 1. - smooth_half_window : int, optional - The half-window to use for smoothing the data before computing the first - and second derivatives. Default is None, which will use ``len(data) / 200``. - num_smooths : int, optional - The number of times to smooth the data before computing the first - and second derivatives. Default is 16. - x_data : array-like, optional - The x-values. Not used by this function, but input is allowed for consistency - with other functions. - **pad_kwargs - Additional keyword arguments to pass to :func:`.pad_edges` for padding - the edges of the data to prevent edge effects from smoothing. - - Returns - ------- - baseline : numpy.ndarray, shape (N,) - The calculated baseline. - params : dict - A dictionary with the following items: - - * 'weights': numpy.ndarray, shape (N,) - The weight array used for fitting the data. - * 'tol_history': numpy.ndarray - An array containing the calculated tolerance values for - each iteration. The length of the array is the number of iterations - completed. If the last value in the array is greater than the input - `tol` value, then the function did not converge. - - Raises - ------ - ValueError - Raised if `p` is not between 0 and 1. - - References - ---------- - Korepanov, V. Asymmetric least-squares baseline algorithm with peak screening for - automatic processing of the Raman spectra. Journal of Raman Spectroscopy. 2020, - 51(10), 2061-2065. - - """ diff --git a/GSASII/scanCCD.py b/GSASII/scanCCD.py index 14e0cdac9..8d3d595b9 100644 --- a/GSASII/scanCCD.py +++ b/GSASII/scanCCD.py @@ -11,11 +11,11 @@ import numpy.ma as ma import wx import matplotlib as mpl -import GSASIIpath -import GSASIImiscGUI as G2IO -import GSASIIimage as G2img -import GSASIIplot as G2plt -import GSASIIfiles as G2fil +from . import GSASIIpath +from . import GSASIImiscGUI as G2IO +from . import GSASIIimage as G2img +from . import GSASIIplot as G2plt +from . import GSASIIfiles as G2fil npsind = lambda x: np.sin(x*np.pi/180.) npcosd = lambda x: np.cos(x*np.pi/180.) diff --git a/GSASII/testDeriv.py b/GSASII/testDeriv.py index 35b892051..32dcc30f2 100644 --- a/GSASII/testDeriv.py +++ b/GSASII/testDeriv.py @@ -17,18 +17,25 @@ import sys import os import copy -import pickle as cPickle +import pickle import io as StringIO import cProfile,pstats import wx import numpy as np -import GSASIIpath +# path hack for restart, when needed +import importlib.util +try: + importlib.util.find_spec('GSASII.GSASIIGUI') +except ModuleNotFoundError: + print('GSAS-II not installed in Python; Hacking sys.path') + sys.path.insert(0,os.path.dirname(os.path.dirname(__file__))) +from GSASII import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIIstrMath as G2stMth -import GSASIItestplot as plot -import GSASIImapvars as G2mv +from GSASII import GSASIIstrMath as G2stMth +from GSASII import GSASIItestplot as plot +from GSASII import GSASIImapvars as G2mv try: # fails on doc build - import pytexture as ptx + from . import pytexture as ptx ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics except ImportError: pass @@ -121,13 +128,13 @@ def OnTestRead(self,event): def TestRead(self): file = open(self.testFile,'rb') - self.values = cPickle.load(file,encoding='Latin-1') - self.HistoPhases = cPickle.load(file,encoding='Latin-1') - (self.constrDict,self.fixedList,self.depVarList) = cPickle.load(file,encoding='Latin-1') - self.parmDict = cPickle.load(file,encoding='Latin-1') - self.varylist = cPickle.load(file,encoding='Latin-1') - self.calcControls = cPickle.load(file,encoding='Latin-1') - self.pawleyLookup = cPickle.load(file,encoding='Latin-1') + self.values = pickle.load(file,encoding='Latin-1') + self.HistoPhases = pickle.load(file,encoding='Latin-1') + (self.constrDict,self.fixedList,self.depVarList) = pickle.load(file,encoding='Latin-1') + self.parmDict = pickle.load(file,encoding='Latin-1') + self.varylist = pickle.load(file,encoding='Latin-1') + self.calcControls = pickle.load(file,encoding='Latin-1') + self.pawleyLookup = pickle.load(file,encoding='Latin-1') self.names = self.varylist+self.depVarList self.use = [False for i in range(len(self.names))] self.delt = [max(abs(self.parmDict[name])*0.0001,1e-6) for name in self.names] diff --git a/GSASII/testMagSym.py b/GSASII/testMagSym.py index 6a57a93e7..27d6c3fe9 100644 --- a/GSASII/testMagSym.py +++ b/GSASII/testMagSym.py @@ -3,11 +3,11 @@ import wx import numpy as np import copy -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIIspc as G2spc -import GSASIIctrlGUI as G2G -import GSASIIphsGUI as G2phsGUI +from . import GSASIIspc as G2spc +from . import GSASIIctrlGUI as G2G +from . import GSASIIphsGUI as G2phsGUI try: wx.NewId diff --git a/GSASII/testSSymbols.py b/GSASII/testSSymbols.py index 20d259863..919c90c3b 100644 --- a/GSASII/testSSymbols.py +++ b/GSASII/testSSymbols.py @@ -1,10 +1,10 @@ #test import sys import wx -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIIspc as G2spc -import GSASIIctrlGUI as G2G +from . import GSASIIspc as G2spc +from . import GSASIIctrlGUI as G2G try: wx.NewIdRef diff --git a/GSASII/testSytSym.py b/GSASII/testSytSym.py index 787710aff..b00785e85 100644 --- a/GSASII/testSytSym.py +++ b/GSASII/testSytSym.py @@ -2,12 +2,12 @@ import sys import wx import numpy as np -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIIspc as G2spc -import GSASIIlattice as G2lat -import GSASIIctrlGUI as G2G -import GSASIIphsGUI as G2phsGUI +from . import GSASIIspc as G2spc +from . import GSASIIlattice as G2lat +from . import GSASIIctrlGUI as G2G +from . import GSASIIphsGUI as G2phsGUI try: wx.NewId diff --git a/GSASII/testXNFF.py b/GSASII/testXNFF.py index 5cb1c5676..ff7a1314a 100644 --- a/GSASII/testXNFF.py +++ b/GSASII/testXNFF.py @@ -11,12 +11,12 @@ import sys import wx import numpy as np -import GSASIIpath +from . import GSASIIpath GSASIIpath.SetBinaryPath() -import GSASIItestplot as plot -import GSASIIElem as G2el -import GSASIIElemGUI as G2elG -import atmdata +from . import GSASIItestplot as plot +from . import GSASIIElem as G2el +from . import GSASIIElemGUI as G2elG +from . import atmdata WACV = wx.ALIGN_CENTER_VERTICAL try: diff --git a/LICENSE b/LICENSE index ec11c705d..cd1c0367d 100644 --- a/LICENSE +++ b/LICENSE @@ -32,10 +32,6 @@ that the following conditions are met: Also note that GSAS-II is normally distributed with the following included software: -CifFile (PyCifRW): Distributed with direct permission of J. Hester, see - https://github.com/jamesrhester/pycifrw/blob/development/LICENSE for - license. - pybaselines: for license see GSASII/pybaselines/LICENSE.txt and GSASII/pybaselines/LICENSES_bundled.txt. diff --git a/README.md b/README.md index bbb1e91ed..3bd95af06 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,11 @@ # GSAS-II - - @@ -39,22 +36,20 @@ GSAS-II is free open source software. ## URLs * The - [home page for GSAS-II](https://advancedphotonsource.github.io/GSAS-II-tutorials) - has recently been moved to GitHub, but some content may still be - only at - [the old location](https://subversion.xray.aps.anl.gov/trac/pyGSAS). - Note the new [installation instructions](https://advancedphotonsource.github.io/GSAS-II-tutorials/install.html). + [home page for GSAS-II](https://advancedphotonsource.github.io/GSAS-II-tutorials) has + [installation instructions](https://advancedphotonsource.github.io/GSAS-II-tutorials/install.html) and a link + to [the downloads location](https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/latest). + (There might be some content only at + [the home page](https://subversion.xray.aps.anl.gov/trac/pyGSAS), but I hope not.) -* This repo is the main repository for the GSAS-II source code, but - note that there is a separate repo for the - [GSAS-II installation tools](https://github.com/AdvancedPhotonSource/GSAS-II-buildtools) - and another for - [GSAS-II web content](https://github.com/AdvancedPhotonSource/GSAS-II-tutorials) (including tutorials). The +* [This repo](https://github.com/AdvancedPhotonSource/GSAS-II) is the main repository for the GSAS-II source code (replacing the [old subversion site](https://subversion.xray.aps.anl.gov/pyGSAS) - and its - [associated web site](https://subversion.xray.aps.anl.gov/trac/pyGSAS/browser) - will be maintained for the immediate future. -* Code documentation: https://gsas-ii.readthedocs.io. + and the [trac web site](https://subversion.xray.aps.anl.gov/trac/pyGSAS/browser)), but there are two additional repo's associated with GSAS-II + * [GSAS-II installation tools](https://github.com/AdvancedPhotonSource/GSAS-II-buildtools) that includes [downloads](https://github.com/AdvancedPhotonSource/GSAS-II-buildtools/releases/latest) as well as [automated build/test scripts](https://github.com/AdvancedPhotonSource/GSAS-II/actions) + and + * [GSAS-II web content](https://github.com/AdvancedPhotonSource/GSAS-II-tutorials) that includes [tutorials](https://advancedphotonsource.github.io/GSAS-II-tutorials/tutorials.html). + +* Code documentation: https://gsas-ii.readthedocs.io and scripting documentation: https://gsas-ii-scripting.readthedocs.io (subset of full documentation) ## Please Cite If you use GSAS-II in any part of your project, please cite it in your @@ -113,7 +108,7 @@ single refinement with subsequent parametric fitting. GSAS-II is freely distributed as open source software; see the license file for more details. GSAS-II runs on Windows, -MacOS, Linux and Raspberry Pi computers. It currently receives >600 +MacOS, Linux and Raspberry Pi computers. It currently receives >950 citations/year.