TB2J 0.9.12.18__py3-none-any.whl → 0.9.12.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. TB2J/MAE.py +8 -1
  2. TB2J/MAEGreen.py +0 -2
  3. TB2J/exchange.py +11 -4
  4. TB2J/exchangeCL2.py +2 -0
  5. TB2J/exchange_params.py +24 -0
  6. TB2J/green.py +15 -3
  7. TB2J/interfaces/abacus/gen_exchange_abacus.py +2 -1
  8. TB2J/io_exchange/__init__.py +19 -1
  9. TB2J/io_exchange/edit.py +594 -0
  10. TB2J/io_exchange/io_exchange.py +238 -74
  11. TB2J/io_exchange/io_tomsasd.py +4 -3
  12. TB2J/io_exchange/io_txt.py +72 -7
  13. TB2J/io_exchange/io_vampire.py +1 -1
  14. TB2J/io_merge.py +60 -40
  15. TB2J/magnon/magnon3.py +27 -2
  16. TB2J/mathutils/rotate_spin.py +7 -7
  17. TB2J/mycfr.py +11 -11
  18. TB2J/plot.py +26 -0
  19. TB2J/scripts/TB2J_edit.py +403 -0
  20. TB2J/scripts/TB2J_plot_exchange.py +48 -0
  21. TB2J/spinham/hamiltonian.py +156 -13
  22. TB2J/spinham/hamiltonian_terms.py +40 -1
  23. TB2J/spinham/spin_xml.py +40 -8
  24. TB2J/symmetrize_J.py +138 -7
  25. TB2J/tests/test_cli_remove_sublattice.py +33 -0
  26. TB2J/tests/test_cli_toggle_exchange.py +50 -0
  27. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/METADATA +7 -3
  28. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/RECORD +32 -35
  29. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/WHEEL +1 -1
  30. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/entry_points.txt +2 -0
  31. TB2J/.gitignore +0 -5
  32. TB2J/agent_files/debug_spinphon_fd/debug_main.py +0 -156
  33. TB2J/agent_files/debug_spinphon_fd/test_compute_dJdx.py +0 -272
  34. TB2J/agent_files/debug_spinphon_fd/test_ispin0_only.py +0 -120
  35. TB2J/agent_files/debug_spinphon_fd/test_no_d2j.py +0 -31
  36. TB2J/agent_files/debug_spinphon_fd/test_with_d2j.py +0 -28
  37. TB2J/interfaces/abacus/test_read_HRSR.py +0 -43
  38. TB2J/interfaces/abacus/test_read_stru.py +0 -32
  39. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/licenses/LICENSE +0 -0
  40. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/top_level.txt +0 -0
@@ -55,6 +55,7 @@ class SpinIO(object):
55
55
  write_experimental=True,
56
56
  description=None,
57
57
  standardize_Jani=False,
58
+ sia_tensor=None,
58
59
  ):
59
60
  """
60
61
  :param atoms: Ase atoms structure.
@@ -80,6 +81,7 @@ class SpinIO(object):
80
81
  :param gyro_ratio: gyromagnetic ratio
81
82
  :param write_experimental: write_experimental data to output files
82
83
  :param description: add some description into the xml file.
84
+ :param sia_tensor: single ion anisotropy tensor, dictionary {i: 3x3 tensor}
83
85
  """
84
86
  self.atoms = atoms #: atomic structures, ase.Atoms object
85
87
  self.index_spin = index_spin
@@ -103,12 +105,10 @@ class SpinIO(object):
103
105
  self._ind_atoms[ispin] = iatom
104
106
 
105
107
  if exchange_Jdict is not None:
106
- self.has_exchange = True #: whether there is isotropic exchange
107
108
  #: The dictionary of :math:`J_{ij}(R)`, the keys are (i,j, R),
108
109
  # where R is a tuple, and the value is the isotropic exchange
109
110
  self.exchange_Jdict = exchange_Jdict
110
111
  else:
111
- self.has_exchange = False
112
112
  self.exchange_Jdict = None
113
113
 
114
114
  self.Jiso_orb = Jiso_orb
@@ -120,21 +120,37 @@ class SpinIO(object):
120
120
  self.dJdx2 = dJdx2
121
121
 
122
122
  if dmi_ddict is not None:
123
- self.has_dmi = True #: Whether there is DMI.
124
123
  #: The dictionary of DMI. the key is the same as exchange_Jdict, the values are 3-d vectors (Dx, Dy, Dz).
125
124
  self.dmi_ddict = dmi_ddict
126
125
  else:
127
- self.has_dmi = False
128
126
  self.dmi_ddict = None
129
127
 
130
128
  if Jani_dict is not None:
131
- self.has_bilinear = True #: Whether there is anisotropic exchange term
132
129
  #: The dictionary of anisotropic exchange. The vlaues are matrices of shape (3,3).
133
130
  self.Jani_dict = Jani_dict
134
131
  else:
135
- self.has_bilinear = False
136
132
  self.Jani_dict = None
137
133
 
134
+ if k1 is not None and k1dir is not None:
135
+ # Convert uniaxial anisotropy to SIA tensor
136
+ if sia_tensor is None:
137
+ sia_tensor = {}
138
+ for i, (K, axis) in enumerate(zip(k1, k1dir)):
139
+ if abs(K) > 1e-10:
140
+ e = np.array(axis)
141
+ norm = np.linalg.norm(e)
142
+ if norm > 1e-6:
143
+ e = e / norm
144
+ # A = K * e * e^T
145
+ A = K * np.outer(e, e)
146
+ if i in sia_tensor:
147
+ sia_tensor[i] += A
148
+ else:
149
+ sia_tensor[i] = A
150
+ # Clear k1 and k1dir as they are now integrated into sia_tensor
151
+ k1 = None
152
+ k1dir = None
153
+
138
154
  if k1 is not None and k1dir is not None:
139
155
  self.has_uniaxial_anistropy = True
140
156
  self.k1 = k1
@@ -144,14 +160,19 @@ class SpinIO(object):
144
160
  self.k1 = None
145
161
  self.k1dir = None
146
162
 
147
- self.has_bilinear = not (Jani_dict == {} or Jani_dict is None)
148
-
149
163
  self.has_biquadratic = not (
150
164
  biquadratic_Jdict == {} or biquadratic_Jdict is None
151
165
  )
152
166
 
153
167
  self.biquadratic_Jdict = biquadratic_Jdict
154
168
 
169
+ if sia_tensor is not None:
170
+ self.has_sia_tensor = True
171
+ self.sia_tensor = sia_tensor
172
+ else:
173
+ self.has_sia_tensor = False
174
+ self.sia_tensor = None
175
+
155
176
  if NJT_ddict is not None:
156
177
  self.has_NJT_dmi = True
157
178
  self.NJT_ddict = NJT_ddict
@@ -189,6 +210,18 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
189
210
  self.orbital_names = orbital_names
190
211
  self.TB2J_version = __version__
191
212
 
213
+ @property
214
+ def has_exchange(self):
215
+ return self.exchange_Jdict is not None
216
+
217
+ @property
218
+ def has_dmi(self):
219
+ return self.dmi_ddict is not None
220
+
221
+ @property
222
+ def has_bilinear(self):
223
+ return self.Jani_dict is not None
224
+
192
225
  def _build_Rlist(self):
193
226
  Rset = set()
194
227
  ispin_set = set()
@@ -228,12 +261,14 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
228
261
  return self.index_spin[symdict[symnum]]
229
262
 
230
263
  def i_spin(self, i):
231
- if isinstance(i, int):
232
- return i
264
+ if isinstance(i, (int, np.integer)):
265
+ return int(i)
233
266
  elif isinstance(i, str):
234
267
  return self.get_symbol_number_ispin(i)
235
268
  else:
236
- raise ValueError("i must be either an integer or a string.")
269
+ raise ValueError(
270
+ f"i must be either an integer or a string. Got {type(i)}: {i}"
271
+ )
237
272
 
238
273
  def get_charge_ispin(self, i):
239
274
  i = self.i_spin(i)
@@ -332,7 +367,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
332
367
  else:
333
368
  return default
334
369
 
335
- def get_J_tensor(self, i, j, R, Jiso=True, Jani=False, DMI=False):
370
+ def get_J_tensor(self, i, j, R, Jiso=True, Jani=True, DMI=True, SIA=True):
336
371
  """
337
372
  Return the full exchange tensor for atom i and j, and cell R.
338
373
  param i : spin index i
@@ -353,6 +388,20 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
353
388
  if Ja is not None:
354
389
  Ja *= 1
355
390
  Jtensor = combine_J_tensor(Jiso=J, D=D, Jani=Ja)
391
+ if np.linalg.norm(R) < 0.001 and i == j:
392
+ print(f"{SIA=} , {i=}")
393
+ print(f"{self.has_sia_tensor=}")
394
+
395
+ if (
396
+ SIA
397
+ and self.has_sia_tensor
398
+ and self.sia_tensor is not None
399
+ and i == j
400
+ and np.linalg.norm(R) < 0.001
401
+ ):
402
+ if i in self.sia_tensor:
403
+ print(f"Adding SIA tensor for {i}, with {self.sia_tensor[i]}")
404
+ Jtensor += self.sia_tensor[i]
356
405
 
357
406
  # if iso_only:
358
407
  # J = self.get_Jiso(i, j, R)
@@ -368,7 +417,9 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
368
417
  # )
369
418
  return Jtensor
370
419
 
371
- def get_full_Jtensor_for_one_R_i3j3(self, R, Jiso=True, Jani=True, DMI=True):
420
+ def get_full_Jtensor_for_one_R_i3j3(
421
+ self, R, Jiso=True, Jani=True, DMI=True, SIA=True
422
+ ):
372
423
  """
373
424
  Return the full exchange tensor of all i and j for cell R.
374
425
  param R (tuple of integers): cell index R
@@ -380,11 +431,13 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
380
431
  for i in range(self.nspin):
381
432
  for j in range(self.nspin):
382
433
  Jmat[i * 3 : i * 3 + 3, j * 3 : j * 3 + 3] = self.get_J_tensor(
383
- i, j, R, Jiso=Jiso, Jani=Jani, DMI=DMI
434
+ i, j, R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
384
435
  )
385
436
  return Jmat
386
437
 
387
- def get_full_Jtensor_for_one_R_ij33(self, R, Jiso=True, Jani=True, DMI=True):
438
+ def get_full_Jtensor_for_one_R_ij33(
439
+ self, R, Jiso=True, Jani=True, DMI=True, SIA=True
440
+ ):
388
441
  """
389
442
  Return the full exchange tensor of all i and j for cell R.
390
443
  param R (tuple of integers): cell index R
@@ -396,20 +449,22 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
396
449
  for i in range(self.nspin):
397
450
  for j in range(self.nspin):
398
451
  Jmat[i, j, :, :] = self.get_J_tensor(
399
- i, j, R, Jiso=Jiso, Jani=Jani, DMI=DMI
452
+ i, j, R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
400
453
  )
401
454
  return Jmat
402
455
 
403
- def get_full_Jtensor_for_one_R_ij(self, R, Jiso=True, Jani=True, DMI=True):
456
+ def get_full_Jtensor_for_one_R_ij(
457
+ self, R, Jiso=True, Jani=True, DMI=True, SIA=True,
458
+ ):
404
459
  """
405
460
  Return the full exchange tensor of all i and j for cell R.
406
461
  param R (tuple of integers): cell index R
407
462
  returns:
408
463
  Jmat: (nspin,nspin,3,3) matrix.
409
464
  """
410
- if Jani or DMI:
465
+ if Jani or DMI or SIA:
411
466
  raise ValueError(
412
- "Jani and DMI are not supported for this method. Use get_full_Jtensor_for_one_R_ij33 instead."
467
+ "Jani, DMI and SIA are not supported for this method. Use get_full_Jtensor_for_one_R_ij33 instead."
413
468
  )
414
469
  n = self.nspin
415
470
  Jmat = np.zeros((n, n), dtype=float)
@@ -421,7 +476,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
421
476
  return Jmat
422
477
 
423
478
  def get_full_Jtensor_for_Rlist(
424
- self, asr=False, Jiso=True, Jani=True, DMI=True, order="i3j3"
479
+ self, asr=False, Jiso=True, Jani=True, DMI=True, SIA=True, order="i3j3"
425
480
  ):
426
481
  n = self.nspin
427
482
  n3 = n * 3
@@ -430,7 +485,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
430
485
  Jmat = np.zeros((nR, n3, n3), dtype=float)
431
486
  for iR, R in enumerate(self.Rlist):
432
487
  Jmat[iR] = self.get_full_Jtensor_for_one_R_i3j3(
433
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
488
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
434
489
  )
435
490
  if asr:
436
491
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -442,8 +497,9 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
442
497
  elif order == "ij33":
443
498
  Jmat = np.zeros((nR, n, n, 3, 3), dtype=float)
444
499
  for iR, R in enumerate(self.Rlist):
500
+ print(f"R={R}")
445
501
  Jmat[iR] = self.get_full_Jtensor_for_one_R_ij33(
446
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
502
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
447
503
  )
448
504
  if asr:
449
505
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -454,8 +510,9 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
454
510
  elif order == "i3j3_2D":
455
511
  Jmat = np.zeros((nR, n3, n3), dtype=float)
456
512
  for iR, R in enumerate(self.Rlist):
513
+ print(f"R={R}")
457
514
  Jmat[iR] = self.get_full_Jtensor_for_one_R_i3j3(
458
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
515
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
459
516
  ).reshape((n3, n3))
460
517
  if asr:
461
518
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -468,7 +525,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
468
525
  Jmat = np.zeros((nR, n, n), dtype=float)
469
526
  for iR, R in enumerate(self.Rlist):
470
527
  Jmat[iR] = self.get_full_Jtensor_for_one_R_ij(
471
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
528
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
472
529
  )
473
530
  if asr:
474
531
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -565,17 +622,41 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
565
622
  marker="o",
566
623
  fname=None,
567
624
  show=False,
625
+ by_species=False,
568
626
  **kwargs,
569
627
  ):
570
628
  if ax is None:
571
629
  fig, ax = plt.subplots()
572
- ds = []
573
- Js = []
574
- for key, val in self.exchange_Jdict.items():
575
- d = self.distance_dict[key][1]
576
- ds.append(d)
577
- Js.append(val * 1e3)
578
- ax.scatter(ds, Js, marker=marker, color=color, **kwargs)
630
+ if by_species:
631
+ groups = {}
632
+ for key, val in self.exchange_Jdict.items():
633
+ R, i, j = key
634
+ idx_i = self.ind_atoms[i]
635
+ idx_j = self.ind_atoms[j]
636
+ spec_i = self.atoms[idx_i].symbol
637
+ spec_j = self.atoms[idx_j].symbol
638
+ pair = tuple(sorted([spec_i, spec_j]))
639
+ pair_name = "-".join(pair)
640
+ if pair_name not in groups:
641
+ groups[pair_name] = {"ds": [], "Js": []}
642
+ d = self.distance_dict[key][1]
643
+ groups[pair_name]["ds"].append(d)
644
+ groups[pair_name]["Js"].append(val * 1e3)
645
+
646
+ for pair_name, data in groups.items():
647
+ ax.scatter(
648
+ data["ds"], data["Js"], marker=marker, label=pair_name, **kwargs
649
+ )
650
+ ax.legend()
651
+ else:
652
+ ds = []
653
+ Js = []
654
+ for key, val in self.exchange_Jdict.items():
655
+ d = self.distance_dict[key][1]
656
+ ds.append(d)
657
+ Js.append(val * 1e3)
658
+ ax.scatter(ds, Js, marker=marker, color=color, **kwargs)
659
+
579
660
  ax.axhline(color="gray")
580
661
  ax.set_xlabel(r"Distance ($\AA$)")
581
662
  ax.set_ylabel("J (meV)")
@@ -585,25 +666,68 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
585
666
  plt.show()
586
667
  return ax
587
668
 
588
- def plot_DvsR(self, ax=None, fname=None, show=False):
669
+ def plot_DvsR(self, ax=None, fname=None, show=False, by_species=False):
589
670
  if ax is None:
590
671
  fig, ax = plt.subplots()
591
- ds = []
592
- Ds = []
593
- for key, val in self.dmi_ddict.items():
594
- d = self.distance_dict[key][1]
595
- ds.append(d)
596
- Ds.append(val * 1e3)
597
- Ds = np.array(Ds)
598
- ax.scatter(ds, Ds[:, 0], marker="s", color="r", label="Dx")
599
- ax.scatter(
600
- ds, Ds[:, 1], marker="o", edgecolors="g", facecolors="none", label="Dy"
601
- )
602
- ax.scatter(
603
- ds, Ds[:, 2], marker="D", edgecolors="b", facecolors="none", label="Dz"
604
- )
672
+ if by_species:
673
+ groups = {}
674
+ for key, val in self.dmi_ddict.items():
675
+ R, i, j = key
676
+ idx_i = self.ind_atoms[i]
677
+ idx_j = self.ind_atoms[j]
678
+ spec_i = self.atoms[idx_i].symbol
679
+ spec_j = self.atoms[idx_j].symbol
680
+ pair = tuple(sorted([spec_i, spec_j]))
681
+ pair_name = "-".join(pair)
682
+ if pair_name not in groups:
683
+ groups[pair_name] = {"ds": [], "Ds": []}
684
+ d = self.distance_dict[key][1]
685
+ groups[pair_name]["ds"].append(d)
686
+ groups[pair_name]["Ds"].append(val * 1e3)
687
+
688
+ markers = ["s", "o", "D", "v", "^", "<", ">", "p", "*", "h", "H"]
689
+ for idx, (pair_name, data) in enumerate(groups.items()):
690
+ Ds = np.array(data["Ds"])
691
+ marker = markers[idx % len(markers)]
692
+ ax.scatter(
693
+ data["ds"],
694
+ Ds[:, 0],
695
+ marker=marker,
696
+ color="r",
697
+ label=f"{pair_name} Dx",
698
+ )
699
+ ax.scatter(
700
+ data["ds"],
701
+ Ds[:, 1],
702
+ marker=marker,
703
+ color="g",
704
+ label=f"{pair_name} Dy",
705
+ )
706
+ ax.scatter(
707
+ data["ds"],
708
+ Ds[:, 2],
709
+ marker=marker,
710
+ color="b",
711
+ label=f"{pair_name} Dz",
712
+ )
713
+ ax.legend(ncol=3, fontsize="small")
714
+ else:
715
+ ds = []
716
+ Ds = []
717
+ for key, val in self.dmi_ddict.items():
718
+ d = self.distance_dict[key][1]
719
+ ds.append(d)
720
+ Ds.append(val * 1e3)
721
+ Ds = np.array(Ds)
722
+ ax.scatter(ds, Ds[:, 0], marker="s", color="r", label="Dx")
723
+ ax.scatter(
724
+ ds, Ds[:, 1], marker="o", edgecolors="g", facecolors="none", label="Dy"
725
+ )
726
+ ax.scatter(
727
+ ds, Ds[:, 2], marker="D", edgecolors="b", facecolors="none", label="Dz"
728
+ )
729
+ ax.legend(loc=1)
605
730
  ax.axhline(color="gray")
606
- ax.legend(loc=1)
607
731
  ax.set_ylabel("D (meV)")
608
732
  ax.set_xlabel(r"Distance ($\AA$)")
609
733
  if fname is not None:
@@ -612,31 +736,71 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
612
736
  plt.show()
613
737
  return ax
614
738
 
615
- def plot_JanivsR(self, ax=None, fname=None, show=False):
739
+ def plot_JanivsR(self, ax=None, fname=None, show=False, by_species=False):
616
740
  if ax is None:
617
741
  fig, ax = plt.subplots()
618
- ds = []
619
- Jani = []
620
- for key, val in self.Jani_dict.items():
621
- d = self.distance_dict[key][1]
622
- ds.append(d)
623
- # val = val - np.diag([np.trace(val) / 3] * 3)
624
- Jani.append(val * 1e3)
625
- Jani = np.array(Jani)
626
- s = "xyz"
627
- for i in range(3):
628
- ax.scatter(ds, Jani[:, i, i], marker="s", label=f"J{s[i]}{s[i]}")
629
- c = "rgb"
630
- for ic, (i, j) in enumerate([(0, 1), (0, 2), (1, 2)]):
631
- ax.scatter(
632
- ds,
633
- Jani[:, i, j],
634
- edgecolors=c[ic],
635
- facecolors="none",
636
- label=f"J{s[i]}{s[j]}",
637
- )
742
+ if by_species:
743
+ groups = {}
744
+ for key, val in self.Jani_dict.items():
745
+ R, i, j = key
746
+ idx_i = self.ind_atoms[i]
747
+ idx_j = self.ind_atoms[j]
748
+ spec_i = self.atoms[idx_i].symbol
749
+ spec_j = self.atoms[idx_j].symbol
750
+ pair = tuple(sorted([spec_i, spec_j]))
751
+ pair_name = "-".join(pair)
752
+ if pair_name not in groups:
753
+ groups[pair_name] = {"ds": [], "Jani": []}
754
+ d = self.distance_dict[key][1]
755
+ groups[pair_name]["ds"].append(d)
756
+ groups[pair_name]["Jani"].append(val * 1e3)
757
+
758
+ markers = ["s", "o", "D", "v", "^", "<", ">", "p", "*", "h", "H"]
759
+ s = "xyz"
760
+ for idx, (pair_name, data) in enumerate(groups.items()):
761
+ Jani = np.array(data["Jani"])
762
+ marker = markers[idx % len(markers)]
763
+ # Diagonal
764
+ for i in range(3):
765
+ ax.scatter(
766
+ data["ds"],
767
+ Jani[:, i, i],
768
+ marker=marker,
769
+ label=f"{pair_name} J{s[i]}{s[i]}",
770
+ )
771
+ # Off-diagonal
772
+ for i, j in [(0, 1), (0, 2), (1, 2)]:
773
+ ax.scatter(
774
+ data["ds"],
775
+ Jani[:, i, j],
776
+ marker=marker,
777
+ facecolors="none",
778
+ label=f"{pair_name} J{s[i]}{s[j]}",
779
+ )
780
+ ax.legend(ncol=3, fontsize="small")
781
+ else:
782
+ ds = []
783
+ Jani = []
784
+ for key, val in self.Jani_dict.items():
785
+ d = self.distance_dict[key][1]
786
+ ds.append(d)
787
+ # val = val - np.diag([np.trace(val) / 3] * 3)
788
+ Jani.append(val * 1e3)
789
+ Jani = np.array(Jani)
790
+ s = "xyz"
791
+ for i in range(3):
792
+ ax.scatter(ds, Jani[:, i, i], marker="s", label=f"J{s[i]}{s[i]}")
793
+ c = "rgb"
794
+ for ic, (i, j) in enumerate([(0, 1), (0, 2), (1, 2)]):
795
+ ax.scatter(
796
+ ds,
797
+ Jani[:, i, j],
798
+ edgecolors=c[ic],
799
+ facecolors="none",
800
+ label=f"J{s[i]}{s[j]}",
801
+ )
802
+ ax.legend(loc=1, ncol=2)
638
803
  ax.axhline(color="gray")
639
- ax.legend(loc=1, ncol=2)
640
804
  ax.set_xlabel(r"Distance ($\AA$)")
641
805
  ax.set_ylabel("Jani (meV)")
642
806
  if fname is not None:
@@ -645,7 +809,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
645
809
  plt.show()
646
810
  return ax
647
811
 
648
- def plot_all(self, title=None, savefile=None, show=False):
812
+ def plot_all(self, title=None, savefile=None, show=False, by_species=False):
649
813
  if self.has_dmi and self.has_bilinear:
650
814
  naxis = 3
651
815
  else:
@@ -653,11 +817,11 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
653
817
  fig, axes = plt.subplots(naxis, 1, sharex=True, figsize=(5, 2.2 * naxis))
654
818
 
655
819
  if self.has_dmi and self.has_bilinear:
656
- self.plot_JvsR(axes[0])
657
- self.plot_DvsR(axes[1])
658
- self.plot_JanivsR(axes[2])
820
+ self.plot_JvsR(axes[0], by_species=by_species)
821
+ self.plot_DvsR(axes[1], by_species=by_species)
822
+ self.plot_JanivsR(axes[2], by_species=by_species)
659
823
  else:
660
- self.plot_JvsR(axes)
824
+ self.plot_JvsR(axes, by_species=by_species)
661
825
 
662
826
  if title is not None:
663
827
  fig.suptitle(title)
@@ -1,7 +1,8 @@
1
- import numpy as np
2
1
  import os
3
2
  from itertools import groupby
4
3
 
4
+ import numpy as np
5
+
5
6
 
6
7
  def write_tom_ucf(cls, fname):
7
8
  """
@@ -25,8 +26,8 @@ def write_tom_ucf(cls, fname):
25
26
  gyro_ratio = cls.gyro_ratio[i]
26
27
  symbol = cls.atoms.get_chemical_symbols()[i]
27
28
  if cls.has_uniaxial_anistropy:
28
- k1 = cls.k1[i]
29
- k1dir = cls.k1dir[i]
29
+ k1 = cls.k1[id_spin]
30
+ k1dir = cls.k1dir[id_spin]
30
31
  else:
31
32
  k1 = 0.0
32
33
  k1dir = [0.0, 0.0, 1.0]
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
 
3
4
  import numpy as np
4
5
  from numpy import array_str
@@ -9,6 +10,7 @@ from TB2J.utils import symbol_number
9
10
  def write_info_section(cls, myfile):
10
11
  myfile.write("=" * 90 + "\n")
11
12
  myfile.write("Information: \n")
13
+ myfile.write(f"Executed: \n{' '.join(sys.argv)}\n")
12
14
  # myfile.write("Exchange parameters generated by TB2J %s\n" % (__version__))
13
15
  # myfile.write("Generation time: %s\n" % (now.strftime("%y/%m/%d %H:%M:%S")))
14
16
  myfile.write(cls.description)
@@ -82,6 +84,24 @@ def write_atom_section(cls, myfile):
82
84
  )
83
85
  )
84
86
 
87
+ # write single ion anisotropy
88
+ if cls.k1 is not None and cls.k1dir is not None:
89
+ myfile.write("\n")
90
+ myfile.write("-" * 90 + "\n")
91
+ myfile.write("Single Ion Anisotropy (meV): \n")
92
+ myfile.write("(k1 is the anisotropy constant, k1dir is the direction vector)\n")
93
+ myfile.write("{:^12s} {:^12s} {:^24s}\n".format("Atom number", "k1", "k1dir"))
94
+ for i, s in enumerate(symnum):
95
+ ispin = cls.index_spin[i]
96
+ if ispin >= 0:
97
+ k1 = cls.k1[ispin] * 1e3
98
+ k1dir = cls.k1dir[ispin]
99
+ myfile.write(
100
+ "{:<12s} {:12.4f} ({:7.4f}, {:7.4f}, {:7.4f})\n".format(
101
+ s, k1, k1dir[0], k1dir[1], k1dir[2]
102
+ )
103
+ )
104
+
85
105
  myfile.write("\n")
86
106
 
87
107
 
@@ -159,17 +179,17 @@ def write_exchange_section(
159
179
 
160
180
  if cls.has_biquadratic and write_experimental:
161
181
  Jprime, B = cls.biquadratic_Jdict[ll]
162
- myfile.write(f"[Testing!] Jprime: {Jprime*1e3:.3f}, B: {B*1e3:.3f}\n")
182
+ myfile.write(f"[Testing!] Jprime: {Jprime * 1e3:.3f}, B: {B * 1e3:.3f}\n")
163
183
 
164
184
  if cls.dJdx is not None:
165
185
  dJdx = cls.dJdx[ll]
166
- myfile.write(f"dJ/dx: {dJdx*1e3:.3f}\n")
186
+ myfile.write(f"dJ/dx: {dJdx * 1e3:.3f}\n")
167
187
 
168
188
  if cls.dJdx2 is not None:
169
189
  dJdx2 = cls.dJdx2[ll]
170
- myfile.write(f"d2J/dx2: {dJdx2*1e3:.3f}\n")
190
+ myfile.write(f"d2J/dx2: {dJdx2 * 1e3:.3f}\n")
171
191
 
172
- if cls.dmi_ddict is not None:
192
+ if cls.dmi_ddict is not None and ll in cls.dmi_ddict:
173
193
  DMI = cls.dmi_ddict[ll] * 1e3
174
194
  myfile.write(
175
195
  "[Testing!] DMI: ({:7.4f} {:7.4f} {:7.4f})\n".format(
@@ -188,13 +208,13 @@ def write_exchange_section(
188
208
  except Exception as e:
189
209
  myfile.write(f"[Debug!] DMI2 not available: {e}\n")
190
210
 
191
- if cls.Jani_dict is not None:
211
+ if cls.Jani_dict is not None and ll in cls.Jani_dict:
192
212
  J = cls.Jani_dict[ll] * 1e3
193
213
  myfile.write(
194
214
  f"[Testing!]J_ani:\n{array_str(J, precision=3, suppress_small=True)}\n"
195
215
  )
196
216
 
197
- if cls.NJT_ddict is not None:
217
+ if cls.NJT_ddict is not None and ll in cls.NJT_ddict:
198
218
  DMI = cls.NJT_ddict[ll] * 1e3
199
219
  myfile.write(
200
220
  "[Experimental!] DMI_NJt: ({:7.4f} {:7.4f} {:7.4f})\n".format(
@@ -202,7 +222,7 @@ def write_exchange_section(
202
222
  )
203
223
  )
204
224
 
205
- if cls.NJT_Jdict is not None:
225
+ if cls.NJT_Jdict is not None and ll in cls.NJT_Jdict:
206
226
  J = cls.NJT_Jdict[ll] * 1e3
207
227
  myfile.write(
208
228
  "[Testing!] Jani_NJt: ({:7.4f} {:7.4f} {:7.4f})\n".format(
@@ -305,6 +325,50 @@ def write_Jq_info(cls, kpts, evals, evecs, myfile, special_kpoints={}):
305
325
  print(f"{sns[cls.ind_atoms[i]]}, {v[0]: 8.3f}, {v[2]: 8.3f}, {v[2]: 8.3f}\n")
306
326
 
307
327
 
328
+ def write_sia_section(cls, myfile):
329
+ """
330
+ Write single ion anisotropy tensor section.
331
+ """
332
+ if cls.has_sia_tensor and cls.sia_tensor is not None:
333
+ myfile.write("\n")
334
+ myfile.write("=" * 90 + "\n")
335
+ myfile.write("Single Ion Anisotropy: \n")
336
+ symnum = symbol_number(cls.atoms)
337
+ sns = list(symnum.keys())
338
+ myfile.write(
339
+ "{:^12s} {:^13s} {:^13s} {:^13s}\n".format(
340
+ "Atom_number", "A_xx", "A_xy", "A_xz"
341
+ )
342
+ )
343
+ myfile.write(
344
+ "{:^12s} {:^13s} {:^13s} {:^13s}\n".format("", "A_yx", "A_yy", "A_yz")
345
+ )
346
+ myfile.write(
347
+ "{:^12s} {:^13s} {:^13s} {:^13s}\n".format("", "A_zx", "A_zy", "A_zz")
348
+ )
349
+ for i, tensor in cls.sia_tensor.items():
350
+ iatom = cls.ind_atoms[i]
351
+ myfile.write(
352
+ "{:<12s} {:13.8f} {:13.8f} {:13.8f}\n".format(
353
+ sns[iatom],
354
+ tensor[0, 0] * 1e3,
355
+ tensor[0, 1] * 1e3,
356
+ tensor[0, 2] * 1e3,
357
+ )
358
+ )
359
+ myfile.write(
360
+ "{:<12s} {:13.8f} {:13.8f} {:13.8f}\n".format(
361
+ "", tensor[1, 0] * 1e3, tensor[1, 1] * 1e3, tensor[1, 2] * 1e3
362
+ )
363
+ )
364
+ myfile.write(
365
+ "{:<12s} {:13.8f} {:13.8f} {:13.8f}\n".format(
366
+ "", tensor[2, 0] * 1e3, tensor[2, 1] * 1e3, tensor[2, 2] * 1e3
367
+ )
368
+ )
369
+ myfile.write("-" * 88 + "\n")
370
+
371
+
308
372
  def write_txt(
309
373
  cls,
310
374
  path="TB2J_results",
@@ -321,6 +385,7 @@ def write_txt(
321
385
  write_info_section(cls, myfile)
322
386
  write_atom_section(cls, myfile)
323
387
  write_orbital_section(cls, myfile)
388
+ write_sia_section(cls, myfile)
324
389
  write_exchange_section(
325
390
  cls,
326
391
  myfile,
@@ -50,7 +50,7 @@ def write_vampire_unitcell_file(cls, fname):
50
50
  counter = -1
51
51
  for key in cls.exchange_Jdict:
52
52
  R, ispin, jspin = key
53
- Jtensor = cls.get_J_tensor(ispin, jspin, R)
53
+ Jtensor = cls.get_J_tensor(ispin, jspin, R, Jani=True, DMI=True)
54
54
  counter += 1 # starts at 0
55
55
  myfile.write(
56
56
  f"{counter:5d} {ispin:3d} {jspin:3d} {R[0]:3d} {R[1]:3d} {R[2]:3d} "