# Qiskit Aer: Pulse simulation of two qubits using a Duffing oscillator model

This notebook shows how to use the Qiskit Aer pulse simulator, which simulates experiments specified as pulse `Schedule` objects at the Hamiltonian level. The simulator solves the Schrodinger equation for a specified Hamiltonian model and pulse `Schedule` in the frame of the drift Hamiltonian.

In particular, in this tutorial we will:

- Construct a model of a two qubit superconducting system.
- Calibrate $\pi$ pulses on each qubit in the simulated system.
- Observe cross-resonance oscillations when driving qubit 1 with target qubit 0.

The Introduction outlines the concepts and flow of this notebook.

## 1. Introduction

The main sections proceed as follows.

### Section 3: Duffing oscillator model

To simulate a physical system, it is necessary to specify a model. In this notebook, we will model superconducting qubits as a collection of *Duffing oscillators*. The model is specified in terms of the following parameters:

-  Each Duffing oscillator is specified by a frequency $\nu$, anharmonicity $\alpha$, and drive strength $r$, which result in the Hamiltonian terms:

$$\begin{equation}
    2\pi\nu a^\dagger a + \pi \alpha a^\dagger a(a^\dagger a - 1) + 2 \pi r (a + a^\dagger) \times D(t),
\end{equation}$$

where $D(t)$ is the signal on the drive channel for the qubit, and $a^\dagger$ and $a$ are, respectively, the creation and annihilation operators for the qubit. Note that the drive strength $r$ sets the scaling of the control term, with $D(t)$ assumed to be a complex and unitless number satisfying $|D(t)| \leq 1$.

-  A coupling between a pair of oscillators $(l,k)$ is specified by the coupling strength $J$, resulting in an exchange coupling term:

$$\begin{equation}
    2 \pi J (a_l a_k^\dagger + a_l^\dagger a_k),
\end{equation}$$

where the subscript denotes which qubit the operators act on.

- Additionally, for numerical simulation, it is necessary to specify a cutoff dimension; the Duffing oscillator model is *infinite dimensional*, and computer simulation requires restriction of the operators to a finite dimensional subspace.

**In the code:** We will define a model of the above form for two coupled qubits using the helper function `duffing_system_model`.

### Section 4: $\pi$-pulse calibration using Ignis

Once the model is defined, we will calibrate $\pi$-pulses on each qubit. A $\pi$-pulse is defined as a pulse on the drive channel of a qubit that "flips" the qubit; i.e. that takes the ground state to the first excited state, and the first excited state to the ground state.

We will experimentally find a $\pi$-pulse for each qubit using the following procedure:

- A fixed pulse shape is set - in this case it will be a Gaussian pulse.
- A sequence of experiments is run, each consisting of a Gaussian pulse on the qubit, followed by a measurement, with each experiment in the sequence having a subsequently larger amplitude for the Gaussian pulse.
- The measurement data is fit, and the pulse amplitude that completely flips the qubit is found (i.e. the $\pi$-pulse amplitude).

**In the code:** Using Ignis we will construct `Schedule` objects for the above experiments, then fit the data to find the $\pi$-pulse amplitudes.

### Section 5: Cross-resonance oscillations

Once the $\pi$-pulses are calibrated, we will simulate the effects of cross-resonance driving on qubit $1$ with target qubit $0$. This means that we will drive qubit $1$ at the frequency of qubit $0$, with the goal of observing that the trajectory and oscillations of qubit $0$ *depends* on the state of qubit $1$. This phenomenon provides a basis for creating two-qubit *controlled* gates. Note: This section requires the calibration of the $\pi$-pulse in Section 4.

To observe cross-resonance driving, we will use experiments very similar to the $\pi$-pulse calibration case:

- Initially, qubit $1$ is either left in the ground state, or is driven to its first excited state using the $\pi$-pulse found in Section 4.
- A sequence of experiments is run, each consisting of a Gaussian pulse on qubit $1$ driven at the frequency of qubit $0$, followed by a measurement of both qubits, with each experiment of the sequence having a subsequently larger amplitude for the Gaussian pulse.

**In the code:** Functions for defining the experiments and visualizing the data are constructed, including a visualization of the trajectory of the target qubit on the Bloch sphere.

## 2. Imports

This notebook makes use of the following imports.

```python
import numpy as np
from scipy.optimize import curve_fit, root

# visualization tools
import matplotlib.pyplot as plt
from qiskit.visualization.bloch import Bloch
```

Import qiskit libraries for working with `pulse` and calibration:

```python
import qiskit.pulse as pulse
from qiskit.pulse.library import Gaussian, GaussianSquare
from qiskit.compiler import assemble

from qiskit.ignis.characterization.calibrations import rabi_schedules, RabiFitter
```

Imports for qiskit pulse simulator:

```python
# The pulse simulator
from qiskit.providers.aer import PulseSimulator

# function for constructing duffing models
from qiskit.providers.aer.pulse import duffing_system_model
```

## 3. Duffing oscillator system model

An object representing a model for a collection of Duffing oscillators can be constructed using the `duffing_system_model` function. Here we construct a $2$ Duffing oscillator model with cutoff dimension $3$.

```python
# cutoff dimension
dim_oscillators = 3

# frequencies for transmon drift terms, harmonic term and anharmonic term
# Number of oscillators in the model is determined from len(oscillator_freqs)
oscillator_freqs = [5.0e9, 5.2e9]
anharm_freqs = [-0.33e9, -0.33e9]

# drive strengths
drive_strengths = [0.02e9, 0.02e9]

# specify coupling as a dictionary (qubits 0 and 1 are coupled with a coefficient 0.002e9)
coupling_dict = {(0,1): 0.002e9}

# sample duration for pulse instructions
dt = 1e-9

# create the model
two_qubit_model = duffing_system_model(dim_oscillators=dim_oscillators,
                                       oscillator_freqs=oscillator_freqs,
                                       anharm_freqs=anharm_freqs,
                                       drive_strengths=drive_strengths,
                                       coupling_dict=coupling_dict,
                                       dt=dt)
```

The function `duffing_system_model` returns a `PulseSystemModel` object, which is a general object for storing model information required for simulation with the `PulseSimulator`.

## 4 Calibrating $\pi$ pulses on each qubit using Ignis

As described in the introduction, we now calibrate $\pi$ pulses on each qubit in `two_qubit_model`. The experiments in this calibration procedure are known as *Rabi experiments*, and the data we will observe are known as *Rabi oscillations*.

### 4.1 Constructing the schedules

We construct the schedules using the `rabi_schedules` function in Ignis. To do this, we need to supply an `InstructionScheduleMap` containing a measurement schedule.

```python
# list of qubits to be used throughout the notebook
qubits = [0, 1]

# Construct a measurement schedule and add it to an InstructionScheduleMap
meas_amp = 0.025
meas_samples = 1200
meas_sigma = 4
meas_width = 1150
meas_pulse = GaussianSquare(duration=meas_samples, amp=meas_amp,
                            sigma=meas_sigma, width=meas_width)

acq_sched = pulse.Acquire(meas_samples, pulse.AcquireChannel(0), pulse.MemorySlot(0))
acq_sched += pulse.Acquire(meas_samples, pulse.AcquireChannel(1), pulse.MemorySlot(1))

measure_sched = pulse.Play(meas_pulse, pulse.MeasureChannel(0)) | pulse.Play(meas_pulse, pulse.MeasureChannel(1)) | acq_sched

inst_map = pulse.InstructionScheduleMap()
inst_map.add('measure', qubits, measure_sched)
```

Next, construct the Rabi schedules.

```python
# construct Rabi experiments
drive_amps = np.linspace(0, 0.9, 48)
drive_sigma = 16
drive_duration = 128
drive_channels = [pulse.DriveChannel(0), pulse.DriveChannel(1)]

rabi_experiments, rabi_amps = rabi_schedules(amp_list=drive_amps,
                                             qubits=qubits,
                                             pulse_width=drive_duration,
                                             pulse_sigma=drive_sigma,
                                             drives=drive_channels,
                                             inst_map=inst_map,
                                             meas_map=[[0, 1]])
```

The `Schedule`s in `rabi_schedules` correspond to experiments to generate Rabi oscillations on both qubits in parallel. Each experiment consists of a Gaussian pulse on the qubits of a given magnitude, followed by measurement.

For example:

```python
rabi_experiments[10].draw()
```

### 4.2 Simulate the Rabi experiments

To simulate the Rabi experiments, assemble the `Schedule` list into a qobj. When assembling, pass the `PulseSimulator` as the backend.

Here, we want to use local oscillators with frequencies automatically computed from Duffing model Hamiltonian.

```python
# instantiate the pulse simulator
backend_sim = PulseSimulator()

# compute frequencies from the Hamiltonian
qubit_lo_freq = two_qubit_model.hamiltonian.get_qubit_lo_from_drift()

rabi_qobj = assemble(rabi_experiments,
                     backend=backend_sim,
                     qubit_lo_freq=qubit_lo_freq,
                     meas_level=1,
                     meas_return='avg',
                     shots=512)
```

Run the simulation using the simulator backend.

```python
# run the simulation
rabi_result = backend_sim.run(rabi_qobj, two_qubit_model).result()
```

### 4.3 Fit and plot the data

Next, we use `RabiFitter` in Ignis to fit the data, extract the $\pi$-pulse amplitude, and then plot the data.

```python
rabifit = RabiFitter(rabi_result, rabi_amps, qubits, fit_p0 = [0.5,0.5,0.6,1.5])

plt.figure(figsize=(15, 10))
q_offset = 0
for qubit in qubits:
    ax = plt.subplot(2, 2, qubit + 1)
    rabifit.plot(qubit, ax=ax)
    print('Pi Amp: %f'%rabifit.pi_amplitude(qubit))
plt.show()
```

Plotted is the averaged IQ data for observing each qubit. Observe that here, each qubit oscillates between the 0 and 1 state. The amplitude at which a given qubit reaches the peak of the oscillation is the desired $\pi$-pulse amplitude.

## 5. Oscillations from cross-resonance drive

Next, we simulate the effects of a cross-resonance drive on qubit $1$ with target qubit $0$, observing that the trajectory and oscillations of qubit $0$ *depends* on the state of qubit $1$.

**Note:** This section depends on the $\pi$-pulse calibrations of Section 2.

### 5.1 Cross-resonance `ControlChannel` indices

Driving qubit $1$ at the frequency of qubit $0$ requires use of a pulse `ControlChannel`. The model generating function `duffing_system_model`, automatically sets up `ControlChannels` for performing cross-resonance drives between pairs of coupled qubits. The index of the `ControlChannel` for performing a particular cross-resonance drive is retrievable using the class method `control_channel_index` on the returned `PulseSystemModel`. For example, to get the `ControlChannel` index corresponding to a CR drive on qubit 1 with target 0, call the function `control_channel_index` with the tuple `(1,0)`:

```python
two_qubit_model.control_channel_index((1,0))
```

    1

Hence, to perform a cross-resonance drive on qubit $1$ with target qubit $0$, use `ControlChannel(1)`. This will be made use of when constructing `Schedule` objects in this section.

### 5.2 Functions to generate the experiment list, and analyze the output

First, we define a function `cr_drive_experiments`, which, given the drive and target indices, and the option to either start with the drive qubit in the ground or excited state, returns a list of experiments for observing the oscillations.

```python
# store the pi amplitudes from Section 2 in a list
pi_amps = [rabifit.pi_amplitude(0), rabifit.pi_amplitude(1)]

def cr_drive_experiments(drive_idx,
                         target_idx,
                         flip_drive_qubit = False,
                         cr_drive_amps=np.linspace(0, 0.9, 16),
                         cr_drive_samples=800,
                         cr_drive_sigma=4,
                         pi_drive_samples=128,
                         pi_drive_sigma=16):
    """Generate schedules corresponding to CR drive experiments.

    Args:
        drive_idx (int): label of driven qubit
        target_idx (int): label of target qubit
        flip_drive_qubit (bool): whether or not to start the driven qubit in the ground or excited state
        cr_drive_amps (array): list of drive amplitudes to use
        cr_drive_samples (int): number samples for each CR drive signal
        cr_drive_sigma (float): standard deviation of CR Gaussian pulse
        pi_drive_samples (int): number samples for pi pulse on drive
        pi_drive_sigma (float): standard deviation of Gaussian pi pulse on drive

    Returns:
        list[Schedule]: A list of Schedule objects for each experiment
    """

    # Construct measurement commands to be used for all schedules
    meas_amp = 0.025
    meas_samples = 1200
    meas_sigma = 4
    meas_width = 1150
    meas_pulse = GaussianSquare(duration=meas_samples, amp=meas_amp,
                               sigma=meas_sigma, width=meas_width)

    acq_sched = pulse.Acquire(meas_samples, pulse.AcquireChannel(0), pulse.MemorySlot(0))
    acq_sched += pulse.Acquire(meas_samples, pulse.AcquireChannel(1), pulse.MemorySlot(1))

    # create measurement schedule
    measure_sched = (pulse.Play(meas_pulse, pulse.MeasureChannel(0)) |
                     pulse.Play(meas_pulse, pulse.MeasureChannel(1))|
                     acq_sched)

    # Create schedule
    schedules = []
    for ii, cr_drive_amp in enumerate(cr_drive_amps):

        # pulse for flipping drive qubit if desired
        pi_pulse = Gaussian(duration=pi_drive_samples, amp=pi_amps[drive_idx], sigma=pi_drive_sigma)

        # cr drive pulse
        cr_width = cr_drive_samples - 2*cr_drive_sigma*4
        cr_rabi_pulse = GaussianSquare(duration=cr_drive_samples,
                                       amp=cr_drive_amp,
                                       sigma=cr_drive_sigma,
                                       width=cr_width)

        # add commands to schedule
        schedule = pulse.Schedule(name='cr_rabi_exp_amp_%s' % cr_drive_amp)

        # flip drive qubit if desired
        if flip_drive_qubit:
            schedule += pulse.Play(pi_pulse, pulse.DriveChannel(drive_idx))

        # do cr drive
        # First, get the ControlChannel index for CR drive from drive to target
        cr_idx = two_qubit_model.control_channel_index((drive_idx, target_idx))
        schedule += pulse.Play(cr_rabi_pulse, pulse.ControlChannel(cr_idx))  << schedule.duration

        schedule += measure_sched << schedule.duration

        schedules.append(schedule)
    return schedules
```

Next we create two functions for observing the data:

- `plot_cr_pop_data` - for plotting the oscillations between the ground state and the first excited state
- `plot_bloch_sphere` - for viewing the trajectory of the target qubit on the bloch sphere

```python
def plot_cr_pop_data(drive_idx,
                     target_idx,
                     sim_result,
                     cr_drive_amps=np.linspace(0, 0.9, 16)):
    """Plot the population of each qubit.

    Args:
        drive_idx (int): label of driven qubit
        target_idx (int): label of target qubit
        sim_result (Result): results of simulation
        cr_drive_amps (array): list of drive amplitudes to use for axis labels
    """

    amp_data_Q0 = []
    amp_data_Q1 = []

    for exp_idx in range(len(cr_drive_amps)):
        exp_mem = sim_result.get_memory(exp_idx)
        amp_data_Q0.append(np.abs(exp_mem[0]))
        amp_data_Q1.append(np.abs(exp_mem[1]))

    plt.plot(cr_drive_amps, amp_data_Q0, label='Q0')
    plt.plot(cr_drive_amps, amp_data_Q1, label='Q1')
    plt.legend()
    plt.xlabel('Pulse amplitude, a.u.', fontsize=20)
    plt.ylabel('Signal, a.u.', fontsize=20)
    plt.title('CR (Target Q{0}, driving on Q{1})'.format(target_idx, drive_idx), fontsize=20)
    plt.grid(True)

def bloch_vectors(drive_idx, drive_energy_level, sim_result):
    """Plot the population of each qubit.

    Args:
        drive_idx (int): label of driven qubit
        drive_energy_level (int): energy level of drive qubit at start of CR drive
        sim_result (Result): results of simulation

    Returns:
        list: list of Bloch vectors corresponding to the final state of the target qubit
              for each experiment
    """

    # get the dimension used for simulation
    dim = int(np.sqrt(len(sim_result.get_statevector(0))))

    # get the relevant dressed state indices
    idx0 = 0
    idx1 = 0
    if drive_idx == 0:
        if drive_energy_level == 0:
            idx0, idx1 = 0, dim
        elif drive_energy_level == 1:
            idx0, idx1 = 1, dim + 1
    if drive_idx == 1:
        if drive_energy_level == 0:
            idx0, idx1 = 0, 1
        elif drive_energy_level == 1:
            idx0, idx1 = dim, dim + 1

    # construct Pauli operators for correct dressed manifold
    state0 = np.array([two_qubit_model.hamiltonian._estates[idx0]])
    state1 = np.array([two_qubit_model.hamiltonian._estates[idx1]])

    outer01 = np.transpose(state0)@state1
    outer10 = np.transpose(state1)@state0
    outer00 = np.transpose(state0)@state0
    outer11 = np.transpose(state1)@state1

    X = outer01 + outer10
    Y = -1j*outer01 + 1j*outer10
    Z = outer00 - outer11

    # function for computing a single bloch vector
    bloch_vec = lambda vec: np.real(np.array([np.conj(vec)@X@vec, np.conj(vec)@Y@vec, np.conj(vec)@Z@vec]))

    return [bloch_vec(sim_result.get_statevector(idx)) for idx in range(len(sim_result.results))]

def plot_bloch_sphere(bloch_vectors):
    """Given a list of Bloch vectors, plot them on the Bloch sphere

    Args:
        bloch_vectors (list): list of bloch vectors
    """
    sphere = Bloch()
    sphere.add_points(np.transpose(bloch_vectors))
    sphere.show()
```

### 5.3 Drive qubit 1 to observe CR oscillations on qubit 0

#### Qubit 1 in the ground state

First, we drive with both qubit 0 and qubit 1 in the ground state.

```python
# construct experiments
drive_idx = 1
target_idx = 0
flip_drive = False
experiments = cr_drive_experiments(drive_idx, target_idx, flip_drive)

# compute frequencies from the Hamiltonian
qubit_lo_freq = two_qubit_model.hamiltonian.get_qubit_lo_from_drift()

# assemble the qobj
cr_rabi_qobj = assemble(experiments,
                        backend=backend_sim,
                        qubit_lo_freq=qubit_lo_freq,
                        meas_level=1,
                        meas_return='avg',
                        shots=512)
```

Run the simulation:

```python
sim_result = backend_sim.run(cr_rabi_qobj, two_qubit_model).result()

plot_cr_pop_data(drive_idx, target_idx, sim_result)
```

Observe that qubit 1 remains in the ground state, while excitations are driven in qubit 0.

We may also observe the trajectory of qubit 0 on the Bloch sphere:

```python
bloch_vecs = bloch_vectors(drive_idx, int(flip_drive), sim_result)
plot_bloch_sphere(bloch_vecs)
```

#### Qubit 1 in the first excited state

Next, we again perform a CR drive qubit 1 with qubit 0 as the target, but now we start each experiment by flipping qubit 1 into the first excited state.

```python
# construct experiments, now with flip_drive == True
drive_idx = 1
target_idx = 0
flip_drive = True
experiments = cr_drive_experiments(drive_idx, target_idx, flip_drive)

# compute frequencies from the Hamiltonian
qubit_lo_freq = two_qubit_model.hamiltonian.get_qubit_lo_from_drift()

# assemble the qobj
cr_rabi_qobj = assemble(experiments,
                        backend=backend_sim,
                        qubit_lo_freq=qubit_lo_freq,
                        meas_level=1,
                        meas_return='avg',
                        shots=512)
```

```python
sim_result = backend_sim.run(cr_rabi_qobj, two_qubit_model).result()

plot_cr_pop_data(drive_idx, target_idx, sim_result)
```

Observe that now qubit 1 is in the excited state, while oscillations are again being driven on qubit 0, now at a different rate as before.

Again, observe the trajectory of qubit 0 on the Bloch sphere:

```python
bloch_vecs = bloch_vectors(drive_idx, int(flip_drive), sim_result)
plot_bloch_sphere(bloch_vecs)
```

Here we see that qubit 0 takes a *different* trajectory on the Bloch sphere when qubit 1 is in the excited state. This is what enables controlled operations between two qubits.

```python
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
```

Version Information

Qiskit Software	Version

Qiskit	0.19.6

Terra	0.14.2

Aer	0.5.2

Ignis	0.3.3

Aqua	0.7.3

IBM Q Provider	0.7.2

System information

Python	3.8.5 (default, Jul 27 2020, 08:42:51)
[GCC 10.1.0]

OS	Linux

CPUs	32

Memory (Gb)	125.72603988647461

Sun Aug 09 12:28:03 2020 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 2020.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
 of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

```python

```


```python
clean_up=True # removes gams-related files in work-folder if true
%run StdPackages.ipynb
os.chdir(directory['py'])
import techdata_to_tree, sys, ShockFunction
import abatement_ID_calib as abatement
os.chdir(directory['curr'])
def flatten_list(list_):
    return [item for sublist in list_ for item in sublist]
```

    The file_gams_py_gdb0.gdx is still active and was not deleted.
    The file_gams_py_gdb1.gdx is still active and was not deleted.

*Specify settings:*

```python
name = 'A1_calib_test'
f = {'tech': directory['data']+"/techdata_dors_2.xlsx",
     'other': directory['data']+"/Othdata_dors_2.xlsx"}
s = ['inputdisp','endofpipe','inputprices']
m = ['ID','EOP']
e = {'M': 'vars','M_sets': 'sets'} # dictionary used to load emissions data
other_trees = {'ID_Y': {'s': 'Y', 'read_data': {'Y': 'vars'}, 'Q2P': 'Q2P'}} # the key = tree name, s = sheet that specifies tree structure, read_data (if not None) specifies how to load data.
```

```python
type_f = {'ID_EC': 'CES_norm',
          'ID_CU': 'MNL',
          'ID_TU': 'CET_norm',
          'ID_TX': 'CES',
          'ID_BU': 'linear_out',
          'ID_BX': 'CES',
          'EOP_CU': 'MNL',
          'EOP_TU': 'CET_norm',
          'EOP_TX': 'CES'}
```

*Set up nesting tree:*

```python
%run ATree.ipynb
```

## 1: Initialize model

*Set up gams model from tree:*

```python
gm = abatement.abate(nt=nts['ID'],tech=tech,work_folder=directory['work'],**{'data_folder':directory['gams'],'name': name})
```

*Add a couple of extra aliases, and update sets with elements:*

```python
gm.add_aliases([('n','nnnn'),('n','nnnnn')])
gm.model.database.update_all_sets(clean_up=False)
```

*Initialise variables at leontief levels, and fill out with simple standard values in potentially missing domains:*

```python
gm.initialize_variables_leontief()
gm.initialize_variables(**{'check_variables': True})
```

## 2: Solve model

*Set up baseline model and solve:*

```python
gm.write_and_run(name='baseline')
db0 = gm.model_instances['baseline'].out_db
```

*Read back solution to main db:*

```python
gm.model.database.merge_dbs(gm.model.database,db0,'second')
```

*Change to calibration state and resolve:*

```python
gm.reset_settings()
gm.setstate('ID_calibrate',init=False)
gm.write_and_run(name='ID_calibrate',kwargs_init={'check_variables':True},overwrite=True)
db = gm.model_instances['ID_calibrate'].out_db
```

*Print some... Note that if the ID technologies do not have any overlap with other technologies, the calibration procedure only has one possible solution; i.e. there are not any room for really minimizing the minobj.*

```python
def compare_plots(db0,db,var):
    return pd.concat([db0.get(var).rename(f"{var}, B"),db.get(var).rename(f"{var}, C")],axis=1).plot.bar();
```

```python
var = 'PwThat'
compare_plots(db0,db,var)
```

```python
var = 'qD'
compare_plots(db0,db,var)
```

## 3: Test that the baseline model is also solved with the new calibrated values:

## A: Additional equations

### Current application

* Define currapp[n,nn] as the share of energy-services covered by technology goods produced by technology $nn$ to satisfy the energy-service need $n$. In other words:

$$\begin{align}
    \text{currapp[n,nn]} =\text{sum(nnn\$(e2u[n,nnn]), qD[nnn])} / qD[n].
\end{align}$$

* Define the technology shares $U/C$ as $s\_uc[u,c]$ as the shares under the counterfactual scenario outlined in the article. Specifically:

$$\begin{align}
    \text{s_uc[n,nn]} &= \dfrac{exp\left(\sigma[nn]\left(PwThat[nn]-PwThat[n]\right)\right)\mu[n,nn]}{sum\left[nnn\$(\text{non-baseline}[nnn]), share[nnn]\right]+sum\left[nnn\$(\text{baseline}[nnn]) shareB[nnn]\right]} \\
    \sum share[nnn] &= sum\left[nnn\$\left(u2c[nnn,nn]\text{ and }bra\_ID\_TU[nnn]\right), \mu[nnn,nn] exp\left(\sigma[nn]\left(PwThat[nn]-PwThat[nnn]\right)\right)\right] \\
    \sum shareB[nnn]&= sum\left[nnn\$\left(u2c[nnn,nn]\text{ and }bra\_ID\_BU[nnn]\right), \mu[nnn,nn] exp\left(\sigma[nn]\left(PwThat[nn]-PwThatX[nnn]\right)\right)\right]
\end{align}$$
where the $share[nnn]$ is the MNL-like contribution from non-baseline goods in the relevant nest, and $shareB[nnn]$ is the MNL-like contribution from baseline goods. This price is adjusted here, to reflect a technology level ($\tau$ instead of $U$) price:

$$\begin{align}
    PwThatX[nnn] = sum\left[nnnn\$\left(e2u[nnnn,n]\right), sum(nnnnn\$(u2t[n,nnnnn]), gamma\_tau[nnnn,nnnnn]) * sum(nnnnn\$\left(u2t[nnn,nnnnn]\right), PwThat[nnnnn])\right]
\end{align}$$

Here we have: $n$ = technology good, $nn$ = relevant component, $nnn$ = baseline good $U$ in the same nest as good $n$, $nnnn$ = relevant energy-service nest, and $nnnnn$ first identifies the relevant technology $(\tau)$ to good $n$, and afterwards the relevant technology $(\tau)$ to good $nnn$.

Here, the first part identifies the relevant energy-service that good $U$ contributes to. The second part then first identifies the technology-specific component and picks out the $(e,\tau)$ parameter in gamma\_tau. This is multiplied by the baseline technology price.

* Define the modified current application $currapp\_mod[n,nn]$ level with $n$ = energy-service and $nn$=technology:

$$\begin{align}
    \text{currapp_mod[n,nn]} = \text{sum([nnn,nnnn]\$(u2t[nnn,nn] and c2e[nnnn,n]), s_uc[nnn,nnnn]*qD[nnnn]/qD[n])}
\end{align}$$

This takes the hypothetical share $U/C$ and multiplies by the actual share $C/E$.


# CE200A: Environmental Fluid Mechanics

### Homework 01

**Course Professor:** Tina Chow (chow@berkeley.edu) (CEE, UC Berkeley)

**GSI:** Adam Wise (adamwise@berkeley.edu) (CEE, UC Berkeley)

**Python expert:** Ajay B Harish (ajaybh@berkeley.edu) (SimCenter, UC Berkeley)

---

### Due date

Sep. 4 at 11am PDT

### Goal of this homework

- Review of basic fluid mechanics
- Review of basic mathematical operations
- **Estimated time for completion:** 90-120 minutes

### Question 01

Sketch the vertical temperature profile in a lake which fits the following descriptions:

1. neutrally stratified
2. stably stratified
2. unstably stratified

###Question 02

Consider viscous flow in a channel of width $2b$. The channel is aligned in the $x$-direction and the velocity at a distance $z$ from teh centerline is given by the parabolic distribution
\begin{equation*}
u(x) = U_0 \left[ 1 - \frac{z^2}{b^2} \right]
\end{equation*}
with the viscosity $\mu$, calculate the shear stress at a distance of $z = b/2$

### Question 03

Consider a density gradient in an open channel of $\rho(x) = \rho_0 − \alpha x$ with a free surface elevation of $h(x) = h_0 +\beta x$ where $x$ is the streamwise direction, and $z$ is in the vertical with $z=0$ at the bottom of the channel. $\rho$ does not vary with $z$, and $\rho_0$, $h_0$, $\alpha$, and $\beta$ are positive constants. Calculate the barotropic pressure gradient and the baroclinic pressure gradient components that result. Sketch profiles of $\partial p$ for each component as $\partial x$ a function of $z$ and explain which direction they will each drive flow in the channel.

### Question 04

In a compressible medium like the atmosphere, the stability of a fluid parcel is determined by comparisons to a background neutral state, which does not have constant density. In a neutral atmosphere, the pressure, density, and temperature change adiabatically with height such that entropy is constant. We can define the potential temperature, $\theta$, which remains constant when the atmosphere is neutral:
\begin{equation*}
\theta = T \left( \frac{P_0}{P} \right)^{R/c_p}
\end{equation*}
where $T$ is the temperature, $P$ is pressure, $P_0$ is pressure at ground level, $R$ is the gas constant, and $c_p$ is the specific heat at constant pressure. Any gradients in the potential temperature vertical profile indicate deviations from neutral stratification. We will not be diving into the details of atmospheric thermodynamics, but we can use profiles of potential temperature (instead of regular temperature) to easily determine if the atmosphere is stable, neutral, or unstable, and thus how these conditions affect advection and diffusion in the atmosphere.

Fig 1: (left) Sketch of typical daylight profile of potential temperature. (right) Potential temperature $(\theta)$ at Oakland, CA radiosonde site on August 24, 2020.

The above figure shows an idealized daytime $\theta$ profile which is labeled to indicate the different stability regions:
\begin{equation*}
\begin{split}
&\frac{\partial \theta}{\partial z} > 0 \, \ \text{stable} \\
&\frac{\partial \theta}{\partial z} = 0 \, \ \text{neutral} \\
&\frac{\partial \theta}{\partial z} < 0 \, \ \text{unstable} \\
\end{split}
\end{equation*}

(a) Now consider these profiles from actual radiosonde data from Oakland. There are usually two soundings launched each day, one at 00 UTC and one at 12 UTC (Greenwich mean time), but on this date there is also one at 18 UTC (18 Z). Discuss differences between the profiles.

(b) With this background potential temperature field, if a passive contaminant were released just above the ground, how high would you expect it to mix? Explain in a few words.

(c) What if smoke from a wildfire is released - does your answer change? Consider what you know about large fires. Consider the photos in Figure 4 and refer to them in your brief explanation.

Fig 2: (left) Picture from airplane landing at SFO on the evening of August 24, 2020. (right) Picture of the LNU lightning complex fire on August 19, 2020.

## Math Review

### Question 05

Let
\begin{equation*}
a = \begin{bmatrix}
3\\1\\4
\end{bmatrix}, \,
B = \begin{bmatrix}
0&1\\0&-2\\2&3
\end{bmatrix}, \,
C = \begin{bmatrix}
1&0&-1\\2&3&0\\0&3&4
\end{bmatrix}, \,
d = [1 \ 0 \ 2].
\end{equation*}
Evaluate the following expressions (if possible).

(a) $\mathbf{C} \mathbf{B}$, $\mathbf{B}^T \mathbf{C}^T$

(b) $\mathbf{a}^{T} \mathbf{a}$, $\mathbf{a}^T \mathbf{C} \mathbf{a}$

(c) $\mathbf{B}^{T} \mathbf{a}$, $\mathbf{B} \mathbf{d}$, $\mathbf{a} \mathbf{d}$

(d) $\det{\mathbf{C}}$

(e) $\text{tr}(\mathbf{C})$

### Question 06

Show that the linear transformation $\boldsymbol{\xi} = Ax$ with
\begin{equation*}
\mathbf{A} = \begin{bmatrix}
\cos \theta & -\sin \theta \\
\sin \theta & \cos \theta
\end{bmatrix}, \
\mathbf{x} = \begin{bmatrix}
x_1 \\
x_2
\end{bmatrix}, \
\boldsymbol{\xi} = \begin{bmatrix}
\xi_1 \\
\xi_2
\end{bmatrix}
\end{equation*}
is a counterclockwise rotation of the Cartesian $x_1x_2$-coordinate system in the plane about the origin, where $\theta$ is the angle of rotation. (Hint: start by evaluating for $\mathbf{x}=[1,0]$ and $[0,1]$ when $\theta = 90^{\circ}$.)

### Question 07

Let $\mathbf{a} = [2,-1,5]$, $\mathbf{a} = [2,-1,5]$, $\mathbf{a} = [2,-1,5]$. Find the following expression:

(a) $\mathbf{a} \cdot \mathbf{b}$, $\mathbf{b} \cdot \mathbf{a}$

(b) $\mathbf{a} \times \mathbf{b}$, $\mathbf{b} \times \mathbf{a}$

(c) $(\mathbf{a} \times \mathbf{b} ) \cdot \mathbf{c}$

### Question 08

Find the gradient $\nabla f$ where $f = e^{x^2−y^2} \sin{2xy}$

### Question 09

Given $\mathbf{u} = (yz, zx, xy)$, calculate the gradient $\nabla \mathbf{u}$, the divergence $\nabla \cdot \mathbf{u}$, and the curl $\nabla \times \mathbf{u}$ of this vector field. One should give you a scalar, one a vector, and one a tensor (matrix) as the result. Give an explanation in words to define the gradient, divergence, and curl.

### Question 10

**(a)** Given $u = u(x, y, z)$, $x = x(t)$, $y = y(t)$, $z = z(t)$, expand \frac{du}{dt} using the chain rule.

**(b)** Given $w = x^2 −y^2$ and we define polar coordinates $r,\theta$ by $x = r\cos\theta$, $y = r\sin\theta$, then find $\frac{\partial w}{\partial r}$ and $\frac{\partial w}{\partial \theta}$

### Question 11

Write a Taylor series expansion for the pressure P at point $(x+\Delta x)$ given the pressure at point $x$. Also write an expansion for P at the point $(x − \Delta x)$.

### Question 12

Solve this system of equations. First put into matrix form $(Ax = b)$ and then solve by Gauss elimination. Check your answer.

\begin{equation}
\begin{split}
x_1 + 2x_2 + 3x_3 &= 2 \\
4x_1 + 5x_2 + 6x_3 &= 2 \\
x_1 + 2x_2 + 2x_3 &= 2
\end{split}
\end{equation}

### Question 13

Find the general solution $f(t)$ to this ordinary differential equation:

\begin{equation}
f′′ + 4f′ =0
\end{equation}

### Question 14

Solve this constant co-efficient ordinary differential equation by substituting a solution of the form $f(y) = e^{ky}$:

\begin{equation}
f′′ −16f =0, \ f(0)=1, \ f′(0)=20
\end{equation}

### Question 15

Define the following; calculate them using the math-library (of python) and plot using matplotlib-library (of python):

(a) error function

(b) complementary error function

(c) cosh

(d) sinh

(e) tanh

### Question 16

Consider complex numbers $z_1 = 2+3i$ and $z_2 = 1-2i$. Find:

(a) $z_1 - z_2$

(b) $\|z_1 \|$ (magnitude)

(c) $z_1*z_1$

(d) $z_1*z_2$

(e) $z_1/z_2$

### Question 17

Use $f = e^{ikx} = \cos{kx} + i\sin{kx}$, evaluate $e^{i\pi}$. Take the real and imaginary parts of $f$

### Question 18

Show that

\begin{equation}
\cos(t) = \frac{e^{it} + e^{-it}}{2} \ \text{and} \ \cos(t) = \frac{e^{it} - e^{-it}}{2i}
\end{equation}

### Question 19

Plot the discrete Fourier transform for a square wave (periodic over $2\pi$) for $N = 3,5,7,9$:

\begin{equation}
f(x) = \frac{4}{\pi} \sum_{k=1,3,5,...}^{N} {\frac{1}{k} \sin{(kx)}}
\end{equation}

It may also help to plot each of the sin waves separately so you can see how each term is contributing. See this page http://mathworld.wolfram.com/FourierSeriesSquareWave.html

### Question 20

Consider this partial differential equation:

\begin{equation}
\frac{\partial C}{\partial t} = \alpha \frac{\partial^2 C}{\partial x^2}
\end{equation}

Given an initial condition of $C(0) = C_0 \exp{(ikx)}$, use separation of variables to assume a solution of the form $C(x, t) = a(t) \exp{(ikx)}$ and solve for $C(x,t)$.