Release 0.31.0
New features since last release
Seamlessly create and combine fermionic operators 🔬
-
Fermionic operators and arithmetic are now available. (#4191) (#4195) (#4200) (#4201) (#4209) (#4229) (#4253) (#4255) (#4262) (#4278)
There are a couple of ways to create fermionic operators with this new feature:
-
qml.FermiC
andqml.FermiA
: the fermionic creation and annihilation operators, respectively. These operators are defined by passing the index of the orbital that the fermionic operator acts on. For instance, the operatorsa⁺(0)
anda(3)
are respectively constructed as>>> qml.FermiC(0) a⁺(0) >>> qml.FermiA(3) a(3)
These operators can be composed with (
*
) and linearly combined with (+
and-
) other Fermi operators to create arbitrary fermionic Hamiltonians. Multiplying several Fermi operators together creates an operator that we call a Fermi word:>>> word = qml.FermiC(0) * qml.FermiA(0) * qml.FermiC(3) * qml.FermiA(3) >>> word a⁺(0) a(0) a⁺(3) a(3)
Fermi words can be linearly combined to create a fermionic operator that we call a Fermi sentence:
>>> sentence = 1.2 * word - 0.345 * qml.FermiC(3) * qml.FermiA(3) >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) - 0.345 * a⁺(3) a(3)
-
via qml.fermi.from_string: create a fermionic operator that represents multiple creation and annihilation operators being multiplied by each other (a Fermi word).
>>> qml.fermi.from_string('0+ 1- 0+ 1-') a⁺(0) a(1) a⁺(0) a(1) >>> qml.fermi.from_string('0^ 1 0^ 1') a⁺(0) a(1) a⁺(0) a(1)
Fermi words created with
from_string
can also be linearly combined to create a Fermi sentence:>>> word1 = qml.fermi.from_string('0+ 0- 3+ 3-') >>> word2 = qml.fermi.from_string('3+ 3-') >>> sentence = 1.2 * word1 + 0.345 * word2 >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) + 0.345 * a⁺(3) a(3)
Additionally, any fermionic operator, be it a single fermionic creation/annihilation operator, a Fermi word, or a Fermi sentence, can be mapped to the qubit basis by using qml.jordan_wigner:
>>> qml.jordan_wigner(sentence) ((0.4725+0j)*(Identity(wires=[0]))) + ((-0.4725+0j)*(PauliZ(wires=[3]))) + ((-0.3+0j)*(PauliZ(wires=[0]))) + ((0.3+0j)*(PauliZ(wires=[0]) @ PauliZ(wires=[3])))
Learn how to create fermionic Hamiltonians describing some simple chemical systems by checking out our fermionic operators demo!
-
Workflow-level resource estimation 🧮
-
PennyLane's Tracker now monitors the resource requirements of circuits being executed by the device. (#4045) (#4110)
Suppose we have a workflow that involves executing circuits with different qubit numbers. We can obtain the resource requirements as a function of the number of qubits by executing the workflow with the
Tracker
context:dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(n_wires): for i in range(n_wires): qml.Hadamard(i) return qml.probs(range(n_wires)) with qml.Tracker(dev) as tracker: for i in range(1, 5): circuit(i)
The resource requirements of individual circuits can then be inspected as follows:
>>> resources = tracker.history["resources"] >>> resources[0] wires: 1 gates: 1 depth: 1 shots: Shots(total=None) gate_types: {'Hadamard': 1} gate_sizes: {1: 1} >>> [r.num_wires for r in resources] [1, 2, 3, 4]
Moreover, it is possible to predict the resource requirements without evaluating circuits using the
null.qubit
device, which follows the standard execution pipeline but returns numeric zeros. Consider the following workflow that takes the gradient of a50
-qubit circuit:n_wires = 50 dev = qml.device("null.qubit", wires=n_wires) weight_shape = qml.StronglyEntanglingLayers.shape(2, n_wires) weights = np.random.random(weight_shape, requires_grad=True) @qml.qnode(dev, diff_method="parameter-shift") def circuit(weights): qml.StronglyEntanglingLayers(weights, wires=range(n_wires)) return qml.expval(qml.PauliZ(0)) with qml.Tracker(dev) as tracker: qml.grad(circuit)(weights)
The tracker can be inspected to extract resource requirements without requiring a 50-qubit circuit run:
>>> tracker.totals {'executions': 451, 'batches': 2, 'batch_len': 451} >>> tracker.history["resources"][0] wires: 50 gates: 200 depth: 77 shots: Shots(total=None) gate_types: {'Rot': 100, 'CNOT': 100} gate_sizes: {1: 100, 2: 100}
-
Custom operations can now be constructed that solely define resource requirements — an explicit decomposition or matrix representation is not needed. (#4033)
PennyLane is now able to estimate the total resource requirements of circuits that include one or more of these operations, allowing you to estimate requirements for high-level algorithms composed of abstract subroutines.
These operations can be defined by inheriting from ResourcesOperation and overriding the
resources()
method to return an appropriate Resources object:class CustomOp(qml.resource.ResourcesOperation): def resources(self): n = len(self.wires) r = qml.resource.Resources( num_wires=n, num_gates=n ** 2, depth=5, ) return r
>>> wires = [0, 1, 2] >>> c = CustomOp(wires) >>> c.resources() wires: 3 gates: 9 depth: 5 shots: Shots(total=None) gate_types: {} gate_sizes: {}
A quantum circuit that contains
CustomOp
can be created and inspected using qml.specs:dev = qml.device("default.qubit", wires=wires) @qml.qnode(dev) def circ(): qml.PauliZ(wires=0) CustomOp(wires) return qml.state()
>>> specs = qml.specs(circ)() >>> specs["resources"].depth 6
Community contributions from UnitaryHack 🤝
-
ParametrizedHamiltonian now has an improved string representation. (#4176)
>>> def f1(p, t): return p[0] * jnp.sin(p[1] * t) >>> def f2(p, t): return p * t >>> coeffs = [2., f1, f2] >>> observables = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] >>> qml.dot(coeffs, observables) (2.0*(PauliX(wires=[0]))) + (f1(params_0, t)*(PauliY(wires=[0]))) + (f2(params_1, t)*(PauliZ(wires=[0])))
-
The quantum information module now supports trace distance. (#4181)
Two cases are enabled for calculating the trace distance:
-
A QNode transform via qml.qinfo.trace_distance:
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(param): qml.RY(param, wires=0) qml.CNOT(wires=[0, 1]) return qml.state()
>>> trace_distance_circuit = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[0]) >>> x, y = np.array(0.4), np.array(0.6) >>> trace_distance_circuit((x,), (y,)) 0.047862689546603415
-
Flexible post-processing via qml.math.trace_distance:
>>> rho = np.array([[0.3, 0], [0, 0.7]]) >>> sigma = np.array([[0.5, 0], [0, 0.5]]) >>> qml.math.trace_distance(rho, sigma) 0.19999999999999998
-
-
It is now possible to prepare qutrit basis states with qml.QutritBasisState. (#4185)
wires = range(2) dev = qml.device("default.qutrit", wires=wires) @qml.qnode(dev) def qutrit_circuit(): qml.QutritBasisState([1, 1], wires=wires) qml.TAdd(wires=wires) return qml.probs(wires=1)
>>> qutrit_circuit() array([0., 0., 1.])
-
A new transform called one_qubit_decomposition has been added to provide a unified interface for decompositions of a single-qubit unitary matrix into sequences of X, Y, and Z rotations. All decompositions simplify the rotations angles to be between
0
and4
pi. (#4210) (#4246)>>> from pennylane.transforms import one_qubit_decomposition >>> U = np.array([[-0.28829348-0.78829734j, 0.30364367+0.45085995j], ... [ 0.53396245-0.10177564j, 0.76279558-0.35024096j]]) >>> one_qubit_decomposition(U, 0, "ZYZ") [RZ(tensor(12.32427531, requires_grad=True), wires=[0]), RY(tensor(1.14938178, requires_grad=True), wires=[0]), RZ(tensor(1.73305815, requires_grad=True), wires=[0])] >>> one_qubit_decomposition(U, 0, "XYX", return_global_phase=True) [RX(tensor(10.84535137, requires_grad=True), wires=[0]), RY(tensor(1.39749741, requires_grad=True), wires=[0]), RX(tensor(0.45246584, requires_grad=True), wires=[0]), (0.38469215914523336-0.9230449299422961j)*(Identity(wires=[0]))]
-
The
has_unitary_generator
attribute inqml.ops.qubit.attributes
no longer contains operators with non-unitary generators. (#4183) -
PennyLane Docker builds have been updated to include the latest plugins and interface versions. (#4178)
Extended support for differentiating pulses ⚛️
-
The stochastic parameter-shift gradient method can now be used with hardware-compatible Hamiltonians. (#4132) (#4215)
This new feature generalizes the stochastic parameter-shift gradient transform for pulses (
stoch_pulse_grad
) to support Hermitian generating terms beyond just Pauli words in pulse Hamiltonians, which makes it hardware-compatible. -
A new differentiation method called qml.gradients.pulse_generator is available, which combines classical processing with the parameter-shift rule for multivariate gates to differentiate pulse programs. Access it in your pulse programs by setting
diff_method=qml.gradients.pulse_generator
. (#4160) -
qml.pulse.ParametrizedEvolution
now uses batched compressed sparse row (BCSR
) format. This allows for computing Jacobians of the unitary directly even whendense=False
. (#4126)def U(params): H = jnp.polyval * qml.PauliZ(0) # time dependent Hamiltonian Um = qml.evolve(H, dense=False)(params, t=10.) return qml.matrix(Um) params = jnp.array([[0.5]], dtype=complex) jac = jax.jacobian(U, holomorphic=True)(params)
Broadcasting and other tweaks to Torch and Keras layers 🦾
-
The
TorchLayer
andKerasLayer
integrations withtorch.nn
andKeras
have been upgraded. Consider the followingTorchLayer
:n_qubits = 2 dev = qml.device("default.qubit", wires=n_qubits) @qml.qnode(dev) def qnode(inputs, weights): qml.AngleEmbedding(inputs, wires=range(n_qubits)) qml.BasicEntanglerLayers(weights, wires=range(n_qubits)) return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)] n_layers = 6 weight_shapes = {"weights": (n_layers, n_qubits)} qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)
The following features are now available:
-
Native support for parameter broadcasting. (#4131)
>>> batch_size = 10 >>> inputs = torch.rand((batch_size, n_qubits)) >>> qlayer(inputs) >>> dev.num_executions == 1 True
-
The ability to draw a
TorchLayer
andKerasLayer
usingqml.draw()
andqml.draw_mpl()
. (#4197)>>> print(qml.draw(qlayer, show_matrices=False)(inputs)) 0: ─╭AngleEmbedding(M0)─╭BasicEntanglerLayers(M1)─┤ <Z> 1: ─╰AngleEmbedding(M0)─╰BasicEntanglerLayers(M1)─┤ <Z>
-
Support for
KerasLayer
model saving and clearer instructions onTorchLayer
model saving. (#4149) (#4158)>>> torch.save(qlayer.state_dict(), "weights.pt") # Saving >>> qlayer.load_state_dict(torch.load("weights.pt")) # Loading >>> qlayer.eval()
Hybrid models containing
KerasLayer
orTorchLayer
objects can also be saved and loaded.
-
Improvements 🛠
A more flexible projector
-
qml.Projector
now accepts a state vector representation, which enables the creation of projectors in any basis. (#4192)dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(state): return qml.expval(qml.Projector(state, wires=[0, 1])) zero_state = [0, 0] plusplus_state = np.array([1, 1, 1, 1]) / 2
>>> circuit(zero_state) tensor(1., requires_grad=True) >>> circuit(plusplus_state) tensor(0.25, requires_grad=True)
Do more with qutrits
-
Three qutrit rotation operators have been added that are analogous to
RX
,RY
, andRZ
:qml.TRX
: an X rotationqml.TRY
: a Y rotationqml.TRZ
: a Z rotation
-
Qutrit devices now support parameter-shift differentiation. (#2845)
The qchem module
-
qchem.molecular_hamiltonian()
,qchem.qubit_observable()
,qchem.import_operator()
, andqchem.dipole_moment()
now return an arithmetic operator ifenable_new_opmath()
is active.
(#4138) (#4159) (#4189) (#4204) -
Non-cubic lattice support for all electron resource estimation has been added. (#3956)
-
The
qchem.molecular_hamiltonian()
function has been upgraded to support custom wires for constructing differentiable Hamiltonians. The zero imaginary component of the Hamiltonian coefficients have been removed. (#4050) (#4094) -
Jordan-Wigner transforms that cache Pauli gate objects have been accelerated. (#4046)
-
An error is now raised by
qchem.molecular_hamiltonian
when thedhf
method is used for an open-shell system. This duplicates a similar error inqchem.Molecule
but makes it clear that thepyscf
backend can be used for open-shell calculations. (#4058) -
Updated various qubit tapering methods to support operator arithmetic. (#4252)
Next-generation device API
-
The new device interface has been integrated with
qml.execute
for autograd, backpropagation, and no differentiation. (#3903) -
Support for adjoint differentiation has been added to the
DefaultQubit2
device. (#4037) -
A new function called
measure_with_samples
that returns a sample-based measurement result given a state has been added. (#4083) (#4093) (#4162) (#4254) -
DefaultQubit2.preprocess
now returns a newExecutionConfig
object with decisions forgradient_method
,use_device_gradient
, andgrad_on_execution
. (#4102) -
Support for sample-based measurements has been added to the
DefaultQubit2
device. (#4105) (#4114) (#4133) (#4172) -
The
DefaultQubit2
device now has aseed
keyword argument. (#4120) -
Added a
dense
keyword toParametrizedEvolution
that allows forcing dense or sparse matrices. (#4079) (#4095) (#4285) -
Adds the Type variables
pennylane.typing.Result
andpennylane.typing.ResultBatch
for type hinting the result of an execution. (#4018) -
qml.devices.ExecutionConfig
no longer has ashots
property, as it is now on theQuantumScript
. It now has ause_device_gradient
property.ExecutionConfig.grad_on_execution = None
indicates a request for"best"
, instead of a string. (#4102) -
The new device interface for Jax has been integrated with
qml.execute
. (#4137) -
The new device interface is now integrated with
qml.execute
for Tensorflow. (#4169) -
The experimental device
DefaultQubit2
now supportsqml.Snapshot
. (#4193) -
The experimental device interface is integrated with the
QNode
. (#4196) -
The new device interface in integrated with
qml.execute
for Torch. (#4257)
Handling shots
-
QuantumScript
now has ashots
property, allowing shots to be tied to executions instead of devices. (#4067) (#4103) (#4106) (#4112) -
Several Python built-in functions are now properly defined for instances of the
Shots
class.print
: printingShots
instances is now human-readablestr
: convertingShots
instances to human-readable strings==
: equating two differentShots
instanceshash
: obtaining the hash values ofShots
instances
-
qml.devices.ExecutionConfig
no longer has ashots
property, as it is now on theQuantumScript
. It now has ause_device_gradient
property.ExecutionConfig.grad_on_execution = None
indicates a request for"best"
instead of a string. (#4102) -
QuantumScript.shots
has been integrated with QNodes so that shots are placed on theQuantumScript
duringQNode
construction. (#4110) -
The
gradients
module has been updated to use the newShots
object internally (#4152)
Operators
-
qml.prod
now accepts a single quantum function input for creating newProd
operators. (#4011) -
DiagonalQubitUnitary
now decomposes intoRZ
,IsingZZ
andMultiRZ
gates instead of aQubitUnitary
operation with a dense matrix. (#4035) -
All objects being queued in an
AnnotatedQueue
are now wrapped so thatAnnotatedQueue
is not dependent on the has of any operators or measurement processes. (#4087) -
A
dense
keyword toParametrizedEvolution
that allows forcing dense or sparse matrices has been added. (#4079) (#4095) -
Added a new function
qml.ops.functions.bind_new_parameters
that creates a copy of an operator with new parameters without mutating the original operator. (#4113) (#4256) -
qml.CY
has been moved fromqml.ops.qubit.non_parametric_ops
toqml.ops.op_math.controlled_ops
and now inherits fromqml.ops.op_math.ControlledOp
. (#4116) -
qml.CZ
now inherits from theControlledOp
class and supports exponentiation to arbitrary powers withpow
, which is no longer limited to integers. It also supportssparse_matrix
anddecomposition
representations. (#4117) -
The construction of the Pauli representation for the
Sum
class is now faster. (#4142) -
qml.drawer.drawable_layers.drawable_layers
andqml.CircuitGraph
have been updated to not rely onOperator
equality or hash to work correctly. (#4143)
Other improvements
-
A transform dispatcher and program have been added. (#4109) (#4187)
-
Reduced density matrix functionality has been added via
qml.math.reduce_dm
andqml.math.reduce_statevector
. Both functions have broadcasting support. (#4173) -
The following functions in
qml.qinfo
now support parameter broadcasting:reduced_dm
purity
vn_entropy
mutual_info
fidelity
relative_entropy
trace_distance
-
The following functions in
qml.math
now support parameter broadcasting:purity
vn_entropy
mutual_info
fidelity
relative_entropy
max_entropy
sqrt_matrix
-
pulse.ParametrizedEvolution
now raises an error if the number of input parameters does not match the number of parametrized coefficients in theParametrizedHamiltonian
that generates it. An exception is made forHardwareHamiltonian
s which are not checked. (#4216) -
The default value for the
show_matrices
keyword argument in all drawing methods is nowTrue
. This allows for quick insights into broadcasted tapes, for example. (#3920) -
Type variables for
qml.typing.Result
andqml.typing.ResultBatch
have been added for type hinting the result of an execution. (#4108) -
The Jax-JIT interface now uses symbolic zeros to determine trainable parameters. (#4075)
-
A new function called
pauli.pauli_word_prefactor()
that extracts the prefactor for a given Pauli word has been added. (#4164) -
Variable-length argument lists of functions and methods in some docstrings is now more clear. (#4242)
-
qml.drawer.drawable_layers.drawable_layers
andqml.CircuitGraph
have been updated to not rely onOperator
equality or hash to work correctly. (#4143) -
Drawing mid-circuit measurements connected by classical control signals to conditional operations is now possible. (#4228)
-
The autograd interface now submits all required tapes in a single batch on the backward pass. (#4245)
Breaking changes 💔
-
The default value for the
show_matrices
keyword argument in all drawing methods is nowTrue
. This allows for quick insights into broadcasted tapes, for example. (#3920) -
DiagonalQubitUnitary
now decomposes intoRZ
,IsingZZ
, andMultiRZ
gates rather than aQubitUnitary
. (#4035) -
Jax trainable parameters are now
Tracer
instead ofJVPTracer
. It is not always the right definition for the JIT interface, but we update them in the custom JVP using symbolic zeros. (#4075) -
The experimental Device interface
qml.devices.experimental.Device
now requires that thepreprocess
method also returns anExecutionConfig
object. This allows the device to choose what"best"
means for various hyperparameters likegradient_method
andgrad_on_execution
. (#4007) (#4102) -
Gradient transforms with Jax no longer support
argnum
. Useargnums
instead. (#4076) -
qml.collections
,qml.op_sum
, andqml.utils.sparse_hamiltonian
have been removed. (#4071) -
The
pennylane.transforms.qcut
module now uses(op, id(op))
as nodes in directed multigraphs that are used within the circuit cutting workflow instead ofop
. This change removes the dependency of the module on the hash of operators. (#4227) -
Operator.data
now returns atuple
instead of alist
. (#4222) -
The pulse differentiation methods,
pulse_generator
andstoch_pulse_grad
, now raise an error when they are applied to a QNode directly. Instead, use differentiation via a JAX entry point (jax.grad
,jax.jacobian
, ...). (#4241)
Deprecations 👋
-
LieAlgebraOptimizer
has been renamed toRiemannianGradientOptimizer
. (#4153) -
Operation.base_name
has been deprecated. Please useOperation.name
ortype(op).__name__
instead. -
QuantumScript
'sname
keyword argument and property have been deprecated. This also affectsQuantumTape
andOperationRecorder
. (#4141) -
The
qml.grouping
module has been removed. Its functionality has been reorganized in theqml.pauli
module. -
The public methods of
DefaultQubit
are pending changes to follow the new device API, as used inDefaultQubit2
. Warnings have been added to the docstrings to reflect this. (#4145) -
qml.math.reduced_dm
has been deprecated. Please useqml.math.reduce_dm
orqml.math.reduce_statevector
instead. (#4173) -
qml.math.purity
,qml.math.vn_entropy
,qml.math.mutual_info
,qml.math.fidelity
,qml.math.relative_entropy
, andqml.math.max_entropy
no longer support state vectors as input. Please callqml.math.dm_from_state_vector
on the input before passing to any of these functions. (#4186) -
The
do_queue
keyword argument inqml.operation.Operator
has been deprecated. Instead of settingdo_queue=False
, use theqml.QueuingManager.stop_recording()
context. (#4148) -
zyz_decomposition
andxyx_decomposition
are now deprecated in favour ofone_qubit_decomposition
. (#4230)
Documentation 📝
-
The documentation is updated to construct
QuantumTape
upon initialization instead of with queuing. (#4243) -
The docstring for
qml.ops.op_math.Pow.__new__
is now complete and it has been updated along withqml.ops.op_math.Adjoint.__new__
. (#4231) -
The docstring for
qml.grad
now states that it should be used with the Autograd interface only. (#4202) -
The description of
mult
in theqchem.Molecule
docstring now correctly states the value ofmult
that is supported. (#4058)
Bug Fixes 🐛
-
Fixed adjoint jacobian results with
grad_on_execution=False
in the JAX-JIT interface. (#4217) -
Fixed the matrix of
SProd
when the coefficient is tensorflow and the target matrix is notcomplex128
. (#4249) -
Fixed a bug where
stoch_pulse_grad
would ignore prefactors of rescaled Pauli words in the generating terms of a pulse Hamiltonian. (#4156) -
Fixed a bug where the wire ordering of the
wires
argument toqml.density_matrix
was not taken into account. (#4072) -
A patch in
interfaces/autograd.py
that checks for thestrawberryfields.gbs
device has been removed. That device is pinned to PennyLane <= v0.29.0, so that patch is no longer necessary. (#4089) -
qml.pauli.are_identical_pauli_words
now treats all identities as equal. Identity terms on Hamiltonians with non-standard wire orders are no longer eliminated. (#4161) -
qml.pauli_sentence()
is now compatible with empty Hamiltoniansqml.Hamiltonian([], [])
. (#4171) -
Fixed a bug with Jax where executing multiple tapes with
gradient_fn="device"
would fail. (#4190) -
A more meaningful error message is raised when broadcasting with adjoint differentiation on
DefaultQubit
. (#4203) -
The
has_unitary_generator
attribute inqml.ops.qubit.attributes
no longer contains operators with non-unitary generators. (#4183) -
Fixed a bug where
op = qml.qsvt()
was incorrect up to a global phase when usingconvention="Wx""
andqml.matrix(op)
. (#4214) -
Fixed a buggy calculation of the angle in
xyx_decomposition
that causes it to give an incorrect decomposition. Anif
conditional was intended to prevent divide by zero errors, but the division was by the sine of the argument. So, any multiple of$\pi$ should trigger the conditional, but it was only checking if the argument was 0. Example:qml.Rot(2.3, 2.3, 2.3)
(#4210) -
Fixed bug that caused
ShotAdaptiveOptimizer
to truncate dimensions of parameter-distributed shots during optimization. (#4240) -
Sum
observables can now have trainable parameters. (#4251) (#4275)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Venkatakrishnan AnushKrishna,
Utkarsh Azad,
Thomas Bromley,
Isaac De Vlugt,
Lillian M. A. Frederiksen,
Emiliano Godinez Ramirez
Nikhil Harle
Soran Jahangiri,
Edward Jiang,
Korbinian Kottmann,
Christina Lee,
Vincent Michaud-Rioux,
Romain Moyard,
Tristan Nemoz,
Mudit Pandey,
Manul Patel,
Borja Requena,
Modjtaba Shokrian-Zini,
Mainak Roy,
Matthew Silverman,
Jay Soni,
Edward Thomas,
David Wierichs,
Frederik Wilde.