Skip to content

Spin System

SpinSys

Define the :class:SpinSys container used throughout the simulation package.

This module provides a single class, :class:SpinSys, which stores physical constants, system geometry, magnetic fields, interaction terms, tunneling parameters, and spectroscopy settings in one mutable object. It also includes helpers for parameter inspection and saving/ loading the object state.

SpinSys

SpinSys(Spins).

A class to represent a quantum spin system for spin simulations.

Parameters:

Name Type Description Default
Spins list

List of spin quantum numbers for each spin in the system.

required

Attributes:

Name Type Description
muB float

Bohr magneton in meV/T.

kB float

Boltzmann constant in meV/K.

meVtoGHzConversion float

Conversion factor from meV to GHz.

g float

Default g-factor for spins (can be overwritten).

Spins list

List of spin quantum numbers.

NSpins int

Number of spins in the system.

dimensionOfMatrix int

Dimension of the Hilbert space matrix representing the spin system.

ReadoutSpin int

Index of the spin used for readout.

AtomPositions ndarray

Positions of atoms in the system (shape: NSpins x 3).

latticeLengthXY ndarray

Lattice constants in the XY plane [nm].

T float

Temperature in Kelvin.

B list of float

External magnetic field vector [T].

BTip list of float

Magnetic field from the tip [T].

tip str

Tip type: 'a' for antiferromagnetic, 'f' for ferromagnetic.

H ndarray

Spin Hamiltonian matrix (complex).

gfactorvector list

List of g-factors for each spin.

Dvector ndarray

Out-of-plane anisotropy for each spin.

EvectorX ndarray

In-plane anisotropy in x direction for each spin.

EvectorY ndarray

In-plane anisotropy in x direction for each spin.

D0 ndarray

Dipole interaction matrix.

DipoleBool ndarray

Boolean array to activate/deactivate dipole interactions between spins.

Jvector ndarray

Vector containing all possible spin pair interactions.

Jxy ndarray

XY component of exchange interaction.

Jxz ndarray

XZ component of exchange interaction.

Jyz ndarray

YZ component of exchange interaction.

Jyx ndarray

YX component of exchange interaction.

Jzx ndarray

ZX component of exchange interaction.

Jzy ndarray

ZY component of exchange interaction.

V_DC float

DC bias voltage [V].

TipPolarization list of float

Polarization vector of the tip.

SamplePolarization list of float

Polarization vector of the sample.

U ndarray

On-site energies for each spin.

b0 float

Ratio of spin-independent tunneling electrons.

G_st float

Experimental conductance at 0V [A/V].

G_ss list

Sample-sample conductance for each spin.

G_tt int

Enables (1) or disables (0) tip-tip scattering contribution.

Stevens list of dict

Stevens operators for each spin.

use_Stevens bool

Flag to indicate if Stevens operators are used.

Source code in spinfinity/SpinSys.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
class SpinSys:
    """
    SpinSys(Spins).

    A class to represent a quantum spin system for spin simulations.

    Parameters
    ----------
    Spins : list
        List of spin quantum numbers for each spin in the system.

    Attributes
    ----------
    muB : float
        Bohr magneton in meV/T.
    kB : float
        Boltzmann constant in meV/K.
    meVtoGHzConversion : float
        Conversion factor from meV to GHz.
    g : float
        Default g-factor for spins (can be overwritten).
    Spins : list
        List of spin quantum numbers.
    NSpins : int
        Number of spins in the system.
    dimensionOfMatrix : int
        Dimension of the Hilbert space matrix representing the spin system.
    ReadoutSpin : int
        Index of the spin used for readout.
    AtomPositions : ndarray
        Positions of atoms in the system (shape: NSpins x 3).
    latticeLengthXY : ndarray
        Lattice constants in the XY plane [nm].
    T : float
        Temperature in Kelvin.
    B : list of float
        External magnetic field vector [T].
    BTip : list of float
        Magnetic field from the tip [T].
    tip : str
        Tip type: 'a' for antiferromagnetic, 'f' for ferromagnetic.
    H : ndarray
        Spin Hamiltonian matrix (complex).
    gfactorvector : list
        List of g-factors for each spin.
    Dvector : ndarray
        Out-of-plane anisotropy for each spin.
    EvectorX : ndarray
        In-plane anisotropy in x direction for each spin.
    EvectorY : ndarray
        In-plane anisotropy in x direction for each spin.
    D0 : ndarray
        Dipole interaction matrix.
    DipoleBool : ndarray
        Boolean array to activate/deactivate dipole interactions between spins.
    Jvector : ndarray
        Vector containing all possible spin pair interactions.
    Jxy : ndarray
        XY component of exchange interaction.
    Jxz : ndarray
        XZ component of exchange interaction.
    Jyz : ndarray
        YZ component of exchange interaction.
    Jyx : ndarray
        YX component of exchange interaction.
    Jzx : ndarray
        ZX component of exchange interaction.
    Jzy : ndarray
        ZY component of exchange interaction.
    V_DC : float
        DC bias voltage [V].
    TipPolarization : list of float
        Polarization vector of the tip.
    SamplePolarization : list of float
        Polarization vector of the sample.
    U : ndarray
        On-site energies for each spin.
    b0 : float
        Ratio of spin-independent tunneling electrons.
    G_st : float
        Experimental conductance at 0V [A/V].
    G_ss : list
        Sample-sample conductance for each spin.
    G_tt : int
        Enables (1) or disables (0) tip-tip scattering contribution.
    Stevens : list of dict
        Stevens operators for each spin.
    use_Stevens : bool
        Flag to indicate if Stevens operators are used.

    """

    # Constants
    mu0 = 1.25663706127e-6  # [N/A^2]
    muB = 0.05788  # [meV/T]
    muBHz = 9.274010078328e-24  # [J/T]
    kB = const.Boltzmann / const.e * 1e3  # [meV/K] Boltzmann constant in meV/K
    meVtoGHzConversion = 1e-12 * const.e / const.h  # approx 241.8 GHz/meV
    GHztomeVConversion = 1 / meVtoGHzConversion  # approx 4.1357 uV/GHz
    hbar_meV = const.hbar * 1e-12 / const.e  # [meV*s] Reduced Planck's
    # constant in meV*s
    h_meV = const.h * 1e-12 / const.e  # [meV*s] Planck's constant in meV*s
    meVtoJConversion = 1.602e-22  # approx 1.6e-22 J/meV
    JtomeVConversion = 1 / meVtoJConversion  # approx 6.24e21 meV/J

    # Initialize the Spin System ##
    def __init__(self, Spins: list):

        self.Spins = Spins
        self.NSpins = len(self.Spins)
        # A system with N spins [S1,S2,S3,...,SN] is represented through a matrix 
        # of dimension (2*S1+1)*(2*S2+1)*...*(2*SN+1)
        self.dimensionOfMatrix = 1  
        for i in range(self.NSpins):  
            self.dimensionOfMatrix *= (2 * self.Spins[i] + 1)
        self.dimensionOfMatrix = int(self.dimensionOfMatrix)
        self.ReadoutSpin = 0
        self.AtomPositions = np.zeros((self.NSpins, 3))
        # Lattice constants in the XY plane [nm]
        self.latticeLengthXY = np.array([0.2877, 0.2877, 0]) 

        # External Parameters
        self.T = 1  # [K]
        self.B = [0.0, 0.0, 0.0]  # [T]
        self.BTip = [0.0, 0.0, 0.0]  # [T]
        self.tip = 'f'  # 'a' for antiferromagentic and 'f' for ferromagnetic

        # Spin Hamiltoninan
        self.H = np.zeros((self.dimensionOfMatrix, self.dimensionOfMatrix),
                           dtype=complex)
        self.gfactorvector = [2.0] * self.NSpins
        self.Dvector = np.zeros(self.NSpins)
        self.EvectorX = np.zeros(self.NSpins)
        self.EvectorY = np.zeros(self.NSpins)
        self.D0 = np.zeros((self.NSpins, self.NSpins))
        self.DipoleBool = np.zeros(
            (1, (self.NSpins * (self.NSpins - 1)) // 2),
            dtype=bool,
        )
        self.Jvector = np.zeros(
            ((self.NSpins * (self.NSpins - 1)) // 2, 3)
        )
        # Off diagonal Elements of the Exchange Parameter J
        self.Jxy = np.zeros((self.NSpins * (self.NSpins - 1)) // 2, dtype=float)
        self.Jxz = np.zeros((self.NSpins * (self.NSpins - 1)) // 2, dtype=float)
        self.Jyz = np.zeros((self.NSpins * (self.NSpins - 1)) // 2, dtype=float)
        self.Jyx = np.zeros((self.NSpins * (self.NSpins - 1)) // 2, dtype=float)
        self.Jzx = np.zeros((self.NSpins * (self.NSpins - 1)) // 2, dtype=float)
        self.Jzy = np.zeros((self.NSpins * (self.NSpins - 1)) // 2, dtype=float)

        # Tunneling Parameters
        self.V_DC = 0  # [V]
        self.TipPolarization = [0, 0, 0]
        self.SamplePolarization = [0, 0, 0]
        self.U = np.zeros(self.NSpins) 
        self.b0 = 0  # Ratio how many electrons that tunnel do not care about the spin!!
        self.G_st = 1e-12  # [A/V], Experimental conductance at 0V;
        self.G_ss = [1e-8] * self.NSpins  # Sample-Sample conductance
        self.G_tt = 0  # 1 enables tip-tip scattering contribution, 0 disables it!

        # Stevens Operators
        self.Stevens = [{} for _ in range(self.NSpins)]
        self.use_Stevens = False  # Flag to indicate if Stevens operators are used

    # region PrintParameters

    def _round_to_significant_figures(self, value, sig_figs=4):
        """
        Round a value or array to specified number of significant figures.

        Parameters
        ----------
        value : float, int, np.ndarray, list, tuple
            Value to round
        sig_figs : int
            Number of significant figures (default: 4)

        Returns
        -------
        Rounded value in same format as input
        """
        if isinstance(value, (list, tuple)):
            return type(value)(
                [self._round_to_significant_figures(v, sig_figs) for v in value]
            )
        elif isinstance(value, np.ndarray):
            if np.issubdtype(value.dtype, np.number):
                # Only round if it's a numeric array
                rounded = np.array(
                    [
                        self._round_to_significant_figures(v, sig_figs)
                        for v in value.flat
                    ]
                )
                return rounded.reshape(value.shape)
            else:
                return value
        elif isinstance(value, (int, float, complex)):
            if value == 0:
                return 0
            try:
                if isinstance(value, complex):
                    real_part = self._round_to_significant_figures(value.real, sig_figs)
                    imag_part = self._round_to_significant_figures(value.imag, sig_figs)
                    return complex(real_part, imag_part)
                else:
                    # For real numbers
                    from math import floor, log10
                    return round(value, -int(floor(log10(abs(value)))) + (sig_figs - 1))
            except (ValueError, OverflowError):
                return value
        else:
            return value

    def print_parameters(self):
        """Print all parameters in a detailed format."""
        params = self.get_parameters_dict()

        for category, param_dict in params.items():
            print(f"\n{category.upper()}")
            print("=" * len(category))
            for key, value in param_dict.items():
                if isinstance(value, np.ndarray):
                    rounded_value = self._round_to_significant_figures(value)
                    print(f"  {key}:")
                    if rounded_value.ndim == 1:
                        # 1D array - display as simple array
                        print(f"    {rounded_value}")
                    elif rounded_value.ndim == 2:
                        # 2D array - display as matrix
                        print(f"    {rounded_value}")
                    else:
                        # Higher dimensional arrays - show shape and summary
                        print(f"    Shape: {rounded_value.shape}")
                        print(f"    Type: {rounded_value.dtype}")
                elif isinstance(value, (list, tuple, int, float, complex)):
                    rounded_value = self._round_to_significant_figures(value)
                    print(f"  {key:<25}: {rounded_value}")
                else:
                    print(f"  {key:<25}: {value}")

    def get_parameters_dict(self):
        """
        Return parameters organized in a dictionary by category.

        Returns
        -------
        dict
            Dictionary with parameter categories as keys and parameter dicts as values.
        """
        params_dict = {
            'System': {
                'Spins': self.Spins,
                'NSpins': self.NSpins,
                'dimensionOfMatrix': self.dimensionOfMatrix,
                'ReadoutSpin': getattr(self, 'ReadoutSpin', 'Not set'),
                'Temperature': f"{self.T} K"
            },
            'Magnetic Fields': {
                'B_external': np.array(self.B) if hasattr(self, 'B') else 'Not set',
                'BTip': np.array(getattr(self, 'BTip', [0, 0, 0])),
                'g_factors': (
                    np.array(getattr(self, 'gfactorvector', []))
                    if getattr(self, 'gfactorvector', None) is not None
                    else 'Not set'
                )
            },
            'Interactions': {},
            'Tunneling': {}
        }

        # Add interaction matrices if they exist
        if hasattr(self, 'Jvector') and self.Jvector is not None:
            params_dict['Interactions']['Exchange_J_meV'] = self.Jvector
            params_dict['Interactions']['Exchange_J_GHz'] = (
                self.Jvector * self.meVtoGHzConversion
            )

        if hasattr(self, 'Dvector') and self.Dvector is not None:
            params_dict['Interactions']['Anisotropy_D_meV'] = np.array(self.Dvector)

        if hasattr(self, 'Evector') and self.Evector is not None:
            params_dict['Interactions']['Anisotropy_E_meV'] = np.array(self.Evector)

        if hasattr(self, 'D0') and self.D0 is not None:
            params_dict['Interactions']['Dipole_matrix_GHz'] = (
                self.D0 * self.meVtoGHzConversion
            )

        if hasattr(self, 'DipoleBool') and self.DipoleBool is not None:
            params_dict['Interactions']['DipoleBool'] = np.array(self.DipoleBool)

        if hasattr(self, 'AtomPositions') and self.AtomPositions is not None:
            params_dict['System']['AtomPositions'] = self.AtomPositions

        # Add tunneling parameters if they exist
        if hasattr(self, 'V_DC'):
            params_dict['Tunneling']['V_DC_mV'] = self.V_DC
        if hasattr(self, 'TipPolarization'):
            params_dict['Tunneling']['TipPolarization'] = np.array(self.TipPolarization)
        if hasattr(self, 'SamplePolarization'):
            params_dict['Tunneling']['SamplePolarization'] = np.array(
                getattr(self, 'SamplePolarization', [0, 0, 0])
            )
        if hasattr(self, 'G'):
            params_dict['Tunneling']['Conductance_G_A_per_V'] = self.G_st
        if hasattr(self, 'G_ss'):
            params_dict['Tunneling']['G_ss'] = (
                np.array(self.G_ss)
                if isinstance(self.G_ss, (list, tuple))
                else self.G_ss
            )
        if hasattr(self, 'G_tt'):
            params_dict['Tunneling']['G_tt'] = self.G_tt
        if hasattr(self, 'U'):
            params_dict['Tunneling']['U'] = self.U
        if hasattr(self, 'b0'):
            params_dict['Tunneling']['b0'] = self.b0

        return params_dict

    # endregion PrintParameters

    # region SaveFunctions

    def save(self, filename=None, filepath=None):
        """
        Save the SpinSys object to a pickle file.

        Parameters
        ----------
        filename : str, optional
            Name of the file to save. If None, generates a default name based on 
            system parameters.
        filepath : str, optional
            Directory path where to save the file. If None, saves in current directory.

        Returns
        -------
        str
            Full path to the saved file.

        Examples
        --------
        >>> spinsys = SpinSys([1, 0.5])
        >>> spinsys.save('my_spinsystem.pkl')
        >>> spinsys.save(filename='experiment1.pkl', filepath='./data/')
        """
        # Generate default filename if not provided
        if filename is None:
            spin_str = '_'.join([f"S{s}" for s in self.Spins])
            filename = f"SpinSys_{spin_str}_B{self.B[2]:.2f}T.pkl"

        # Set default filepath if not provided
        if filepath is None:
            filepath = "."

        # Ensure filepath exists
        os.makedirs(filepath, exist_ok=True)

        # Create full file path
        full_path = os.path.join(filepath, filename)

        # Save the object using pickle
        try:
            with open(full_path, 'wb') as f:
                pickle.dump(self, f)
            print(f"SpinSys object successfully saved to: {full_path}")
            return full_path
        except Exception as e:
            print(f"Error saving SpinSys object: {e}")
            raise

    @staticmethod
    def load(filename: str, filepath: str):
        """
        Load a SpinSys object from a pickle file.

        Parameters
        ----------
        filename : str
            Name of the pickle file to load.
        filepath : str
            Directory path where the file is located.

        Returns
        -------
        SpinSys
            The loaded SpinSys object.

        Raises
        ------
        FileNotFoundError
            If the specified file does not exist.
        """
        full_path = os.path.join(filepath, filename)

        try:
            with open(full_path, 'rb') as f:
                spinsys = pickle.load(f)
            print(f"SpinSys object successfully loaded from: {full_path}")
            return spinsys
        except FileNotFoundError:
            print(f"Error: File '{full_path}' not found.")
            raise
        except Exception as e:
            print(f"Error loading SpinSys object: {e}")
            raise

get_parameters_dict()

Return parameters organized in a dictionary by category.

Returns:

Type Description
dict

Dictionary with parameter categories as keys and parameter dicts as values.

Source code in spinfinity/SpinSys.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def get_parameters_dict(self):
    """
    Return parameters organized in a dictionary by category.

    Returns
    -------
    dict
        Dictionary with parameter categories as keys and parameter dicts as values.
    """
    params_dict = {
        'System': {
            'Spins': self.Spins,
            'NSpins': self.NSpins,
            'dimensionOfMatrix': self.dimensionOfMatrix,
            'ReadoutSpin': getattr(self, 'ReadoutSpin', 'Not set'),
            'Temperature': f"{self.T} K"
        },
        'Magnetic Fields': {
            'B_external': np.array(self.B) if hasattr(self, 'B') else 'Not set',
            'BTip': np.array(getattr(self, 'BTip', [0, 0, 0])),
            'g_factors': (
                np.array(getattr(self, 'gfactorvector', []))
                if getattr(self, 'gfactorvector', None) is not None
                else 'Not set'
            )
        },
        'Interactions': {},
        'Tunneling': {}
    }

    # Add interaction matrices if they exist
    if hasattr(self, 'Jvector') and self.Jvector is not None:
        params_dict['Interactions']['Exchange_J_meV'] = self.Jvector
        params_dict['Interactions']['Exchange_J_GHz'] = (
            self.Jvector * self.meVtoGHzConversion
        )

    if hasattr(self, 'Dvector') and self.Dvector is not None:
        params_dict['Interactions']['Anisotropy_D_meV'] = np.array(self.Dvector)

    if hasattr(self, 'Evector') and self.Evector is not None:
        params_dict['Interactions']['Anisotropy_E_meV'] = np.array(self.Evector)

    if hasattr(self, 'D0') and self.D0 is not None:
        params_dict['Interactions']['Dipole_matrix_GHz'] = (
            self.D0 * self.meVtoGHzConversion
        )

    if hasattr(self, 'DipoleBool') and self.DipoleBool is not None:
        params_dict['Interactions']['DipoleBool'] = np.array(self.DipoleBool)

    if hasattr(self, 'AtomPositions') and self.AtomPositions is not None:
        params_dict['System']['AtomPositions'] = self.AtomPositions

    # Add tunneling parameters if they exist
    if hasattr(self, 'V_DC'):
        params_dict['Tunneling']['V_DC_mV'] = self.V_DC
    if hasattr(self, 'TipPolarization'):
        params_dict['Tunneling']['TipPolarization'] = np.array(self.TipPolarization)
    if hasattr(self, 'SamplePolarization'):
        params_dict['Tunneling']['SamplePolarization'] = np.array(
            getattr(self, 'SamplePolarization', [0, 0, 0])
        )
    if hasattr(self, 'G'):
        params_dict['Tunneling']['Conductance_G_A_per_V'] = self.G_st
    if hasattr(self, 'G_ss'):
        params_dict['Tunneling']['G_ss'] = (
            np.array(self.G_ss)
            if isinstance(self.G_ss, (list, tuple))
            else self.G_ss
        )
    if hasattr(self, 'G_tt'):
        params_dict['Tunneling']['G_tt'] = self.G_tt
    if hasattr(self, 'U'):
        params_dict['Tunneling']['U'] = self.U
    if hasattr(self, 'b0'):
        params_dict['Tunneling']['b0'] = self.b0

    return params_dict

load(filename, filepath) staticmethod

Load a SpinSys object from a pickle file.

Parameters:

Name Type Description Default
filename str

Name of the pickle file to load.

required
filepath str

Directory path where the file is located.

required

Returns:

Type Description
SpinSys

The loaded SpinSys object.

Raises:

Type Description
FileNotFoundError

If the specified file does not exist.

Source code in spinfinity/SpinSys.py
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
@staticmethod
def load(filename: str, filepath: str):
    """
    Load a SpinSys object from a pickle file.

    Parameters
    ----------
    filename : str
        Name of the pickle file to load.
    filepath : str
        Directory path where the file is located.

    Returns
    -------
    SpinSys
        The loaded SpinSys object.

    Raises
    ------
    FileNotFoundError
        If the specified file does not exist.
    """
    full_path = os.path.join(filepath, filename)

    try:
        with open(full_path, 'rb') as f:
            spinsys = pickle.load(f)
        print(f"SpinSys object successfully loaded from: {full_path}")
        return spinsys
    except FileNotFoundError:
        print(f"Error: File '{full_path}' not found.")
        raise
    except Exception as e:
        print(f"Error loading SpinSys object: {e}")
        raise

print_parameters()

Print all parameters in a detailed format.

Source code in spinfinity/SpinSys.py
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def print_parameters(self):
    """Print all parameters in a detailed format."""
    params = self.get_parameters_dict()

    for category, param_dict in params.items():
        print(f"\n{category.upper()}")
        print("=" * len(category))
        for key, value in param_dict.items():
            if isinstance(value, np.ndarray):
                rounded_value = self._round_to_significant_figures(value)
                print(f"  {key}:")
                if rounded_value.ndim == 1:
                    # 1D array - display as simple array
                    print(f"    {rounded_value}")
                elif rounded_value.ndim == 2:
                    # 2D array - display as matrix
                    print(f"    {rounded_value}")
                else:
                    # Higher dimensional arrays - show shape and summary
                    print(f"    Shape: {rounded_value.shape}")
                    print(f"    Type: {rounded_value.dtype}")
            elif isinstance(value, (list, tuple, int, float, complex)):
                rounded_value = self._round_to_significant_figures(value)
                print(f"  {key:<25}: {rounded_value}")
            else:
                print(f"  {key:<25}: {value}")

save(filename=None, filepath=None)

Save the SpinSys object to a pickle file.

Parameters:

Name Type Description Default
filename str

Name of the file to save. If None, generates a default name based on system parameters.

None
filepath str

Directory path where to save the file. If None, saves in current directory.

None

Returns:

Type Description
str

Full path to the saved file.

Examples:

>>> spinsys = SpinSys([1, 0.5])
>>> spinsys.save('my_spinsystem.pkl')
>>> spinsys.save(filename='experiment1.pkl', filepath='./data/')
Source code in spinfinity/SpinSys.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
def save(self, filename=None, filepath=None):
    """
    Save the SpinSys object to a pickle file.

    Parameters
    ----------
    filename : str, optional
        Name of the file to save. If None, generates a default name based on 
        system parameters.
    filepath : str, optional
        Directory path where to save the file. If None, saves in current directory.

    Returns
    -------
    str
        Full path to the saved file.

    Examples
    --------
    >>> spinsys = SpinSys([1, 0.5])
    >>> spinsys.save('my_spinsystem.pkl')
    >>> spinsys.save(filename='experiment1.pkl', filepath='./data/')
    """
    # Generate default filename if not provided
    if filename is None:
        spin_str = '_'.join([f"S{s}" for s in self.Spins])
        filename = f"SpinSys_{spin_str}_B{self.B[2]:.2f}T.pkl"

    # Set default filepath if not provided
    if filepath is None:
        filepath = "."

    # Ensure filepath exists
    os.makedirs(filepath, exist_ok=True)

    # Create full file path
    full_path = os.path.join(filepath, filename)

    # Save the object using pickle
    try:
        with open(full_path, 'wb') as f:
            pickle.dump(self, f)
        print(f"SpinSys object successfully saved to: {full_path}")
        return full_path
    except Exception as e:
        print(f"Error saving SpinSys object: {e}")
        raise