TB2J 0.9.12.18__py3-none-any.whl → 0.9.12.23__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 +243 -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 +16 -16
  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.23.dist-info}/METADATA +7 -3
  28. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/RECORD +32 -35
  29. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/WHEEL +1 -1
  30. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.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.23.dist-info}/licenses/LICENSE +0 -0
  40. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.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,27 @@ 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,
458
+ R,
459
+ Jiso=True,
460
+ Jani=True,
461
+ DMI=True,
462
+ SIA=True,
463
+ ):
404
464
  """
405
465
  Return the full exchange tensor of all i and j for cell R.
406
466
  param R (tuple of integers): cell index R
407
467
  returns:
408
468
  Jmat: (nspin,nspin,3,3) matrix.
409
469
  """
410
- if Jani or DMI:
470
+ if Jani or DMI or SIA:
411
471
  raise ValueError(
412
- "Jani and DMI are not supported for this method. Use get_full_Jtensor_for_one_R_ij33 instead."
472
+ "Jani, DMI and SIA are not supported for this method. Use get_full_Jtensor_for_one_R_ij33 instead."
413
473
  )
414
474
  n = self.nspin
415
475
  Jmat = np.zeros((n, n), dtype=float)
@@ -421,7 +481,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
421
481
  return Jmat
422
482
 
423
483
  def get_full_Jtensor_for_Rlist(
424
- self, asr=False, Jiso=True, Jani=True, DMI=True, order="i3j3"
484
+ self, asr=False, Jiso=True, Jani=True, DMI=True, SIA=True, order="i3j3"
425
485
  ):
426
486
  n = self.nspin
427
487
  n3 = n * 3
@@ -430,7 +490,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
430
490
  Jmat = np.zeros((nR, n3, n3), dtype=float)
431
491
  for iR, R in enumerate(self.Rlist):
432
492
  Jmat[iR] = self.get_full_Jtensor_for_one_R_i3j3(
433
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
493
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
434
494
  )
435
495
  if asr:
436
496
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -442,8 +502,9 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
442
502
  elif order == "ij33":
443
503
  Jmat = np.zeros((nR, n, n, 3, 3), dtype=float)
444
504
  for iR, R in enumerate(self.Rlist):
505
+ print(f"R={R}")
445
506
  Jmat[iR] = self.get_full_Jtensor_for_one_R_ij33(
446
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
507
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
447
508
  )
448
509
  if asr:
449
510
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -454,8 +515,9 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
454
515
  elif order == "i3j3_2D":
455
516
  Jmat = np.zeros((nR, n3, n3), dtype=float)
456
517
  for iR, R in enumerate(self.Rlist):
518
+ print(f"R={R}")
457
519
  Jmat[iR] = self.get_full_Jtensor_for_one_R_i3j3(
458
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
520
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
459
521
  ).reshape((n3, n3))
460
522
  if asr:
461
523
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -468,7 +530,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
468
530
  Jmat = np.zeros((nR, n, n), dtype=float)
469
531
  for iR, R in enumerate(self.Rlist):
470
532
  Jmat[iR] = self.get_full_Jtensor_for_one_R_ij(
471
- R, Jiso=Jiso, Jani=Jani, DMI=DMI
533
+ R, Jiso=Jiso, Jani=Jani, DMI=DMI, SIA=SIA
472
534
  )
473
535
  if asr:
474
536
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
@@ -565,17 +627,41 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
565
627
  marker="o",
566
628
  fname=None,
567
629
  show=False,
630
+ by_species=False,
568
631
  **kwargs,
569
632
  ):
570
633
  if ax is None:
571
634
  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)
635
+ if by_species:
636
+ groups = {}
637
+ for key, val in self.exchange_Jdict.items():
638
+ R, i, j = key
639
+ idx_i = self.ind_atoms[i]
640
+ idx_j = self.ind_atoms[j]
641
+ spec_i = self.atoms[idx_i].symbol
642
+ spec_j = self.atoms[idx_j].symbol
643
+ pair = tuple(sorted([spec_i, spec_j]))
644
+ pair_name = "-".join(pair)
645
+ if pair_name not in groups:
646
+ groups[pair_name] = {"ds": [], "Js": []}
647
+ d = self.distance_dict[key][1]
648
+ groups[pair_name]["ds"].append(d)
649
+ groups[pair_name]["Js"].append(val * 1e3)
650
+
651
+ for pair_name, data in groups.items():
652
+ ax.scatter(
653
+ data["ds"], data["Js"], marker=marker, label=pair_name, **kwargs
654
+ )
655
+ ax.legend()
656
+ else:
657
+ ds = []
658
+ Js = []
659
+ for key, val in self.exchange_Jdict.items():
660
+ d = self.distance_dict[key][1]
661
+ ds.append(d)
662
+ Js.append(val * 1e3)
663
+ ax.scatter(ds, Js, marker=marker, color=color, **kwargs)
664
+
579
665
  ax.axhline(color="gray")
580
666
  ax.set_xlabel(r"Distance ($\AA$)")
581
667
  ax.set_ylabel("J (meV)")
@@ -585,25 +671,68 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
585
671
  plt.show()
586
672
  return ax
587
673
 
588
- def plot_DvsR(self, ax=None, fname=None, show=False):
674
+ def plot_DvsR(self, ax=None, fname=None, show=False, by_species=False):
589
675
  if ax is None:
590
676
  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
- )
677
+ if by_species:
678
+ groups = {}
679
+ for key, val in self.dmi_ddict.items():
680
+ R, i, j = key
681
+ idx_i = self.ind_atoms[i]
682
+ idx_j = self.ind_atoms[j]
683
+ spec_i = self.atoms[idx_i].symbol
684
+ spec_j = self.atoms[idx_j].symbol
685
+ pair = tuple(sorted([spec_i, spec_j]))
686
+ pair_name = "-".join(pair)
687
+ if pair_name not in groups:
688
+ groups[pair_name] = {"ds": [], "Ds": []}
689
+ d = self.distance_dict[key][1]
690
+ groups[pair_name]["ds"].append(d)
691
+ groups[pair_name]["Ds"].append(val * 1e3)
692
+
693
+ markers = ["s", "o", "D", "v", "^", "<", ">", "p", "*", "h", "H"]
694
+ for idx, (pair_name, data) in enumerate(groups.items()):
695
+ Ds = np.array(data["Ds"])
696
+ marker = markers[idx % len(markers)]
697
+ ax.scatter(
698
+ data["ds"],
699
+ Ds[:, 0],
700
+ marker=marker,
701
+ color="r",
702
+ label=f"{pair_name} Dx",
703
+ )
704
+ ax.scatter(
705
+ data["ds"],
706
+ Ds[:, 1],
707
+ marker=marker,
708
+ color="g",
709
+ label=f"{pair_name} Dy",
710
+ )
711
+ ax.scatter(
712
+ data["ds"],
713
+ Ds[:, 2],
714
+ marker=marker,
715
+ color="b",
716
+ label=f"{pair_name} Dz",
717
+ )
718
+ ax.legend(ncol=3, fontsize="small")
719
+ else:
720
+ ds = []
721
+ Ds = []
722
+ for key, val in self.dmi_ddict.items():
723
+ d = self.distance_dict[key][1]
724
+ ds.append(d)
725
+ Ds.append(val * 1e3)
726
+ Ds = np.array(Ds)
727
+ ax.scatter(ds, Ds[:, 0], marker="s", color="r", label="Dx")
728
+ ax.scatter(
729
+ ds, Ds[:, 1], marker="o", edgecolors="g", facecolors="none", label="Dy"
730
+ )
731
+ ax.scatter(
732
+ ds, Ds[:, 2], marker="D", edgecolors="b", facecolors="none", label="Dz"
733
+ )
734
+ ax.legend(loc=1)
605
735
  ax.axhline(color="gray")
606
- ax.legend(loc=1)
607
736
  ax.set_ylabel("D (meV)")
608
737
  ax.set_xlabel(r"Distance ($\AA$)")
609
738
  if fname is not None:
@@ -612,31 +741,71 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
612
741
  plt.show()
613
742
  return ax
614
743
 
615
- def plot_JanivsR(self, ax=None, fname=None, show=False):
744
+ def plot_JanivsR(self, ax=None, fname=None, show=False, by_species=False):
616
745
  if ax is None:
617
746
  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
- )
747
+ if by_species:
748
+ groups = {}
749
+ for key, val in self.Jani_dict.items():
750
+ R, i, j = key
751
+ idx_i = self.ind_atoms[i]
752
+ idx_j = self.ind_atoms[j]
753
+ spec_i = self.atoms[idx_i].symbol
754
+ spec_j = self.atoms[idx_j].symbol
755
+ pair = tuple(sorted([spec_i, spec_j]))
756
+ pair_name = "-".join(pair)
757
+ if pair_name not in groups:
758
+ groups[pair_name] = {"ds": [], "Jani": []}
759
+ d = self.distance_dict[key][1]
760
+ groups[pair_name]["ds"].append(d)
761
+ groups[pair_name]["Jani"].append(val * 1e3)
762
+
763
+ markers = ["s", "o", "D", "v", "^", "<", ">", "p", "*", "h", "H"]
764
+ s = "xyz"
765
+ for idx, (pair_name, data) in enumerate(groups.items()):
766
+ Jani = np.array(data["Jani"])
767
+ marker = markers[idx % len(markers)]
768
+ # Diagonal
769
+ for i in range(3):
770
+ ax.scatter(
771
+ data["ds"],
772
+ Jani[:, i, i],
773
+ marker=marker,
774
+ label=f"{pair_name} J{s[i]}{s[i]}",
775
+ )
776
+ # Off-diagonal
777
+ for i, j in [(0, 1), (0, 2), (1, 2)]:
778
+ ax.scatter(
779
+ data["ds"],
780
+ Jani[:, i, j],
781
+ marker=marker,
782
+ facecolors="none",
783
+ label=f"{pair_name} J{s[i]}{s[j]}",
784
+ )
785
+ ax.legend(ncol=3, fontsize="small")
786
+ else:
787
+ ds = []
788
+ Jani = []
789
+ for key, val in self.Jani_dict.items():
790
+ d = self.distance_dict[key][1]
791
+ ds.append(d)
792
+ # val = val - np.diag([np.trace(val) / 3] * 3)
793
+ Jani.append(val * 1e3)
794
+ Jani = np.array(Jani)
795
+ s = "xyz"
796
+ for i in range(3):
797
+ ax.scatter(ds, Jani[:, i, i], marker="s", label=f"J{s[i]}{s[i]}")
798
+ c = "rgb"
799
+ for ic, (i, j) in enumerate([(0, 1), (0, 2), (1, 2)]):
800
+ ax.scatter(
801
+ ds,
802
+ Jani[:, i, j],
803
+ edgecolors=c[ic],
804
+ facecolors="none",
805
+ label=f"J{s[i]}{s[j]}",
806
+ )
807
+ ax.legend(loc=1, ncol=2)
638
808
  ax.axhline(color="gray")
639
- ax.legend(loc=1, ncol=2)
640
809
  ax.set_xlabel(r"Distance ($\AA$)")
641
810
  ax.set_ylabel("Jani (meV)")
642
811
  if fname is not None:
@@ -645,7 +814,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
645
814
  plt.show()
646
815
  return ax
647
816
 
648
- def plot_all(self, title=None, savefile=None, show=False):
817
+ def plot_all(self, title=None, savefile=None, show=False, by_species=False):
649
818
  if self.has_dmi and self.has_bilinear:
650
819
  naxis = 3
651
820
  else:
@@ -653,11 +822,11 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
653
822
  fig, axes = plt.subplots(naxis, 1, sharex=True, figsize=(5, 2.2 * naxis))
654
823
 
655
824
  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])
825
+ self.plot_JvsR(axes[0], by_species=by_species)
826
+ self.plot_DvsR(axes[1], by_species=by_species)
827
+ self.plot_JanivsR(axes[2], by_species=by_species)
659
828
  else:
660
- self.plot_JvsR(axes)
829
+ self.plot_JvsR(axes, by_species=by_species)
661
830
 
662
831
  if title is not None:
663
832
  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,