qiskit-nature icon indicating copy to clipboard operation
qiskit-nature copied to clipboard

General methods for second quantized operators

Open LaurinFischer opened this issue 3 years ago • 7 comments

What is the expected enhancement?

There is some general functionality which would be nice to have for all types of second-quantized ops:

  1. Checks for hermiticity of an operator (Is the operator equivalent to its adjoint)?
  2. A direct way to compute commutators, and checks for commutativity.
  3. I think it would be nice to also have a .to_matrix method for the FermionicOp as is already in place for the SpinOp.
  4. For the .to_matrix() methods it would be nice to return a sparse datatype such as one of the scipy.sparse formats like csc. Maybe this could be its own method .to_sparse_matrix().

Points 1. and 2. should be straightforward to implement with the current arithmetics already in place.

For 3. one needs to make a choice whether to use the full Fock space (with varying particle number) as a basis or only a certain particle-number (and fixed spin?) subspace. It might be nice to have this be specified by the user.

LaurinFischer avatar Apr 13 '21 12:04 LaurinFischer

@ikkoham Would you also be willing to look into these suggestions?

The sparse-matrix suggestions could be tied into a separate tasks of restructuring the operators to an internal sparse representation.

mrossinek avatar Apr 13 '21 12:04 mrossinek

Thank you for nice suggestion.

  1. Checks for hermiticity of an operator (Is the operator equivalent to its adjoint)?

SecondQuantizedOp.is_hermite() -> bool is nice!

  1. A direct way to compute commutators, and checks for commutativity.

We have two choices.

  1. SecondQuantizedOp.is_commutative(op: SecondQuantizedOp) -> bool
  2. commutator(op1: SecondQuantizedOp, op2: SecondQuantizedOp).is_zero(). I've made this in opflow.
  1. I think it would be nice to also have a .to_matrix method for the FermionicOp as is already in place for the SpinOp.

The basis ofFermionicOp.to_matrix() isn't trivial as you said. Which basis is convenient for chemistry users?

  1. For the .to_matrix() methods it would be nice to return a sparse datatype such as one of the scipy.sparse formats like csc. Maybe this could be its own method .to_sparse_matrix().

Nice. See https://github.com/Qiskit/qiskit-nature/blob/master/qiskit_nature/operators/second_quantization/spin_op.py#L439. This parameter is same with quantum_info.Pauli.

ikkoham avatar Apr 15 '21 12:04 ikkoham

Thanks for the reply!

The basis of FermionicOp.to_matrix() isn't trivial as you said. Which basis is convenient for chemistry users?

For chemistry applications the total number of particles should always be conserved and the second-quantized Hamiltonian of molecules is also spin-conserving. However, for other applications such as fermionic lattice-problems in condensed matter physics, it would also be desirable to keep the particle-number and spin-number flexible.

I have recently been working on such problems where I needed to build FermionicOps as matrix representations in a flexible way. The solution I implemented for my project is to have a dedicated FermionicBasis object that constructs the required basis.

I think FermionicOp.to_matrix() could also benefit from such an implementation. Let me illustrate this with a small example where the matrix representation of the same FermionicOp is built over three different bases:

Example

basis_spin_conserved = FermionicBasis(sites=2, n_up=1, n_down=1)
print(basis_spin_conserved)
>>> index | occupations ↑ | occupations ↓  
     0.   |0, 1> |0, 1>
     1.   |0, 1> |1, 0>
     2.   |1, 0> |0, 1>
     3.   |1, 0> |1, 0>

where initialization with n_up and n_down separately implies spin conservation. Alternatively, initializing from n_particles could give the basis with no spin-conservation:

basis_number_conserved = FermionicBasis(sites=2, n_particles=2)
print(basis_number_conserved)
>>> index | occupations ↑ | occupations ↓  
     0.   |0, 0> |1, 1>
     1.   |0, 1> |0, 1>
     2.   |0, 1> |1, 0>
     3.   |1, 0> |0, 1>
     4.   |1, 0> |1, 0>
     5.   |1, 1> |0, 0>

Similarly, without a fixed particle number, one would have the total Fock space basis which is already considerably larger:

basis_general = FermionicBasis(sites=2)
print(basis_general)
>>> index | occupations ↑ | occupations ↓  
    0.   |1, 1> |1, 1>
    1.   |0, 1> |1, 1>
    2.   |1, 0> |1, 1>
    3.   |1, 1> |0, 1>
    4.   |1, 1> |1, 0>
    5.   |0, 0> |1, 1>
    6.   |0, 1> |0, 1>
    7.   |0, 1> |1, 0>
    8.   |1, 0> |0, 1>
    9.   |1, 0> |1, 0>
    10.  |1, 1> |0, 0>
    11.  |0, 0> |0, 1>
    12.  |0, 0> |1, 0>
    13.  |0, 1> |0, 0>
    14.  |1, 0> |0, 0>
    15.  |0, 0> |0, 0>

This would then enable the creation of a matrix representation over the specified basis by passing the basis to the to_matrix() method, for example:

op = FermionicOp([("+-II", 1), ("-+II", -1), ("II+-", 1), ("II-+", -1)])

mat1 = op.to_matrix(basis=basis_spin_conserved)
print(mat1)
>>>[[0. 1. 1. 0.]
    [1. 0. 0. 1.]
    [1. 0. 0. 1.]
    [0. 1. 1. 0.]]

mat2 = op.to_matrix(basis=basis_number_conserved)
print(mat2)
>>>[[0. 0. 0. 0. 0. 0.]
    [0. 0. 1. 1. 0. 0.]
    [0. 1. 0. 0. 1. 0.]
    [0. 1. 0. 0. 1. 0.]
    [0. 0. 1. 1. 0. 0.]
    [0. 0. 0. 0. 0. 0.]]

mat3 = op.to_matrix(basis=basis_general)
print(mat3)
>>>[[0. 0. 0. 0. 0. 0. 0. 0. 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. 1. 0. 0. 0. 0. 0. 0. 0. 0. 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. 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. 0.]
    [0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 0. 0. 0. 0. 1. 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. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
    [0. 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. 0. 1. 0.]
    [0. 0. 0. 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. 0.]]

The ordering of states in these matrices corresponds to the internal order of states within the FermionicBasis objects as printed. I think one would need the FermionicBasis as its own object for maximum flexibility but this may debatable. Maybe one could directly build the matrix as

op.to_matrix(n_up=1, n_down=1)
>>>[[0. 1. 1. 0.]
    [1. 0. 0. 1.]
    [1. 0. 0. 1.]
    [0. 1. 1. 0.]]

I would be very curious to hear whether you think such a functionality is of interest to have within qiskit_nature.

LaurinFischer avatar Apr 15 '21 14:04 LaurinFischer

I think if FermionicOp A and FermionicOp B are different, A.to_matrix() and B.to_matrix() must be different, and vice versa.

I'm not sure if this representation is standard now. Do you have any references? (it will be in document in the FermionicBasis class.)

ikkoham avatar Apr 19 '21 06:04 ikkoham

@LaurinFischer thanks a lot for your interesting suggestion here. I believe that expressing the FermionicOp to matrix is important for the general basis in Fock Space (what you described as a basis_general). Restricting to certain number of particles (or spins) will require the use of projectors applied in the general Fermionic Operator that may be a bit of overhead here. @ikkoham I think that the basis general expression to matrix should be straightforward to implement. What do you think?

pbark avatar Apr 20 '21 09:04 pbark

Okay so in summary, I think what we want to implement is:

  • [x] Hermiticity checks for all SecondQuantizedOps SecondQuantizedOp.is_hermite() -> bool

  • [x] A way to compute commutators such as commutator(op1: SecondQuantizedOp, op2: SecondQuantizedOp).is_zero()

  • [x] .to_matrix()-method for FermionicOp over the full Fock basis

  • [x] scipy.sparse representations of the matrices for FermionicOp and SpinOp, e.g. with parameter to_matrix(sparse: bool)

After an offline discussion with @ikkoham I think I could take care of these (this would also be a good learning opportunity for me to get into the workflow). Should I open a new issue for each of these points or can we use this issue to keep track of the changes?

LaurinFischer avatar Apr 30 '21 11:04 LaurinFischer

Hi @LaurinFischer, thanks! I think the best would be if I make this to an epic and you add the related issues. Then we chat with @ikkoham and depending on your bandwidth we can assign the issues to the upcoming sprints.

pbark avatar Apr 30 '21 12:04 pbark

#858 added commutator utilities, the last suggestion missing from these feature suggestions :+1:

mrossinek avatar Oct 31 '22 17:10 mrossinek