Lights

Defines a helper class for creating light schedules and implements typical schedules used in circadian models

Overview

The Lights module streamlines the process of creating light schedules for circadian models. Its main class is LightSchedule which facilitates creating light functions of time. LightSchedule objects can be added, subtracted, and concatenated between each other to create custom light schedules.

Creation and visualization

Light schedules can be created by passing either a float value or a function to the LightSchedule constructor. For example, we can create a schedule of constant light by:

constant_schedule = LightSchedule(100.0)

and plot it between 0 and 72 hours using the plot method:

ax = constant_schedule.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');
ax.set_ylim(0.0, 120.0);

If we want to create a schedule that varies over time, we can pass a function to the constructor:

def smooth_pulse_function(time):
    rise = np.tanh((time - 7.0))
    fall = np.tanh((time - 20.0))
    y = 100 * (rise - fall) / 2.0
    return y

smooth_pulse = LightSchedule(smooth_pulse_function)
ax = smooth_pulse.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

Additionally, the LightSchedule constructor accepts a period argument which allows us to specify the periodicity of the schedule. For example, we can make the smooth pulse repeat every 24 hours:

smooth_pulse = LightSchedule(smooth_pulse_function, period=24.0)
ax = smooth_pulse.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

Typically, we would like to create schedules that are composed of on-and-off pulses of light. We can do this via the LightSchedule.from_pulse() function. For example, we can create a periodic schedule with 16 hours of light and 8 hours of darkness by:

on_off_schedule = LightSchedule.from_pulse(100.0, start=8, duration=16.0, period=24.0)
ax = on_off_schedule.plot(0.0, 72.0)
ax.set_ylim(-5, 110);
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

see the API documentation for a description of the parameters that can be passed to LightSchedule.from_pulse(), including adding a baseline to the pulse via baseline.

The resulting LightSchedule objects are callable and return the value of the schedule at the specified times

times = np.linspace(0.0, 72.0, 10)
print(on_off_schedule(times))
[  0. 100. 100.   0. 100. 100.   0. 100. 100.   0.]

Addition and subtraction

LightSchedule objects can be combined using the + and - operators. For example, we can add a pulse of light at 40 hours to our on_off_schedule by:

single_pulse = LightSchedule.from_pulse(20.0, start=40, duration=2.0)
combined_schedule = on_off_schedule + single_pulse
ax = combined_schedule.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

or subtract it:

combined_schedule = on_off_schedule - single_pulse
ax = combined_schedule.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

Concatenation

LightSchedule objects can be concatenated in time using the concatenate_at() method. For example, we can create a schedule where the first 48 hours consist of 8 hours of darkness and 16 hours of light, but after that the schedule is constant at 50 lux:

regular_schedule = LightSchedule.from_pulse(100.0, start=8, duration=16.0, period=24.0)
constant_light = LightSchedule(50.0)
concatenated_schedule = regular_schedule.concatenate_at(constant_light, 48.0)
ax = concatenated_schedule.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

The approach works for all types of schedules. For example, we can concatenate smooth_pulse to on_off_schedule at 28 hours by:

on_off_to_smooth = on_off_schedule.concatenate_at(smooth_pulse, 28.0)
ax = on_off_to_smooth.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

It is important to note that the default behavior of schedule_1.concatenate_at(schedule_2, timepoint) is to shift the time=0 of schedule_2 to timepoint. This can be appreciated if we concatenate the same schedules as above, but with timepoint=40:

on_off_to_smooth = on_off_schedule.concatenate_at(smooth_pulse, 40.0)
ax = on_off_to_smooth.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

we see that the second on-and-off pulse is suddenly interrupted by the beginning of the smoot pulse. This is because the time=0 of smooth_pulse is shifted to timepoint=40. If we want to concatenate the schedules without shifting the time=0 of schedule_2 (keep the time=0 of schedule_2 aligned with that of schedule_1), we can pass shift=False to the concatenate_at() method:

on_off_to_smooth = on_off_schedule.concatenate_at(smooth_pulse, 40.0, shift_schedule=False)
ax = on_off_to_smooth.plot(0.0, 72.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');

Typical light schedules

The LightSchedule class implements several helper functions to obtain common light schedules:

Check out the API documentation for a description of the parameters that can be passed to each function.

Regular light

LightSchedule.Regular is a typical light schedule that repeats every 24 hours. The default schedule is 16 hours of light and 8 hours of darkness, but this can be changed by passing lights_on and lights_off to the constructor.

regular_light = LightSchedule.Regular()
ax = regular_light.plot(0.0, 24*7.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');
ax.figure.set_size_inches(16, 4);
# add a vertical line to the start of each day in dashed gray
for day in range(8):
    ax.axvline(day*24.0, color='gray', linestyle='--');
ax.set_xticks(np.arange(0, 24.0*8.0, 12));
ax.set_xlim(0.0, 24.0*7.0);

Shift worker

LightSchedule.ShiftWork approximates what a typical light schedule looks for some shift workers. The schedule is periodic over a whole work week (determined by the sum of days_on and days_off) and implements transitions between workdays and days off. The default schedule is:

shift_schedule = LightSchedule.ShiftWork()
ax = shift_schedule.plot(0.0, 24.0*8.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');
ax.figure.set_size_inches(16, 4);
# add a vertical line to the start of each day in dashed gray
for day in range(8):
    ax.axvline(day*24.0, color='gray', linestyle='--');
ax.set_xticks(np.arange(0, 24.0*8.0, 12));
days_on = 5
days_off = 2
lights_off_workday = 9.0
lights_on_workday = 17.0
time_last_workday = lights_off_workday + 24*(days_on-1)
time_last_day_off = lights_on_workday + 24.0*(days_on + days_off - 1)
time_first_workday = time_last_day_off + lights_on_workday
# set background color to light red for the workdays
ax.axvspan(0.0, time_last_workday, facecolor='r', alpha=0.1, label='Workdays');
ax.axvspan(time_last_day_off, time_first_workday, facecolor='r', alpha=0.1);
# set background color to light green for days off
ax.axvspan(time_last_workday, time_last_day_off, facecolor='g', alpha=0.1, label='Days off');
# set limits to be a week
ax.set_xlim(0.0, 24.0*7.0);
# place legend at the top of the plot
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2);

Slam shift

LightSchedule.SlamShift approximates the light schedule that slam shift workers experience when changing shifts. The schedule starts with before_days where the worker is on a regular schedule and then shifts to a new schedule by shift hours. Before the shift happens, there’s a transition via sleep banking. The default schedule is:

slam_shift = LightSchedule.SlamShift()
ax = slam_shift.plot(0.0, 24.0*12.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');
ax.figure.set_size_inches(16, 4);
# add a vertical line to the start of each day in dashed gray
for day in range(13):
    ax.axvline(day*24.0, color='gray', linestyle='--');
ax.set_xticks(np.arange(0, 24.0*12.0, 12));
# set background color to light red for the days before the shift
ax.axvspan(0.0, 24.0*5, facecolor='r', alpha=0.1, label='Before shift');
# set background color to light green for days after the shift
ax.axvspan(24.0*5, 24.0*12.0, facecolor='g', alpha=0.1, label='After shift');
# place legend at the top of the plot
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2);
ax.set_xlim(0.0, 24.0*12.0);

Social jet lag

LightSchedule.SocialJetlag implements a light schedule where the person stays up late on weekends while maintaining a regular schedule during the week. The schedule is periodic over the sum of num_regular_days and num_jetlag_days.

social_jetlag = LightSchedule.SocialJetlag()
ax = social_jetlag.plot(0.0, 24.0*8.0)
ax.set_xlabel('Time (hours)');
ax.set_ylabel('Light (lux)');
ax.figure.set_size_inches(16, 4);
# add a vertical line to the start of each day in dashed gray
for day in range(9):
    ax.axvline(day*24.0, color='gray', linestyle='--');
ax.set_xticks(np.arange(0, 24.0*8.0, 12));
# set background color to light red for the regular days
ax.axvspan(0.0, 24.0*5, facecolor='r', alpha=0.1, label='Regular days');
ax.axvspan(24.0*7, 24.0*8.0, facecolor='r', alpha=0.1);
# set background color to light green for days with jetlag
ax.axvspan(24.0*5, 24.0*7.0, facecolor='g', alpha=0.1, label='Social Jetlag days');
# place legend at the top of the plot
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2);
ax.set_xlim(0.0, 24.0*8.0);

API documentation


source

LightSchedule

 LightSchedule (light:Callable[[float],float], period:float=None)

Helper class for creating light schedules

Type Default Details
light typing.Callable[[float], float] function that takes in a time value and returns a float, if a float is passed, then the light function is a constant set to that lux value
period float None period in hours, if None, then the light pulse is not repeated. Must be positive
Returns None

source

LightSchedule.from_pulse

 LightSchedule.from_pulse (lux:float, start:float, duration:float,
                           period:float=None, baseline:float=0.0)

Define a light schedule with a single (or a repetitive) light pulse

Type Default Details
lux float light intensity of the pulse in lux. Must be nonnegative
start float start time in hours
duration float duration in hours. Must be positive
period float None period in hours, if None, then the light pulse is not repeated. Must be positive
baseline float 0.0 baseline intensity outside of the light pulse in lux. Must be nonnegative
Returns LightSchedule

source

LightSchedule.concatenate_at

 LightSchedule.concatenate_at (schedule:__main__.LightSchedule,
                               timepoint:float, shift_schedule:bool=True)

Concatenate two LightSchedules at the provided timepoint. When shift_schedule=True, schedule is shifted in time by timepoint. Not shifted otherwise

Type Default Details
schedule LightSchedule another LightSchedule object
timepoint float timepoint (in hours) at which schedules are concatenated
shift_schedule bool True if True, then the schedule is shifted by the timepoint value
Returns LightSchedule

source

LightSchedule.plot

 LightSchedule.plot (plot_start_time:float, plot_end_time:float,
                     num_samples:int=10000, ax=None, *args, **kwargs)

Plot the light function between start_time and end_time with num_samples samples. Accepts matplotlib *args and **kwargs

Type Default Details
plot_start_time float start time of the plot in hours
plot_end_time float end time of the plot in hours
num_samples int 10000 number of samples to plot
ax NoneType None matplotlib axis to plot on
args
kwargs
Returns Axes

source

LightSchedule.Regular

 LightSchedule.Regular (lux:float=150.0, lights_on:float=7.0,
                        lights_off:float=23.0)

Create a regular light and darkness 24 hour schedule

Type Default Details
lux float 150.0 intensity of the light in lux
lights_on float 7.0 time of the day for lights to come on in hours
lights_off float 23.0 time of the day for lights to go off in hours
Returns LightSchedule

source

LightSchedule.ShiftWork

 LightSchedule.ShiftWork (lux:float=150.0, days_on:int=5, days_off:int=2,
                          lights_on_workday:float=17.0,
                          lights_off_workday:float=9.0,
                          lights_on_day_off:float=9.0,
                          lights_off_day_off:float=24.0)

Create a light schedule for a shift worker

Type Default Details
lux float 150.0 lux intensity of the light. Must be a nonnegative float or int
days_on int 5 number of days on the night shift. Must be a positive int
days_off int 2 number of days off shift. Must be a positive int
lights_on_workday float 17.0 hour of the day for lights to come on on a workday. Must be between 0.0 and 24.0
lights_off_workday float 9.0 hour of the day for lights to go off on a workday. Must be between 0.0 and 24.0
lights_on_day_off float 9.0 hour of the day for lights to come on on a day off. Must be between 0.0 and 24.0
lights_off_day_off float 24.0 hour of the day for lights to go off on a day off. Must be between 0.0 and 24.0
Returns LightSchedule

source

LightSchedule.SlamShift

 LightSchedule.SlamShift (lux:float=150.0, shift:float=8.0,
                          before_days:int=5, starting_lights_on:float=7.0,
                          starting_lights_off:float=23.0)

Create a light schedule for a shift worker under a slam shift

Type Default Details
lux float 150.0 intensity of the light in lux
shift float 8.0 shift in the light schedule in hours
before_days int 5 days before the shift occurs
starting_lights_on float 7.0 time of the day for lights to come on
starting_lights_off float 23.0 time of the day for lights to go off
Returns LightSchedule

source

LightSchedule.SocialJetlag

 LightSchedule.SocialJetlag (lux:float=150.0, num_regular_days:int=5,
                             num_jetlag_days:int=2,
                             hours_delayed:float=2.0,
                             regular_days_lights_on:float=7.0,
                             regular_days_lights_off:float=23.0)

Create a light schedule that simulates the effects of staying up late on the weekend (social jetlag)

Type Default Details
lux float 150.0 intensity of the light in lux
num_regular_days int 5 number of days with a regular schedule
num_jetlag_days int 2 number of days with a delayed schedule
hours_delayed float 2.0 number of hours to delay the schedule on the jetlag days
regular_days_lights_on float 7.0 hour of the day for lights to come on
regular_days_lights_off float 23.0 hour of the day for lights to go off
Returns LightSchedule