To't Hauptinholt springen

Inföhrung in Brökdeel-Gates

Verbruuksschattung: ünnerto 30 Sekunnen op'n Heron r2 Prozessor (ANMARKEN: Dit is bloots en Schattung. Dien Looptiet kann anners ween.)

Achtergrund

Brökdeel-Gates op IBM QPUs

Brökdeel-Gates sünd parameteriseerde Quantengates de dat direkten Utföhren vun willküerlich-Winkel-Dreihen mööglich maakt (binnen bestimmte Grenzen), un sodoende is dat nich nödig ehr in mehrere Basisgates to oplösen. Dör dat Nutzen vun de nativen Wechselwirken twüschen de physikalischen Qubits künnt Brukers bestimmte Unitäre effizienter op Hardware implementeern.

IBM Quantum® Heron QPUs ünnerstütt de folgenden Brökdeel-Gates:

  • RZZ(θ)R_{ZZ}(\theta) för 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) för all reelle Weerte θ\theta

Disse Gates künnt sowohl de Deepde as ok de Duur vun Quantenschaltkreisen bedüdend verminnern. Se sünd besünners vörteilhaft in Anwendungen de stark op RZZR_{ZZ} un RXR_X buut, so as Hamiltonsche Simulatschoon, den Quantum Approximate Optimization Algorithm (QAOA), un Quantenkernel-Methoden. In dit Tutorial konzentreert wi uns op den Quantenkernel as praktisch Biespeel.

Inschränkungen

Brökdeel-Gates sünd to'n jetzigen Tietpunkt en experimentell Funkschoon un kamen mit en paar Inschränkungen:

Brökdeel-Gates bruukt en annern Workflow as den Standardansatz. Dit Tutorial verklort wo wi mit Brökdeel-Gates dör en praktisch Anwendung arbeidt.

Kiek mol na de folgenden för mehr Details över Brökdeel-Gates.

Överblick

De Workflow för dat Bruken vun Brökdeel-Gates folgt generell den Qiskit patterns Workflow. De wesentliche Ünnerscheed is dat all RZZ Winkel de Inschränkung 0<θπ/20 < \theta \leq \pi/2 erfüllen mööt. Dat gifft twee Ansätze üm sicher to stellen dat disse Bedingung erfüllt warrt. Dit Tutorial konzentreert sik op un empfehlt den tweeten Ansatz.

# Added by doQumentation — installs packages not in the Binder environment
!pip install -q qiskit-basis-constructor

1. Parameterweerte genereren de de RZZ Winkelinschränkung erfüllt

Wenn du seker büst dat all RZZ Winkel in'n gülligen Bereich liggt, kannst du den Standardworkflow vun Qiskit patterns folgen. In dissen Fall gifftst du eenfach de Parameterweerte as Deel vun'n PUB an. De Workflow löppt so:

pm = generate_preset_pass_manager(backend=backend, ...)
t_circuit = pm.run(circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit, parameter_values)])
estimator.run([(t_circuit, t_observable, parameter_values)])

Wenn du versöchst en PUB antogeven dat en RZZ Gate mit'n Winkel buten den gülligen Bereich enthölt, kriggst du en Fehlermeldung so as:

'The instruction rzz is supported only for angles in the range [0, pi/2], but an angle (20.0) outside of this range has been requested; via parameter value(s) γ[0]=10.0, substituted in parameter expression 2.0*γ[0].'

Üm dissen Fehler to vermieden, schullst du den tweeten Ansatz bedenken de ünnen beschreven warrt.

2. Parameterweerte to Schaltkreisen toweisen vör de Transpileren

Dat qiskit-ibm-runtime Paket stellt en spezialiseerden Transpiler-Pass bereet de FoldRzzAngle heet. Disse Pass transformeert Quantenschaltkreisen so dat all RZZ Winkel mit de RZZ Winkelinschränkung överenstimmt. Wenn du dat Backend an generate_preset_pass_manager oder transpile gifftst, wennt Qiskit automaatsch FoldRzzAngle op de Quantenschaltkreisen an. Dat verlanggt vun di Parameterweerte to Quantenschaltkreisen vör dat Transpileren totoweisen. De Workflow löppt so:

pm = generate_preset_pass_manager(backend=backend, ...)
b_circuit = circuit.assign_parameters(parameter_values)
t_circuit = pm.run(b_circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit,)])
estimator.run([(t_circuit, t_observable)])

Markt dat disse Workflow mehr Berekenkostn verursaakt as den eersten Ansatz, wieldat dat Parameterweerte to Quantenschaltkreisen toweisen un dat Spiekern vun de parameterbunnen Schaltkreisen lokal insluten deit. Tosätzlich gifft dat en bekanntet Problem in Qiskit wo de Transformatschoon vun RZZ Gates in bestimmte Szenarien fehlslaan kann. För en Lösung kiek mal na den Troubleshooting Afsnitt. Dit Tutorial wiest wo wi Brökdeel-Gates över den tweeten Ansatz bruukt, dör en Biespeel dat vun de Quantenkernel-Methode inspireert is. Üm beter to verstahn wo Quantenkernels wohrschienlich nützlich sünd, empfehlt wi dat Lesen vun Liu, Arunachalam & Temme (2021).

Du kannst ok dat Quantum kernel training Tutorial un de Quantum kernels Lessen in'n Quantum machine learning Kurs op IBM Quantum Learning dörcharbeiten.

Anforderungen

Bevör du mit dit Tutorial anfängst, stell seker dat du dat Folgende installeert hest:

  • Qiskit SDK v2.0 oder später, mit visualization Ünnerstüttung
  • Qiskit Runtime v0.37 oder später (pip install qiskit-ibm-runtime)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

Oprüsten

import matplotlib.pyplot as plt
import numpy as np
from qiskit import QuantumCircuit, generate_preset_pass_manager
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import unitary_overlap
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2

Brökdeel-Gates aktiveren un Basisgates överprööven

Üm Brökdeel-Gates to bruken, kannst du en Backend kriegen dat ehr ünnerstütt, indeem du de use_fractional_gates=True Optschoon settst. Wenn dat Backend Brökdeel-Gates ünnerstütt, seehst du rzz un rx ünner sien Basisgates opgelist.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
) # backend should be a heron device or later
backend_name = backend.name
backend_c = service.backend(backend_name) # w/o fractional gates
backend_f = service.backend(
backend_name, use_fractional_gates=True
) # w/ fractional gates
print(f"Backend: {backend_name}")
print(f"No fractional gates: {backend_c.basis_gates}")
print(f"With fractional gates: {backend_f.basis_gates}")
if "rzz" not in backend_f.basis_gates:
print(f"Backend {backend_name} does not support fractional gates")
Backend: ibm_fez
No fractional gates: ['cz', 'id', 'rz', 'sx', 'x']
With fractional gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x']

Workflow mit Brökdeel-Gates

Schritt 1: Klassische Ingaven op Quantenproblem afbilden

Quantenkernel Schaltkreis

In dissen Afsnitt ünnersöcht wi den Quantenkernel Schaltkreis mit RZZ Gates üm den Workflow för Brökdeel-Gates vörtostellen.

Wi fangt an indeem wi en Quantenschaltkreis buut üm einzelne Indrääg vun de Kernel-Matrix to berekenen. Dat warrt daan indeem wi ZZ Feature-Map Schaltkreisen mit en unitary overlap kombineert. De Kernel-Funkschoon nimmt Vektoren in'n feature-mapped Ruum un gifft ehr inner Produkt as Indraag vun de Kernel-Matrix torüch: K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, wo Φ(x)|\Phi(x)\rangle den feature-mapped Quantentostann darstellt.

Wi buut manuell en ZZ Feature-Map Schaltkreis mit RZZ Gates. Obwohl Qiskit en inbuuten zz_feature_map bereedstellt, ünnerstütt dat to'n jetzigen Tietpunkt keen RZZ Gates as vun Qiskit v2.0.2 (kiek na't Issue).

As nächstet berekent wi de Kernel-Funkschoon för identische Ingaven - to'n Biespeel, K(x,x)=1K(x, x) = 1. Op ruschige Quantencomputers kann disse Weert weniger as 1 ween wegen Ruschen. En Resultaat neger an 1 bedüüdt weniger Ruschen bi de Utföhren. In dit Tutorial nöömt wi dissen Weert de Fidelity, defineert as fidelity=K(x,x).\text{fidelity} = K(x, x).

optimization_level = 2
shots = 2000
reps = 3
rng = np.random.default_rng(seed=123)
def my_zz_feature_map(num_qubits: int, reps: int = 1) -> QuantumCircuit:
x = ParameterVector("x", num_qubits * reps)
qc = QuantumCircuit(num_qubits)
qc.h(range(num_qubits))
for k in range(reps):
K = k * num_qubits
for i in range(num_qubits):
qc.rz(x[i + K], i)
pairs = [(i, i + 1) for i in range(num_qubits - 1)]
for i, j in pairs[0::2] + pairs[1::2]:
qc.rzz((np.pi - x[i + K]) * (np.pi - x[j + K]), i, j)
return qc

def quantum_kernel(num_qubits: int, reps: int = 1) -> QuantumCircuit:
qc = my_zz_feature_map(num_qubits, reps=reps)
inner_product = unitary_overlap(qc, qc, "x", "y", insert_barrier=True)
inner_product.measure_all()
return inner_product

def random_parameters(inner_product: QuantumCircuit) -> np.ndarray:
return np.tile(rng.random(inner_product.num_parameters // 2), 2)

def fidelity(result) -> float:
ba = result.data.meas
return ba.get_int_counts().get(0, 0) / ba.num_shots

Quantenkernel Schaltkreisen un ehr topassende Parameterweerte warrt för Systemen mit 4 bet 40 Qubits genereert, un ehr Fidelities warrt dorna bewertet.

qubits = list(range(4, 44, 4))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]

De Veer-Qubit Schaltkreis warrt ünnen visualiseert.

circuits[0].draw("mpl", fold=-1)

Output of the previous code cell

In'n Standard Qiskit patterns Workflow warrt Parameterweerte normalerwies as Deel vun'n PUB an't Sampler oder Estimator Primitive övergeven. Wenn wi aver en Backend bruukt dat Brökdeel-Gates ünnerstütt, mööt disse Parameterweerte explizit vör dat Transpileren to'n Quantenschaltkreis towiest warrn.

b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
b_qc[0].draw("mpl", fold=-1)

Output of the previous code cell

Schritt 2: Problem för Quantenhardware Utföhren optimeren

Wi transpileert denn den Schaltkreis mit den Pass Manager na den Standard Qiskit Pattern. Indeem wi en Backend bereedstellt dat Brökdeel-Gates ünnerstütt an generate_preset_pass_manager, warrt en spezialiseerden Pass nömmt FoldRzzAngle automaatsch inföögt. Disse Pass modifizeert den Schaltkreis üm mit de RZZ Winkelinschränkungen överento stimmen. As Resultaat warrt RZZ Gates mit negative Weerte in't vörige Bild in positive Weerte transformeert, un en paar tosätzliche X Gates warrt inföögt.

backend_f = service.backend(name=backend_name, use_fractional_gates=True)
# pm_f includes `FoldRzzAngle` pass
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_f
)
t_qc_f = pm_f.run(b_qc)
print(t_qc_f[0].count_ops())
t_qc_f[0].draw("mpl", fold=-1)
OrderedDict([('rz', 35), ('rzz', 18), ('x', 13), ('rx', 9), ('measure', 4), ('barrier', 2)])

Output of the previous code cell

Üm den Infloot vun Brökdeel-Gates to bewerten, evalueert wi de Tahl vun Nich-Lokal Gates (CZ un RZZ för dit Backend), tosamen mit Schaltkreisdeepdeten un Duern, un vergliekt disse Metriken mit de vun'n Standardworkflow später.

nnl_f = [qc.num_nonlocal_gates() for qc in t_qc_f]
depth_f = [qc.depth() for qc in t_qc_f]
duration_f = [
qc.estimate_duration(backend_f.target, unit="u") for qc in t_qc_f
]

Schritt 3: Mit Qiskit Primitives utföhren

Wi löppt den transpileerden Schaltkreis mit dat Backend dat Brökdeel-Gates ünnerstütt.

sampler_f = SamplerV2(mode=backend_f)
sampler_f.options.dynamical_decoupling.enable = True
sampler_f.options.dynamical_decoupling.sequence_type = "XY4"
sampler_f.options.dynamical_decoupling.skip_reset_qubits = True
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())
d4bninsi51bc738j97eg

Schritt 4: Nabearbeiten un Resultaat in't wünschte klassische Format torüchgeven

Du kannst den Kernel-Funkschoonweert K(x,x)K(x, x) kriegen indeem du de Wohrschienlichkeit vun't all-null Bitstring 00...00 in'n Output meetst.

# job = service.job("d1obougt0npc73flhiag")
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)
usage_f = job.usage()
[0.9005, 0.647, 0.3345, 0.355, 0.3315, 0.174, 0.1875, 0.149, 0.1175, 0.085]

Verglieken vun Workflow un Schaltkreis ahn Brökdeel-Gates

In dissen Afsnitt präsenteert wi den Standard Qiskit patterns Workflow mit en Backend dat keen Brökdeel-Gates ünnerstütt. Indeem du de transpileerden Schaltkreisen verglieckst, warrst du marken dat de Versjoon mit Brökdeel-Gates (vun't vörige Afsnitt) kompakter is as de ahn Brökdeel-Gates.

# step 1: map classical inputs to quantum problem
# `circuits` and `params` from the previous section are reused here
# step 2: optimize circuits
backend_c = service.backend(backend_name) # w/o fractional gates
pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc_c = pm_c.run(circuits)
print(t_qc_c[0].count_ops())
t_qc_c[0].draw("mpl", fold=-1)
OrderedDict([('rz', 130), ('sx', 80), ('cz', 36), ('measure', 4), ('barrier', 2)])

Output of the previous code cell

nnl_c = [qc.num_nonlocal_gates() for qc in t_qc_c]
depth_c = [qc.depth() for qc in t_qc_c]
duration_c = [
qc.estimate_duration(backend_c.target, unit="u") for qc in t_qc_c
]
# step 3: execute
sampler_c = SamplerV2(backend_c)
sampler_c.options.dynamical_decoupling.enable = True
sampler_c.options.dynamical_decoupling.sequence_type = "XY4"
sampler_c.options.dynamical_decoupling.skip_reset_qubits = True
job = sampler_c.run(pubs=zip(t_qc_c, params), shots=shots)
print(job.job_id())
d4bnirvnmdfs73ae3a2g
# step 4: post-processing
# job = service.job("d1obp8j3rr0s73bg4810")
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)
usage_c = job.usage()
[0.6675, 0.5725, 0.098, 0.102, 0.065, 0.0235, 0.006, 0.0015, 0.0015, 0.002]

Verglieken vun Deepdeten un Fidelities

In dissen Afsnitt vergliekt wi de Tahl vun Nich-Lokal Gates un de Fidelities twüschen Schaltkreisen mit un ahn Brökdeel-Gates. Dat hövt de potenziellen Vördeele vun't Bruken vun Brökdeel-Gates mit Blick op Utföhrenseffizienz un Qualität rut.

plt.plot(qubits, depth_c, "-o", label="no fractional gates")
plt.plot(qubits, depth_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("depth")
plt.title("Comparison of depths")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bcaac50>

Output of the previous code cell

plt.plot(qubits, duration_c, "-o", label="no fractional gates")
plt.plot(qubits, duration_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("duration (µs)")
plt.title("Comparison of durations")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bdef310>

Output of the previous code cell

plt.plot(qubits, nnl_c, "-o", label="no fractional gates")
plt.plot(qubits, nnl_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("number of non-local gates")
plt.title("Comparison of numbers of non-local gates")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12be8ac90>

Output of the previous code cell

plt.plot(qubits, fidelity_c, "-o", label="no fractional gates")
plt.plot(qubits, fidelity_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("fidelity")
plt.title("Comparison of fidelities")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bea8290>

Output of the previous code cell

Wi vergliekt de QPU Verbruukstiet mit un ahn Brökdeel-Gates. De Resultaten in de folgende Zell wiest dat de QPU Verbruukstieten fast identisch sünd.

print(f"no fractional gates: {usage_c} seconds")
print(f"fractional gates: {usage_f} seconds")
no fractional gates: 7 seconds
fractional gates: 7 seconds

Vörankamen Thema: Bloots fraktionell RX Gates bruken

De Nootwendigkeit för den modifizeerden Workflow bi't Bruken vun Brökdeel-Gates kummt hauptsächlich vun de Inschränkung op RZZ Gate Winkel. Wenn du aver bloots de fraktionell RX Gates bruukst un de fraktionell RZZ Gates utslutst, kannst du wieder den Standard Qiskit patterns Workflow folgen. Disse Ansatz kann trochdem bedüdende Vördeele bringen, besünners in Schaltkreisen de en grote Tahl vun RX Gates un U Gates inthölt, indeem dat de Gesamttahl vun Gates verminnert un möglicherwise de Performance verbetert. In dissen Afsnitt wiest wi wo du dien Schaltkreisen optimeerst mit bloots fraktionelle RX Gates, wieldat du RZZ Gates utslutst.

Üm dat to ünnerstütten, stellt wi en Helperfunkschoon bereed de dat di erlöövt en bestimmten Basisgate in en Target Objekt to deaktiveren. Hier bruukt wi dat üm RZZ Gates to deaktiveren.

from qiskit.circuit.library import n_local
from qiskit.transpiler import Target
def remove_instruction_from_target(target: Target, gate_name: str) -> Target:
new_target = Target(
description=target.description,
num_qubits=target.num_qubits,
dt=target.dt,
granularity=target.granularity,
min_length=target.min_length,
pulse_alignment=target.pulse_alignment,
acquire_alignment=target.acquire_alignment,
qubit_properties=target.qubit_properties,
concurrent_measurements=target.concurrent_measurements,
)

for name, qarg_map in target.items():
if name == gate_name:
continue
instruction = target.operation_from_name(name)
if qarg_map == {None: None}:
qarg_map = None
new_target.add_instruction(instruction, qarg_map, name=name)
return new_target

Wi bruukt en Schaltkreis de ut U, CZ, un RZZ Gates besteiht as Biespeel.

qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")

Output of the previous code cell

Wi transpileert toeerst den Schaltkreis för en Backend dat keen Brökdeel-Gates ünnerstütt.

pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc = pm_c.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 23), ('sx', 16), ('cz', 4)])

Output of the previous code cell

Denn transpileert wi densuven Schaltkreis mit fraktionelle RX Gates, wieldat wi RZZ Gates utslött. Dat föhrt to en lütte Verminnern in de Gesamtgatetahl, dank de effizienter Implementatschoon vun de RX Gates.

backend_f = service.backend(backend_name, use_fractional_gates=True)
target = remove_instruction_from_target(backend_f.target, "rzz")
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 22), ('sx', 14), ('cz', 4), ('rx', 1)])

Output of the previous code cell

U Gates mit fraktionelle RX Gates optimeren

In dissen Afsnitt wiest wi wo wi U Gates mit fraktionelle RX Gates optimeert, upbuut op densuven Schaltkreis de in't vörige Afsnitt vörstellt wurrn is.

Du muttst dat qiskit-basis-constructor Paket för dissen Afsnitt installeern. Dat is en Beta-Versjoon vun'n nien Transpileren-Plugin för Qiskit, de möglicherwise in de Tokumst in Qiskit integreert warrt.

# %pip install qiskit-basis-constructor
from qiskit.circuit.library import UGate
from qiskit_basis_constructor import DEFAULT_EQUIVALENCE_LIBRARY

Wi transpileert den Schaltkreis bloots mit fraktionelle RX Gates, un slött RZZ Gates ut. Indeem wi en brukerdefineerte Oplösungsregel inföögt, so as ünnen wiest, künnt wi de Tahl vun Single-Qubit Gates verminnern de nödig sünd üm en U Gate to implementeren.

Disse Funkschoon warrt to'n jetzigen Tietpunkt in dit GitHub Issue diskuteert.

# special decomposition rule for UGate
x = ParameterVector("x", 3)
zxz = QuantumCircuit(1)
zxz.rz(x[2] - np.pi / 2, 0)
zxz.rx(x[0], 0)
zxz.rz(x[1] + np.pi / 2, 0)
DEFAULT_EQUIVALENCE_LIBRARY.add_equivalence(UGate(x[0], x[1], x[2]), zxz)

As nächstet wennt wi den Transpiler an mit constructor-beta Översetten bereedstellt vun't qiskit-basis-constructor Paket. As Resultaat warrt de Gesamttahl vun Gates verminnert verglichen mit de vörige Transpileren.

pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
translation_method="constructor-beta",
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 16), ('rx', 9), ('cz', 4)])

Output of the previous code cell

Troubleshooting

Problem: Ungüllige RZZ Winkel künnt na dat Transpileren blieven

As vun Qiskit v2.0.3 gifft dat bekannte Problemen wo RZZ Gates mit ungüllige Winkel in de Schaltkreisen blieven künnt ok na dat Transpileren. Dat Problem tritt normalerwies ünner de folgenden Bedingungen op.

Fehlslaan wenn du de target Optschoon mit generate_preset_pass_manager oder transpiler bruukst

Wenn de target Optschoon mit generate_preset_pass_manager oder transpiler bruukt warrt, warrt de spezialiseerde Transpiler-Pass FoldRzzAngle nich opropen. Üm en korrekte Behandeln vun RZZ Winkel för Brökdeel-Gates sicherto stellen, empfehlt wi alltied de backend Optschoon to bruken. Kiek mal na dit Issue för mehr Details.

Fehlslaan wenn Schaltkreisen bestimmte Gates inthölt

Wenn dien Schaltkreis bestimmte Gates so as XXPlusYYGate inthölt, kann de Qiskit Transpiler RZZ Gates mit ungüllige Winkel genereren. Wenn du dit Problem antriffst, kiek mal na dit GitHub Issue för en Lösung.

Tutorial-Ümfroog

Nimm di doch mal de Tiet för disse korte Ümfroog üm Feedback över dit Tutorial to geven. Dien Innsichten warrt uns helpen unser Inhaltangebot un Brukerfarung to verbettern.

Link to de Ümfroog