
Франчес — выпускница Имперского колледжа Лондона с научной степенью в области вычислительных технологий. Она написала свой дипломный проект, работая в подразделении Microsoft Research. Сейчас Франчес работает в Microsoft на должности инженера программных решений. Основные направления ее деятельности — машинное обучение, большие данные и квантовые вычисления.
Содержание статьи
- Повторим основы
- Измеряем кубит
- Квантовые вентили
- Важные вентили
- Несколько кубитов
- Еще один важный вентиль
- Состояние Белла
- Пишем квантовую программу
- Что дальше?
- Приложение
- Дополнительные материалы
Повторим основы
Если вы не программируете на самом низком уровне, то вполне можете забыть, что любые программы, по сути, лишь манипулируют нулями и единицами, которые хранятся в наших «классических» компьютерах. Этим нулям и единицам соответствуют дискретные бинарные состояния физических систем. Квантовые компьютеры работают с непрерывными диапазонами состояний. Отчасти их возможности обусловлены именно этим.
Классический бит может принимать только одно из двух состояний — «включено» или «выключено», как обычная лампа накаливания. Кубиты, основа квантовых компьютеров, больше похожи на светильник с регулировкой яркости.

Для записи двух состояний кубитов можно использовать обозначения бра и кет (обозначения Дирака), которые соответствуют следующим векторам:

С помощью линейной комбинации этих двух состояний можно выразить любое возможное сочетание векторов |0? и |1?. В квантовой механике такое сочетание называется суперпозицией. Соответствующая запись в обозначениях Дирака будет выглядеть так:
|?? = ? |0? + ? |1?
Величины ? и ? связаны с вероятностями (с одним небольшим отличием — эти коэффициенты могут выражаться комплексными числами). Можно считать их действительными числами, но в этом случае помните, что они могут принимать отрицательные значения. Однако сумма их квадратов всегда равна 1.
Измеряем кубит
Квантовые состояния — странная штука. В результате измерения (или, как говорят, «в присутствии наблюдателя») кубит немедленно коллапсирует. Как это понимать? Предположим, кубит находится в состоянии суперпозиции. Если его измерить, то он примет одно конкретное значение — |0? или |1? (сразу оба результата одно измерение показать не может!). После измерения кубита коэффициенты ? и ?, которыми характеризовалось его предыдущее состояние, будут, по сути, утеряны.
Поэтому для описания измерений кубитов используется аппарат теории вероятностей. В общем случае вероятность того, что измерение состояния кубита покажет результат |0?, равна



Если мы будем измерять его состояние, то в 50 % случаев будем получать значение 0, потому что:

То есть после измерения он будет находиться в состоянии |0? (то есть ? = 1, ? = 0). По той же причине вероятность получить состояние 1 составляет 50 %. В этом случае после измерения кубит перейдет в состояние |1? (то есть ? = 0, ? = 1).

В первый раз все это сильно сбивает с толку (на второй, третий и четвертый раз ничего не изменится). Основная идея здесь заключается в том, что вероятностные квантовые состояния можно использовать для вычислений, и в некоторых случаях эта их квантовая «странность» позволяет получить системы с эффективностью выше классических. Теперь посмотрим, как эти кубиты можно использовать для вычислений, подобно классическим битам.
Квантовые вентили
Вернемся пока к более привычным вещам. В классической теории вычислений для выполнения операций над битами используются логические вентили. Для манипуляций над кубитами применяются аналогичные конструкции — квантовые вентили. Например, вентиль NOT выполняет преобразования 0 > 1 и 1 > 0. Квантовый вентиль NOT похож на своего классического предка: он выполняет преобразования |0? > |1? и |1? > |0?. Это значит, что после прохождения такого вентиля кубит из состояния ? |0? + ? |1? перейдет в состояние ? |1? + ? |0?. Вентиль NOT можно записать в виде матрицы (X), которая меняет местами 0 и 1 в матрице состояния:

Как видим, X|0? = |1?, а X|1? = |0?:

Поскольку |0? и |1? в векторной форме записываются как


Казалось бы, отличие от классического случая не столь велико. Но не забывайте, о чем мы говорили в предыдущем разделе: измерение состояния кубита носит вероятностный характер. Как известно из элементарной теории вероятностей, сумма вероятностей полной группы несовместных событий равна единице. Поэтому

Отсюда следует, что в квантовом мире могут существовать не все мыслимые вентили. Вот одно из ограничений: условие нормализации квантового состояния кубита,

Я постараюсь объяснить, что означает математическое понятие унитарности. Если прочитать его достаточно быстро, вы просто окажетесь на следующем предложении. Вентиль называется унитарным, если


Хорошие новости: это ограничение является единственным. Ввиду этого условия у некоторых классических вентилей нет квантового аналога, однако и у некоторых квантовых вентилей нет классического прототипа. Далее мы разберем важнейшие из квантовых вентилей.
Важные вентили: вентиль Z и вентиль Адамара
Описанные ниже вентили будут использоваться в нашей первой квантовой программе, поэтому постарайтесь их запомнить. Вентиль Z работает очень просто: он сохраняет компонент |0? и меняет знак компонента |1?. Его можно записать в виде матрицы

которая преобразует состояния кубитов следующим образом: |0? > |0?, |1? > -|1? (помните, что первый столбец матрицы описывает преобразование вектора |0?, второй — преобразование вектора |1?).
Вентиль Адамара создает суперпозицию состояний |0? и |1?, подобных рассмотренным выше. Его матричная запись выглядит так:

что соответствует следующим преобразованиям состояний кубитов:


Более подробная информация об унитарных матрицах и о способах наглядного представления вентилей приводится в ресурсах, ссылки на которые содержатся в разделе «Дополнительные материалы».
Несколько кубитов
Рассмотрим нечто более привычное. Классические биты существуют не только поодиночке, но и в виде сочетаний: например, 00, 01, 10 и 11. В квантовых вычислениях используются аналогичные комбинации: |00?, |01?, |10? и |11?. Состояние двух кубитов можно описать с помощью следующего вектора:

Как и раньше, вероятность получить в результате измерения величину 00 равна

для 01 вероятность равна

Допустим теперь, что мы хотим измерить состояние не обоих кубитов, а только первого. Вероятность получить при этом 0 равна


Обратите внимание на числитель: мы убрали все слагаемые, для которых первый бит равен 1 (поскольку по условию результат измерения равен 0). Для того чтобы вектор описывал допустимое квантовое состояние, необходимо, чтобы квадрат сумм амплитуд был равен единице (как до, так и после преобразования). Чтобы это условие выполнялось, мы добавляем нормировочный множитель — величину, обратную квадратному корню из определителя.
Еще один важный вентиль
Работу вентиля NOT мы уже разобрали. Следующий на очереди — вентиль CNOT (controlled-NOT, «управляемое НЕ»). На его вход подается два кубита. Первый называется управляющим, второй — управляемым. Если управляющий кубит равен |0?, то состояние управляемого кубита не меняется. Если управляющий кубит равен |1?, то к управляемому кубиту применяется операция NOT.
Операцию CNOT можно интерпретировать несколькими способами. Подобно вентилям X, Z и H, ее можно записать в матричной форме, которая обозначается буквой U.

Можно заметить, что столбцы матрицы соответствуют следующим преобразованиям: |00? > |00?, |01? > |01?, |10? > |11?, |11? > |10?. Как и матрицы, которые мы разобрали в


Также для этого вентиля используется следующее обозначение (верхняя часть соответствует управляющему кубиту, нижняя — управляемому):

Выглядит как экспонат выставки современного искусства.
Состояния Белла
Этой важной теме стоит посвятить целый раздел. Всего существует четыре состояния Белла. Одно из них (|?+?) будет использоваться в квантовой программе ниже. Давайте его рассмотрим.

Допустим, мы измеряем состояние первого кубита. Результат |0? мы получим с вероятностью


Теперь предположим, что мы измерили состояние второго кубита. Согласно тем же рассуждениям, после измерения пара будет находиться в состоянии |00? или |11?. Если после этого мы решим измерить состояние первого кубита, вероятности уже не будут равны 0,5. Мы получим |0? с вероятностью 1 или 0 — в зависимости от того, каким был результат измерения. Здесь важно понять, что эти результаты связаны между собой. Первыми это заметили Альберт Эйнштейн, Борис Подольский и Натан Розен (поэтому эти состояния иногда называют «парами ЭПР»). Впоследствии их теорию развил Джон Белл.
И последнее наблюдение: состояния Белла можно генерировать с помощью вентиля Адамара и вентиля CNOT. По-моему, это достойно восхищения. Вентиль Адамара переводит первый кубит в состояние суперпозиции. Затем этот кубит подается на управляющий вход вентиля CNOT. Вот как этот процесс можно представить с помощью диаграммы цепи:

Чтобы узнать подробнее, как работает каждое из этих преобразований, обратитесь к дополнительным материалам (список приводится ниже). Мы уже знаем о состояниях кубитов, квантовых вентилях и состояниях Белла достаточно, чтобы написать нашу первую программу с использованием кубитов.
Пишем квантовую программу
Мы будем следовать инструкциям из документации.
Это учебное руководство поможет вам выполнить следующие действия: установить пакет разработки квантовых программ (этапы 1–2), выделить кубит и выполнить над ним ряд простых манипуляций — например, установить его в некоторое состояние и измерить его (этапы 3–5), затем перевести кубит в состояние суперпозиции (этап 6), а после этого преобразовать два кубита в запутанное состояние — состояние Белла, или пару ЭПР (этап 7).
Рекомендуется следовать инструкциям из руководства, ссылка на которое приведена выше, и возвращаться к этому материалу, если вам нужны советы или дополнительные пояснения.
Этап 1. Создание проекта и решения
Q# находится в нижней части этого списка.

Этап 2 (необязательный). Обновление пакетов NuGet
Мы последовали этому совету, но делать это необязательно, особенно если вы любите рисковать.
Этап 3. Ввод кода Q#
namespace Quantum.Bell
{
open Microsoft.Quantum.Primitive;
open Microsoft.Quantum.Canon;
operation Set (desired: Result, q1: Qubit) : ()
{
body
{
let current = M(q1);
if (desired != current)
{
X(q1);
}
}
}
}
Эта операция переводит наш кубит в выбранное (нами) состояние — 0 или 1. Вначале мы измеряем кубит (эта операция обозначается буквой M), и он коллапсирует в состояние 0 или 1. Если измеренное состояние не соответствует желаемому, мы меняем его с помощью вентиля NOT, X. В противном случае ничего делать не надо.
operation BellTest (count : Int, initial: Result) : (Int,Int)
{
body
{
mutable numOnes = 0;
using (qubits = Qubit[1])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
let res = M (qubits[0]);
// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}
Set(Zero, qubits[0]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes);
}
}
Этот небольшой фрагмент кода предназначен для тестирования операции, которую мы только что написали. Это очень простая программа: она проверяет, что кубит был переведен в нужное нам состояние.
Для этого она в цикле проводит измерение и подсчитывает количество результатов 1 с помощью переменной numOnes.
Запись «Qubit[1]» означает «создать массив кубитов из одного элемента». Индексация элементов массива ведется с нуля. Чтобы выделить два кубита (позже нам потребуется это сделать), нужно записать «Qubit[2]». Кубитам в таком массиве соответствуют номера 0 и 1.
В цикле for мы устанавливаем кубит, выделенный для определенного начального состояния, — One или Zero (в файле Driver.cs, к которому мы скоро перейдем, это делается в явном виде). Мы измеряем это состояние, и если это One, увеличиваем значение счетчика на единицу. Затем функция возвращает количество наблюдаемых состояний One и Zero. В конце кубит переводится в состояние Zero (просто чтобы оставить его в некотором известном состоянии).
Этап 4. Ввод кода драйвера C#
using (var sim = new QuantumSimulator())
{
// Try initial values
Result[] initials = new Result[] { Result.Zero, Result.One };
foreach (Result initial in initials)
{
var res = BellTest.Run(sim, 1000, initial).Result;
var (numZeros, numOnes) = res;
System.Console.WriteLine(
$"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4}");
}
}
System.Console.WriteLine("Press any key to continue...");
System.Console.ReadKey();
В этом драйвере создается квантовый симулятор и массив начальных значений, которые нужно проверить (Zero и One). Затем симуляция повторяется 1000 раз, а результат для отладки выводится на экран с помощью функции System.Console.WriteLine.
Этап 5. Сборка и выполнение
Init:Zero 0s=1000 1s=0
Init:One 0s=0 1s=1000
Press any key to continue...
Если все в порядке, вывод на экран должен выглядеть так, как показано выше. Этот результат означает, что если мы переведем начальный кубит в состояние Zero и проведем тысячу повторов, то количество состояний |0? по результатам наблюдения будет равно 1000. То же самое должно выполняться для состояния One.
Этап 6. Создание суперпозиции
Попробуем нечто более интересное. Здесь мы меняем состояние кубита с помощью вентиля NOT.
X(qubits[0]);
let res = M (qubits[0]);
Затем запускаем программу заново и видим, что результаты стали обратными.
Init:Zero 0s=0 1s=1000
Init:One 0s=1000 1s=0
Затем вентиль NOT заменяем на вентиль Адамара (H). В результате, как мы знаем, кубит перейдет в суперпозицию состояний, и результат его измерения может быть равен как |0?, так и |1?, с некоторой вероятностью.
H(qubits[0]);
let res = M (qubits[0]);
Если запустить программу снова, мы получим довольно интересный результат.
Init:Zero 0s=484 1s=516
Init:One 0s=522 1s=478
Количество результатов измерений |0? и |1? будет примерно равно.
Шаг 7. Подготовка запутанного состояния
Сейчас мы создадим состояние Белла. Изучите приведенный ниже код. Вначале мы создаем массив из двух кубитов (Qubit[2]). Первый кубит (на предыдущей диаграмме цепи он обозначался символом x) мы переводим в некоторое начальное состояние, а второй (y на диаграмме) устанавливаем в состояние Zero. Это примерно то же самое, что ввод |00? либо |10? в зависимости от X:
operation BellTest (count : Int, initial: Result) : (Int,Int)
{
body
{
mutable numOnes = 0;
using (qubits = Qubit[2])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
Set (Zero, qubits[1]);
H(qubits[0]);
CNOT(qubits[0],qubits[1]);
let res = M (qubits[0]);
// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}
Set(Zero, qubits[0]);
Set(Zero, qubits[1]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes);
}
}
В соответствии с диаграммой первый кубит, qubits[0], нужно пропустить через вентиль Адамара. В результате он окажется в суперпозиции. Затем пропускаем кубиты через вентиль CNOT (qubits[0] — управляющий кубит, qubits[1] — управляемый) и измеряем результат.
Чтобы понять, какой результат следует ожидать, повторим еще раз, как работает наше состояние Белла. Если измерить первый кубит, мы получим значение |0? с вероятностью

В нашем коде мы проверяем, равен ли результат измерения qubits[1] результату измерения qubits[0], с помощью оператора if.
operation BellTest (count : Int, initial: Result) : (Int,Int,Int)
{
body
{
mutable numOnes = 0;
mutable agree = 0;
using (qubits = Qubit[2])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
Set (Zero, qubits[1]);
H(qubits[0]);
CNOT(qubits[0],qubits[1]);
let res = M (qubits[0]);
if (M (qubits[1]) == res)
{
set agree = agree + 1;
}
// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}
Set(Zero, qubits[0]);
Set(Zero, qubits[1]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes, agree);
}
}
Перед тем как проверить результаты, нужно внести в файл Driver.cs еще одно изменение: добавить переменную agree.
using (var sim = new QuantumSimulator())
{
// Try initial values
Result[] initials = new Result[] { Result.Zero, Result.One };
foreach (Result initial in initials)
{
var res = BellTest.Run(sim, 1000, initial).Result;
var (numZeros, numOnes, agree) = res;
System.Console.WriteLine(
$"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4} agree={agree,-4}");
}
}
System.Console.WriteLine("Press any key to continue...");
System.Console.ReadKey();
Теперь программу можно запускать. Что означают эти результаты? Если первый кубит изначально был помещен в состояние Zero (то есть на вход мы подали значение |00?), то вентиль Адамара переводит кубиты в состояние суперпозиции, и результат измерения равен |0? в 50 % случаев и |1? в 50 % случаев. Выполнение этого условия можно оценить по количеству нулей и единиц. Если бы измерение состояния первого бита не влияло на состояние второго, то оно оставалось бы равным |0?, и согласованность достигалась бы только в 499 случаях.
Но, как мы видим, состояния первого и второго кубита полностью согласуются: количество результатов |0? и |1? (примерно) совпадают. Таким образом, результаты согласованы в каждом из 1000 случаев. Именно так и должны работать состояния Белла.
Init:Zero 0s=499 1s=501 agree=1000
Init:One 0s=490 1s=510 agree=1000
На этом мы закончим. Вы написали свою первую квантовую программу и (раз уж вы добрались до конца), вероятно, поняли, что именно она делает. Это стоит отметить хорошей чашкой чая.

Что дальше?
В репозитории GitHub доступно множество примеров.
В следующей статье мы поговорим о теории квантовой телепортации и изучим пример кода.
Квантовые вентили рассматриваются подробнее в блоге Аниты (примечание: Анита просто потрясающая).
Дополнительные материалы
Если вы хотите углубиться в рассмотренные темы, ниже приводится список ресурсов, которые были для нас очень полезными. Первый из них — книга «Квантовые вычисления и квантовая информация» (М. Нильсен, И. Чанг). Второй — документация к пакету SDK от Microsoft.
Если это будет интересно читателям (оставьте комментарий!), мы можем составить отдельную публикацию о других ресурсах.
Приложение. Состояния Белла

Генерировать состояния Белла можно с помощью вентиля Адамара и вентиля CNOT. Вентиль Адамара переводит первый кубит в состояние суперпозиции. Затем этот кубит подается на управляющий вход вентиля CNOT. На диаграмме цепи это выглядит так:
Начнем с первого случая, когда на вход подается пара кубитов в состоянии |00?. Первый кубит, |0?, проходит через вентиль Адамара и превращается в


Затем кубиты проходят через вентиль CNOT (который выполняет преобразования |00? > |00? и |10? > |11?). Теперь их состояние будет описываться формулой

Второй случай: на вход подаются кубиты |01?. Вентиль Адамара переводит первый кубит |0? в состояние


Теперь пропустим кубиты через вентиль CNOT, который выполняет преобразования |01? > |01? и |11? > |10?. Итоговое состояние пары кубитов будет выглядеть так:

Третий случай: на вход подаются кубиты |10?. Вентиль Адамара переводит первый кубит |1? в состояние


Затем кубиты проходят через вентиль CNOT (который выполняет преобразования |00? > |00? и |10? > |11?). Теперь их состояние будет описываться формулой

Четвертый случай: на вход подаются кубиты |11?. Вентиль Адамара переводит первый кубит |1? в состояние


Теперь пропустим кубиты через вентиль CNOT, который выполняет преобразования |01? > |01? и |11? > |10?. Итоговое состояние пары кубитов будет выглядеть так:

Готово, мы разобрали все случаи.
Комментарии (18)
tronix286
26.03.2018 18:16+2Microsoft! Заранее извиняюсь, что не по теме, но всюду куда писал либо игнор, либо игнор. Ищу давно и безрезультатно Windows DDK for Windows 3.0. Да, driver development kit для Windows 3.0.
Readme
26.03.2018 19:53+1Возможно, заблуждаюсь, но… Что общего в понятиях "квантовое программирование", "линейное программирование" и "объектно-ориентированное программирование"? Правильный ответ — в их названиях есть слово "программирование" =) Думаю, в шутке таки есть щепотка истины.
Есть достаточно много открытых и не только материалов для приобщения к квантовому миру, но от себя хочу порекомендовать замечательный русскоязычный курс от СПбГУ "Квантовые вычисления" (Coursera, Stepic) и онлайн-редактор квантовых схем Quirk — курс даёт начальное представление о том, что вообще происходит и зачем мы всё это делаем, а редактор позволяет вдоволь поиграться с вентилями и реализовать простые квантовые схемы и алгоритмы.
Спойлер: бам, это всё линейная алгебра и жонглирование операторами, а эти ваши модные "квантовые" языки программирования — всего лишь синтаксический сахар над матричными операциями!
Nick_mentat
27.03.2018 11:46Спойлер: бам, квантовая физика и за пределами квантовых компьютеров — это матричные вычисления. А нотация Эйнштейна и бракеты — просто синтаксический сахар для удобства записи.
Readme
27.03.2018 12:46Справедливо, не спорю :) Просто, как мне кажется, на волне зарождающегося хайпа на "квантовую" тему далеко не все и не сразу понимают (а после выпада "сложнаа" — и не поймут), что же лежит в основе пресловутых квантовых вычислений — а ведь это банально последовательность унитарных преобразований над векторами гильбертова пространства (именно так, матан-концентрат), со всеми вытекающими последствиями: линейность, обратимость, экспоненциальный рост размерности и т.п.
К сожалению (или к счастью?), да, квантовую "чуйку" развить с нуля простому смертному будет довольно трудно — нужен сильный
царьмат. аппарат в голове и соответствующие навыки линейной алгебры. Увы, все мы в детстве складывали кубики и делили яблоки, а не эволюционировали волновые функции и не вращали вектора состояний в 2n-мерном пространстве — по этому поводу весьма занятно высказался К.Мухин в потрясающей книге «Занимательная ядерная физика»: "квантовую механику — младенцам!"Nick_mentat
27.03.2018 13:29Ну как же так, ведь здесь прямая аналогия с комплексными числами. С комплексными же всем понятно, что это комплекс из двух чисел, и матричками надо его, матричками. Вот так и здесь. Я бы, пожалуй, так это и объяснял.
UncleAndy
26.03.2018 20:01+1Может имело смысл код вставить в виде текста, что-бы не гемороиться с чего перенабором?
stasus Автор
27.03.2018 10:05Код множества примеров, в том числе и аналогичного используемому в статье доступен на GitHub github.com/Microsoft/Quantum/tree/master/Samples
stasus Автор
27.03.2018 12:41+1Но я всё равно постараюсь переделать. Торопился выложить, пошёл по более простому пути.
Oxoron
26.03.2018 21:50Формулы с кусками английского текста добили перевод и без того не самой простой публикации. Но вместе с базовой википедией кое-какое начальное представление о предмете статья дает.
Nick_mentat
27.03.2018 11:49Понял в статье всё, хотя местами перевод немного перекашивало
Спасибо за ссылки на репозиторий и блог)
CyberChipmunk
27.03.2018 12:38Скажите пожалуйста, на чём выполняется данный код (аппаратно)?
stasus Автор
27.03.2018 12:40На обычном компьютере, локально или в облаке. Обратите внимание на код C#, в котором запускается QuantumSimulator.
Kirhgoff
29.03.2018 13:44+1Очень интересна вся эта тема с квантовыми компьютерами, хочется попробовать покопаться, но встает проблема — для меня работает только, если есть какая-то интересная актуальная задача, которую надо решить, тогда получается повозиться и поразбираться. Я знаю про разложение на множители, которое должно супер-быстро решаться, но вот что есть такого еще прикладного, что может пригодиться здесь и сейчас, что имеет смысл решать квантовым программированием? Буду рад каким-то идеям и ссылкам
Meklon
Тут на обычном многопоточном программировании зависаешь, а тут ещё и суперпозицию в голове держать. Профессиональные программисты подобных вещей не будут тихо галоперидол потом есть?
zhaparoff
Боюсь, после определенной концентрации суперпозиций на одно полушарие, программистам придется насильно укольчики делать.
Ну а если серьезно, мне, допустим, сложновато воспринимать штуки, которые непонятно как визуализировать. Все эти комплексные числа, n-мерные тензорные пространства и вот это вот все. Те же множества и операции с ними вполне можно объяснить ученику начальных классов, используя стандартные рисунки с окружностями. Вполне можно было добавить к этой статье сферу Блоха, которая упоминается в статье Аниты. Хотя визуальное представление кубита все равно не слишком упрощает понимание, что делать с целой коробкой этих «шариков» =)