Prinsip desain representasi operator
Panduan ini menjelaskan prinsip desain umum dan konsep inti yang dipakai bersama oleh semua representasi operator di modul operators.
Ikhtisar
Representasi operator yang disediakan modul ini punya beberapa prinsip desain dasar yang sama:
-
Struktur data jarang (sparse): Operator hanya menyimpan operasi non-identitas. Tata letak data internalnya umumnya terinspirasi dari format data matriks jarang, sehingga penyimpanan dan komputasi efisien untuk sistem dengan banyak mode tapi sedikit kontribusi signifikan.
-
Iterasi dan rekonstruksi suku: Terlepas dari penyimpanan jarang internalnya, operator menyediakan antarmuka iterasi yang konsisten, jadi kamu bisa memeriksa, menyaring, dan mentransformasi suku tanpa perlu memahami struktur data di baliknya, lalu merekonstruksi operator baru dari suku yang sudah dimodifikasi.
-
Pengindeksan berbasis mode: Operator memakai indeks mode abstrak untuk melabeli derajat kebebasan fermionik, sehingga pemetaan dari sistem fisis ke representasi operator fleksibel.
-
Pengelompokan suku dan hubungan komutasi: Operator mendukung secara native informasi pengelompokan yang mengaitkan suku dengan indeks grup. Ini memungkinkan optimasi dan pelestarian struktur fisis tanpa struktur data terpisah. Lihat panduan pengelompokan untuk penggunaan praktisnya.
-
Operasi aritmetika dan matematika: Semua operator mengimplementasikan set operasi aritmetika yang konsisten (seperti penjumlahan, perkalian, dan komposisi) dan fungsi matematika dengan memakai protokol
OperatorTrait, sehingga kode seragam di berbagai tipe operator. -
Pengurutan suku operator dan bentuk normal: Operator yang ekuivalen secara matematis bisa punya representasi dan perilaku yang sangat berbeda dalam algoritma kuantum. Semua representasi operator mendukung berbagai bentuk normal (berdasarkan relasi komutasi khusus aljabar) untuk mendapatkan representasi operator yang kanonik, dapat diprediksi, dan bisa dioptimalkan.
Struktur data jarang
Semua operator memakai representasi jarang yang hanya melacak operasi non-identitas. Setiap operasi non-identitas terdiri dari sebuah koefisien (bilangan kompleks) dan urutan aksi pada mode tertentu.
Pendekatan ini sangat mengurangi pemakaian memori dan waktu komputasi, terutama untuk sistem dengan banyak mode tapi sedikit kontribusi signifikan. Dengan hanya menyimpan operasi non-identitas, operasi fokus pada hal yang penting, sehingga sistem besar yang mustahil dengan representasi padat jadi bisa dikerjakan. Selain itu, operator secara alami bisa diskalakan ke jumlah mode berapa pun; operator yang bekerja pada mode {0, 1} tetap berfungsi tanpa perubahan di sistem dengan jauh lebih banyak mode karena mode yang tidak terpengaruh secara implisit adalah identitas.
Suku yang identik dipertahankan terpisah selama operasi aritmetika dan harus digabungkan secara eksplisit kalau perlu.
Format penyimpanan internal
Secara internal, operator disimpan dalam array yang terinspirasi dari format data matriks jarang:
-
Array koefisien: Koefisien kompleks untuk setiap suku
-
Array indeks mode: Mode fermionik yang menjadi sasaran setiap aksi
-
Array batas: Indeks yang menandai di mana mode setiap suku dimulai dan berakhir di array mode
Array tambahan mungkin ada tergantung tipe operator. Misalnya, instans FermionOperator menyertakan array aksi berisi boolean yang menentukan tipe aksi fermionik pada indeks mode yang bersangkutan. Sebaliknya, kelas MajoranaOperator tidak butuh pembedaan ini karena menyandikan informasi itu dalam paritas indeks mode. Lihat dokumentasi API untuk tipe operatormu untuk memahami format penyimpanan lengkapnya.
Contoh berikut menunjukkan bagaimana array-array ini disusun. Pertama adalah konstruksi langsung memakai array jarang:
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[0, 1],
... boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[2, 3],
... boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)
#include <qiskit_fermions.h>
// Construct first operator: 1.0 * +0 -1
QkComplex64 coeff1[1] = {{1.0, 0.0}};
bool actions1[2] = {true, false};
uint32_t modes1[2] = {0, 1};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, actions1, modes1, boundaries1);
// Construct second operator: 1.0 * +2 -3
QkComplex64 coeff2[1] = {{1.0, 0.0}};
bool actions2[2] = {true, false};
uint32_t modes2[2] = {2, 3};
uint32_t boundaries2[2] = {0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(1, 2, coeff2, actions2, modes2, boundaries2);
// Combine the sparse operators. There is no in-place add in the C API, so
// the sum is a new operator that the caller owns.
QfFermionOperator *sum = qf_ferm_op_add(op1, op2);
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(sum);
Metode konstruksi yang praktis
Buat developer Python, tersedia beberapa metode konstruksi praktis yang menyembunyikan detail penyimpanan sparse. Dengan metode ini, kamu bisa membangun operator tanpa perlu repot mengelola array koefisien, mode, dan boundary:
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)
// The C API uses direct array construction; convenience methods are not available.
Implementasi operator tertentu mungkin mendukung metode konstruksi tambahan yang cocok untuk kasus penggunaannya masing-masing. Cek dokumentasi API untuk tipe operator kamu buat melihat semua opsi konstruksi yang tersedia.
Iterasi term dan rekonstruksi
Operator menyediakan antarmuka iterasi yang konsisten lewat OperatorTrait.iter_terms(), apa pun representasi sparse internalnya. Jadi, kamu bisa memeriksa, memfilter, atau mengubah term tanpa perlu paham struktur data di baliknya. Setelah itu, kamu bisa merekonstruksi operator baru dari term yang sudah diubah dengan OperatorTrait.from_terms().
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
... (): 0.5, # constant term (order 0)
... (cre(0), ann(1)): 1.0, # two-body term (order 2)
... (cre(0), cre(1), ann(1), ann(0)): 0.25 # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
... (term, coeff) for term, coeff in op.iter_terms()
... if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term
// WARNING: Term iteration and filtering are not yet available in the C API.
Pengindeksan berbasis mode
Semua representasi operator menyebut indeks yang menjadi sasaran term-nya sebagai mode. Mode itu cuma sebuah indeks yang mengidentifikasi satu derajat kebebasan fermionik di sistem kamu. Pemetaan dari derajat kebebasan fisik (seperti orbital spasial, keadaan spin, atau bilangan kuantum lainnya) ke indeks mode diserahkan ke pengguna, jadi fleksibilitasnya maksimal.
Abstraksi ini juga ada di modul qiskit_fermions.circuit, tempat FermionicCircuit beroperasi pada sebuah register mode fermionik. Baik di representasi operator maupun circuit, mode menyediakan cara yang konsisten dan tidak bergantung pada aljabar untuk menentukan derajat kebebasan mana yang terlibat dalam suatu operasi.
Implementasi saat ini memakai mode tanpa spin: Semua representasi operator yang saat ini disediakan modul ini memperlakukan mode sebagai derajat kebebasan fermionik tanpa spin. Artinya, kalau sistem kamu punya elektron atau fermion dengan spin-up dan spin-down, kamu harus memetakannya secara eksplisit ke mode yang berbeda (misalnya mode 0-3 untuk spin-up dari empat orbital spasial, mode 4-7 untuk spin-down, atau konvensi lain yang kamu pilih).
Desain ini menjaga representasi inti tetap sederhana dan umum, sekaligus menghindari pemaksaan konvensi urutan spin tertentu. Modul utilitas seperti qiskit_fermions.operators.library menyediakan fungsi praktis (misalnya FCIDump.from_file()) yang menangani pemetaan semacam itu buat kamu saat memuat data struktur elektronik.
Seiring berkembangnya paket ini, representasi operator dengan spin mungkin akan ditambahkan, yang mendukung derajat kebebasan spin secara native di model datanya. Representasi itu akan dibedakan dengan jelas dari implementasi tanpa spin yang ada sekarang dan akan hidup berdampingan dengannya di modul ini.
Pengelompokan term dan relasi komutasi
Seperti koefisien dan indeks mode, operator bisa menyimpan array groups secara opsional yang mengaitkan setiap term dengan sebuah indeks grup. Dengan memasukkan pengelompokan langsung ke representasi operator sebagai bagian dari struktur data sparse, informasi pengelompokan otomatis ikut terbawa bersama operator lewat berbagai transformasi. Ini memungkinkan pemakaian struktur secara sistematis, entah berasal dari properti fisik, relasi aljabar, atau simetri khusus masalah. Informasi terstruktur itu lalu bisa dipakai di operasi lanjutan seperti sintesis dan dekomposisi circuit dengan metode seperti OperatorTrait.split_out_groups().
Untuk panduan lengkap soal cara mengelompokkan term operator di workflow kamu, lihat panduan grouping.
- PYTHON
- C
>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
... (gamma(0, False),): 1.0,
... (gamma(1, False),): 1.0,
... (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()
#include <qiskit_fermions.h>
// Create operator with 3 terms
QkComplex64 coeffs[3] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 2, 3};
uint32_t boundaries[4] = {0, 1, 2, 4};
QfMajoranaOperator *op = qf_maj_op_new(3, 4, coeffs, modes, boundaries);
// Assign grouping information
uint32_t groups[3] = {0, 0, 1};
qf_maj_op_set_groups(op, groups, 3);
// Partition operator by groups
QfMajoranaOperator *grouped_ops[2];
qf_maj_op_split_out_groups(op, NULL, 0, grouped_ops);
Operasi aritmetika dan matematika
Semua operator mengimplementasikan protokol OperatorTrait, yang menyediakan kumpulan operasi terpadu untuk berbagai tipe operator. Jadi, kode yang ditulis untuk satu representasi operator juga bekerja sama persis dengan yang lain. Lihat qiskit_fermions.protocols untuk mengetahui hubungan protokol ini dengan protokol lain yang didefinisikan paket ini.
Protokol ini mencakup operasi aritmetika (seperti penjumlahan, perkalian, dan komposisi), operasi struktural (iterasi term, analisis dukungan mode, relabeling), fungsi matematika (normal ordering, penyederhanaan, pengecekan ekuivalensi), dan lainnya. Lihat dokumentasi OperatorTrait untuk referensi lengkap semua operasi yang tersedia.
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
... (cre(0), ann(1)): 1.0,
... (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian
>>> print(f"Operator is Hermitian: {op.is_hermitian(atol=1e-10)}")
Operator is Hermitian: True
#include <qiskit_fermions.h>
// Construct a Hermitian operator: H = +0 -1 + +1 -0
QkComplex64 coeffs[2] = {{1.0, 0.0}, {1.0, 0.0}};
bool actions[4] = {true, false, true, false};
uint32_t modes[4] = {0, 1, 1, 0};
uint32_t boundaries[3] = {0, 2, 4};
QfFermionOperator *op = qf_ferm_op_new(2, 4, coeffs, actions, modes, boundaries);
// Check if the operator is Hermitian
bool is_hermitian = qf_ferm_op_is_hermitian(op, 1e-10);
printf("Operator is Hermitian: %s\n", is_hermitian ? "true" : "false");
// Clean up
qf_ferm_op_free(op);
is_hermitian() memverifikasi bahwa dengan mereduksi selisih itu ke bentuk normal lalu membandingkannya dengan zero(). Kalau dijabarkan dengan operasi protokol lainnya, langkah yang dilakukan adalah sebagai berikut:
>>> difference = (op - op.adjoint()).normal_ordered().simplify(atol=1e-10)
>>> difference.equiv(FermionOperator.zero(), atol=1e-10)
True
Seberapa kuat pengecekan ini bergantung pada tipe operatornya, karena itu tergantung seberapa lengkap bentuk normal tipe tersebut mengontraksi generator yang bersebelahan. Hasil True selalu bisa diandalkan, tapi untuk beberapa tipe, hasil False bersifat konservatif: lihat is_hermitian() untuk detailnya.
Contoh ini memakai atol=1e-10. Parameter atol (toleransi absolut) menentukan sebuah ambang batas. Koefisien dengan magnitudo lebih kecil dari atol dianggap nol dan dibuang. Ini penting buat stabilitas numerik saat membandingkan operator, karena aritmetika floating-point bisa menimbulkan galat pembulatan kecil yang kalau tidak ditangani akan membuat operator yang ekuivalen tidak dikenali sebagai ekuivalen.
Meskipun protokol OperatorTrait menyediakan antarmuka umum, implementasi operator tertentu mungkin menawarkan metode praktis tambahan yang bukan bagian dari protokol. Misalnya, FermionOperator dan MajoranaOperator menyediakan metode max_rank() yang melaporkan panjang term terpanjang mereka. Selalu cek dokumentasi API untuk tipe operator kamu supaya tahu semua fungsionalitas yang tersedia.
Pengurutan term operator dan bentuk normal
Tantangan mendasar dalam aljabar operator kuantum adalah operator yang secara matematis ekuivalen bisa direpresentasikan dalam banyak cara berbeda, masing-masing dengan implikasi berbeda untuk algoritma kuantum. Operator yang sama bisa ditulis dalam bentuk yang ekuivalen secara aljabar (misalnya, bisa dinyatakan sebagai ), tapi representasi-representasi ini menghasilkan perilaku yang berbeda dalam sintesis circuit, penyederhanaan, dan algoritma numerik.
Representasi operator mendukung operasi normal ordering yang mengubah operator ke bentuk kanonik khusus aljabar dengan memakai relasi komutasi. Ini memungkinkan perbandingan yang andal (dua operator ekuivalen punya bentuk ternormal-urutkan yang identik), mengungkap penyederhanaan (relasi komutasi membuat term saling meniadakan atau bergabung), dan mendukung algoritma yang membutuhkan bentuk operator tertentu demi kebenaran dan efisiensi.
Representasi operator yang berbeda mungkin mengimplementasikan normal ordering berdasarkan relasi komutasi berbeda yang sesuai dengan aljabarnya. Misalnya, normal ordering fermionik memakai relasi antikomutasi (), sedangkan normal ordering Majorana memakai konvensi aljabar yang berbeda (). Selalu cek dokumentasi untuk tipe operator kamu supaya paham bagaimana normal ordering diimplementasikan.
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True
#include <qiskit_fermions.h>
#include <stdbool.h>
// Two different representations of the same operator
QkComplex64 coeff1[1] = {{1.0, 0.0}};
bool actions1[2] = {false, true};
uint32_t modes1[2] = {0, 0};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, actions1, modes1, boundaries1);
QkComplex64 coeff2[2] = {{1.0, 0.0}, {-1.0, 0.0}};
bool actions2[2] = {true, false};
uint32_t modes2[2] = {0, 0};
uint32_t boundaries2[3] = {0, 0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(2, 2, coeff2, actions2, modes2, boundaries2);
// Direct comparison fails due to different forms
bool equiv_before = qf_ferm_op_equiv(op1, op2, 1e-10);
printf("Equivalent before normal ordering: %s\n", equiv_before ? "true" : "false");
// Normal-order both and compare again
QfFermionOperator *op1_normal = qf_ferm_op_normal_ordered(op1, NULL);
QfFermionOperator *op2_normal = qf_ferm_op_normal_ordered(op2, NULL);
bool equiv_after = qf_ferm_op_equiv(op1_normal, op2_normal, 1e-10);
printf("Equivalent after normal ordering: %s\n", equiv_after ? "true" : "false");
// Clean up
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(op1_normal);
qf_ferm_op_free(op2_normal);
Metode protokol OperatorTrait.normal_ordered() sengaja tidak menentukan argumen posisional dan keyword-nya, sehingga implementasi operator konkret bisa mendefinisikan parameter yang dapat diatur untuk mengontrol bentuk kanonik persisnya. Ini memungkinkan optimasi khusus operator dan varian bentuk normal yang disesuaikan dengan aljabar atau kasus penggunaannya. Cek dokumentasi API untuk tipe operator kamu buat melihat parameter apa saja yang tersedia.