Skip to content

Commit c7be3ef

Browse files
authored
Cuda (#19)
* Add hamiltonian to __init__ and target.py * Add runge-kutta propagator * structure * Make the scan run * Profiling traces * refactor efp * trive.ini fix * hamiltonian folder structure also fixed unhelpful settings of target.py (set while debugging) * default name * TRSF hamiltonian * Initial cuda implementation, Work in Progress * More cuda device functions * Kernel and memory transfer -- Compiles, but has memory access issue * silence prints, Hamiltoinian POINTER, datatypes * datatypes, remove memoryview, hardcoded mu (temp) * datatypes, fix dot, statically allocated hamiltonian matrices, hard coded rho_0 * Remove extraneous prints, use sys.argv for number of points, use perf_counter for timing * remove multiprocessing * Remove import from propagate * Scaling results * Report * README
1 parent 145d3d8 commit c7be3ef

35 files changed

Lines changed: 1711 additions & 345 deletions

README.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,66 @@
11
WrightSim
22
=========
33
A simulation package for multidimensional spectroscopy.
4+
5+
6+
Installation
7+
------------
8+
9+
.. code-block:: bash
10+
11+
$ git clone https://github.com/wright-group/WrightSim
12+
$ cd WrightSim
13+
$ python setup.py develop
14+
15+
Note: This will install all required dependencies.
16+
PyCUDA is not a required dependency in general, but is required if GPU simulations are desired.
17+
18+
.. code-block:: bash
19+
20+
$ pip install pycuda
21+
22+
PyCUDA requires an Nvida graphics card and drivers, and the CUDA libraries installed.
23+
24+
Usage
25+
-----
26+
27+
An example script is provided at ``./scripts/target.py``
28+
29+
This script can be modified to suit an individual simulation.
30+
31+
The basic steps are:
32+
33+
#. Select an experiment
34+
#. Set up the axes of the scan
35+
#. Set the time interfal and buffers
36+
#. Create a Hamiltonian object
37+
#. Run the scan
38+
#. (optional) review the results
39+
40+
Level of parallelism is selected by the ``mp`` parameter of teh ``exp.run`` method.
41+
42+
- ``True`` or ``"cpu"`` enables CPU multiprocessing
43+
- ``"gpu"`` enables CUDA
44+
- ``False`` or ``""`` runs in single threaded mode
45+
46+
47+
The script is set up to read the dimensions from arguments.
48+
it can be run like so:
49+
50+
.. code-block:: bash
51+
52+
$ ./target.py 32 16
53+
54+
55+
This will run a 3D simulation of 32x32x16 Freq-Freq-Delay.
56+
57+
It will print the time it took to compute.
58+
59+
An example of how to visualize results is provided at the end of the script, but is not active, as it only works for 2D scans.
60+
61+
To convert this script to a 2D scan and visualize, uncomment the lines which set ``exp.d2.points`` and ``exp.d2.active`` and remove the triple quotes around the plotting routine.
62+
There it will ignore the second argument, producing a 32x32 2D frequency spectrum. (The second argument is still parsed, so it is required, just ignored.)
63+
64+
65+
66+

WrightSim/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# flake8: noqa
22

33
from . import experiment
4+
from . import hamiltonian
45
from . import integration
56
from . import measure
67
from . import response

WrightSim/experiment/__init__.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33

44
import os
5-
import collections
65

76
import WrightTools as wt
87

9-
from . import experiment
10-
from . import pulse
8+
from . import _pulse
9+
from ._experiment import Experiment
1110
from ._axis import Axis
1211

1312

@@ -27,7 +26,10 @@ def builtin(name):
2726

2827
def from_ini(p):
2928
ini = wt.kit.INI(p)
30-
# get axes
29+
# pulse
30+
pulse_class_name = ini.read('main', 'pulse class name')
31+
pulse_class = _pulse.__dict__[pulse_class_name]
32+
# axes
3133
axis_names = ini.sections
3234
axis_names.remove('main')
3335
axes = []
@@ -37,12 +39,11 @@ def from_ini(p):
3739
pulses = ini.read(name, 'pulses')
3840
parameter = ini.read(name, 'parameter')
3941
axis = Axis(points=points, units=None, name=name, active=active, pulses=pulses,
40-
parameter=parameter)
42+
parameter=parameter, cols=pulse_class.cols)
4143
axes.append(axis)
4244
# construct experiment object
4345
name = ini.read('main', 'name')
4446
pm = ini.read('main', 'pm')
45-
pulse_class_name = ini.read('main', 'pulse class name')
46-
e = experiment.Experiment(axes=axes, name=name, pm=pm, pulse_class_name=pulse_class_name)
47+
e = Experiment(axes=axes, name=name, pm=pm, pulse_class=pulse_class)
4748
# finish
4849
return e

WrightSim/experiment/_axis.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# --- import --------------------------------------------------------------------------------------
55

66

7+
import numpy as np
8+
9+
710
# --- define --------------------------------------------------------------------------------------
811

912

@@ -15,10 +18,28 @@
1518

1619
class Axis(object):
1720

18-
def __init__(self, name, units=None, points=None, active=False, parameter=None, pulses=None):
21+
def __init__(self, name, units=None, points=None, active=False, parameter=None, pulses=None,
22+
cols=None):
1923
self.name = name
2024
self.units = units
2125
self.points = points
2226
self.active = active
2327
self.parameter = parameter
2428
self.pulses = pulses
29+
self.cols = cols
30+
self._coords = None
31+
32+
def __repr__(self):
33+
return "<WrightSim.Axis '{0}' at {1}>".format(self.name, id(self))
34+
35+
@property
36+
def coords(self):
37+
if self._coords is not None:
38+
return self._coords
39+
parameter_index = self.cols.index(self.parameter)
40+
coords = np.zeros((len(self.pulses), 2), dtype=np.int)
41+
for i, pulse in enumerate(self.pulses):
42+
# specify pulse and then efield param of pulse
43+
coords[i] = [pulse, parameter_index]
44+
self._coords = coords
45+
return coords
Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import WrightTools as wt
99

10-
from . import pulse
10+
from . import _pulse
11+
from ._scan import Scan
1112

1213

1314
# --- define --------------------------------------------------------------------------------------
@@ -28,7 +29,7 @@
2829
class Experiment:
2930
"""Experiment."""
3031

31-
def __init__(self, axes, name, pm, pulse_class_name):
32+
def __init__(self, axes, name, pm, pulse_class):
3233
# basic attributes
3334
self.axes = axes
3435
for a in self.axes:
@@ -40,11 +41,11 @@ def __init__(self, axes, name, pm, pulse_class_name):
4041
self.early_buffer = early_buffer
4142
self.late_buffer = late_buffer
4243
# pulse
43-
self.pulse_class_name = pulse_class_name
44-
self.pulses = [pulse.__dict__[self.pulse_class_name]() for _ in self.pm]
44+
self.pulse_class = pulse_class
45+
self.pulses = [self.pulse_class() for _ in self.pm]
4546

4647
def __repr__(self):
47-
return 'WrightSim.Experiment object \'{0}\' at {1}'.format(self.name, str(id(self)))
48+
return '<WrightSim.Experiment object \'{0}\' at {1}>'.format(self.name, str(id(self)))
4849

4950
@property
5051
def active_axes(self):
@@ -54,6 +55,25 @@ def active_axes(self):
5455
def axis_names(self):
5556
return [a.name for a in self.axes]
5657

58+
def run(self, hamiltonian, mp=True):
59+
"""Run the experiment.
60+
61+
Parameters
62+
----------
63+
hamiltonian : WrightSim Hamiltonian
64+
Hamiltonian.
65+
mp : boolean (optional)
66+
Toggle CPU multiprocessing. Default is True.
67+
68+
Returns
69+
-------
70+
WrightSim Scan
71+
Scan that was run."""
72+
out = Scan(self, hamiltonian)
73+
out.run(mp=mp)
74+
# finish
75+
return out
76+
5777
def set_axis(self, axis_name, points):
5878
'''
5979
Activate and define points for one of the experimental axes.

WrightSim/experiment/_pulse.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# --- import --------------------------------------------------------------------------------------
2+
3+
4+
import numpy as np
5+
6+
7+
# --- define --------------------------------------------------------------------------------------
8+
9+
10+
wn_to_omega = 2 * np.pi * 3e-5 # omega is radians / fs
11+
12+
13+
# factor used to convert FWHM to stdev for function definition
14+
# gaussian defined such that FWHM,intensity scale = sigma * 2 * sqrt(ln(2))
15+
# (i.e. FWHM, intensity scale ~ 1.67 * sigma, amplitude scale)
16+
FWHM_to_sigma = 1. / (2 * np.sqrt(np.log(2)))
17+
18+
19+
# TODO: these should probably not be hard-coded
20+
timestep = 4.0
21+
early_buffer = 100.0
22+
late_buffer = 400.0
23+
24+
# --- functions -----------------------------------------------------------------------------------
25+
26+
27+
def _get_t(obj, d):
28+
"""
29+
returns the t array that is appropriate for the given pulse
30+
parameters; needs the appropriate buffers/timestep as well
31+
"""
32+
if obj.fixed_bounds:
33+
t_min = obj.fixed_bounds_min
34+
t_max = obj.fixed_bounds_max
35+
else:
36+
t_min = d.min() - obj.early_buffer
37+
t_max = d.max() + obj.late_buffer
38+
# span up to and including t_max now
39+
t = np.arange(t_min, t_max + obj.timestep, obj.timestep)
40+
# alternate form:
41+
return t
42+
43+
44+
# --- class ---------------------------------------------------------------------------------------
45+
46+
47+
class GaussRWA:
48+
"""
49+
returns an array of gaussian values with arguments of
50+
input array vec,
51+
mean mu,
52+
standard deviation sigma
53+
amplitude amp
54+
frequency freq
55+
phase offset p
56+
57+
we are in rw approx, so if interaction is supposed to be negative, give
58+
frequency a negative sign
59+
"""
60+
# dictionary to associate array position to pulse attribute
61+
cols = ['A', # amplitude, a.u.
62+
's', # pulse FWHM (fs),
63+
'd', # pulse center delay (fs),
64+
'w', # frequency (wn),
65+
'p'] # phase shift (radians)
66+
# initial vars come from misc module, just as with scan module
67+
timestep = timestep
68+
early_buffer = early_buffer
69+
late_buffer = late_buffer
70+
# fixed bounds set to true if you want fixed time indices for the pulses
71+
fixed_bounds = False
72+
fixed_bounds_min = None
73+
fixed_bounds_max = None
74+
75+
@classmethod
76+
def pulse(cls, eparams, pm=None):
77+
"""Get efields for each pulse.
78+
79+
Parameters
80+
----------
81+
eparams : 2D numpy ndarray
82+
Array in (pulse, parameter). Refer to cols attribute for list
83+
of parameters.
84+
pm : iterable of integers
85+
Phase matching. If None, fully positive phase matching is
86+
assumed.
87+
88+
Returns
89+
-------
90+
(1D array, 1D array)
91+
Tuple of 1D numpy ndarray time (fs) and complex efield (a.u.).
92+
"""
93+
# import if the size is right
94+
area = eparams[:, 0].copy().astype(float)
95+
sigma = eparams[:, 1].copy().astype(float)
96+
mu = eparams[:, 2].copy().astype(float)
97+
freq = eparams[:, 3].copy().astype(float)
98+
p = eparams[:, 4].copy().astype(float)
99+
# proper unit conversions
100+
sigma *= FWHM_to_sigma
101+
freq *= wn_to_omega
102+
# redefine delays s.t. always relative to the first pulse listed
103+
offset = mu[0]
104+
# subtract off the value
105+
mu -= offset
106+
# normalize amplitdue to stdev
107+
y = area / (sigma * np.sqrt(2 * np.pi))
108+
#print y, sigma, mu, freq, p
109+
# calculate t
110+
t = cls.get_t(mu)
111+
# incorporate complex conjugates if necessary
112+
if pm is None:
113+
cc = np.ones((eparams.shape[-1]))
114+
else:
115+
cc = np.sign(pm)
116+
x = np.exp(-1j*(cc[:,None]*(freq[:,None]*(t[None,:] - mu[:,None])+p[:,None])))
117+
x*= y[:,None] * np.exp(-(t[None,:] - mu[:,None])**2 / (2*sigma[:,None]**2) )
118+
return t, x
119+
120+
@classmethod
121+
def get_t(cls, d):
122+
return _get_t(cls, d)

0 commit comments

Comments
 (0)