Class: PostRunner::SleepCycle

Inherits:
Object
  • Object
show all
Defined in:
lib/postrunner/SleepCycle.rb

Overview

A sleep cycle consists of several sleep phases. This class is used to gather and store the relevant data of a sleep cycle. Data is analzyed and stored with a one minute granularity. Time values are stored as minutes past the zero_idx_time.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(zero_idx_time, start_idx, prev_cycle = nil) ⇒ SleepCycle

Create a new SleepCycle record.

Parameters:

  • zero_idx_time (Time)

    This is the time of the 0-th minute. All time values are stored as minutes past this time.

  • start_idx (Fixnum)

    Time when the sleep cycle starts. We may start with an appromated value that gets fine tuned later on.

  • prev_cycle (SleepCycle) (defaults to: nil)

    A reference to the preceding sleep cycle or nil if this is the first cycle of the analyzed period.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/postrunner/SleepCycle.rb', line 57

def initialize(zero_idx_time, start_idx, prev_cycle = nil)
  @zero_idx_time = zero_idx_time
  @start_idx = start_idx
  # These values will be determined later.
  @end_idx = nil
  # Every sleep cycle has at most one high/low heart rate transition and
  # one low/high transition. These variables store the time of these
  # transitions or nil if the transition does not exist. Every cycle must
  # have at least one of these transitions to be a valid cycle.
  @high_low_trans_idx = @low_high_trans_idx = nil
  @prev_cycle = prev_cycle
  # Register this cycle as successor of the previous cycle.
  prev_cycle.next_cycle = self if prev_cycle
  @next_cycle = nil
  # Array holding the sleep phases of this cycle
  @phases = []
  # A hash with the total durations (in secods) of the various sleep
  # phases.
  @total_seconds = Hash.new(0)
end

Instance Attribute Details

#end_idxObject

Returns the value of attribute end_idx.



46
47
48
# File 'lib/postrunner/SleepCycle.rb', line 46

def end_idx
  @end_idx
end

#high_low_trans_idxObject

Returns the value of attribute high_low_trans_idx.



46
47
48
# File 'lib/postrunner/SleepCycle.rb', line 46

def high_low_trans_idx
  @high_low_trans_idx
end

#low_high_trans_idxObject

Returns the value of attribute low_high_trans_idx.



46
47
48
# File 'lib/postrunner/SleepCycle.rb', line 46

def low_high_trans_idx
  @low_high_trans_idx
end

#next_cycleObject

Returns the value of attribute next_cycle.



46
47
48
# File 'lib/postrunner/SleepCycle.rb', line 46

def next_cycle
  @next_cycle
end

#prev_cycleObject

Returns the value of attribute prev_cycle.



46
47
48
# File 'lib/postrunner/SleepCycle.rb', line 46

def prev_cycle
  @prev_cycle
end

#start_idxObject

Returns the value of attribute start_idx.



46
47
48
# File 'lib/postrunner/SleepCycle.rb', line 46

def start_idx
  @start_idx
end

#total_secondsObject (readonly)

Returns the value of attribute total_seconds.



45
46
47
# File 'lib/postrunner/SleepCycle.rb', line 45

def total_seconds
  @total_seconds
end

#totalsObject (readonly)

Returns the value of attribute totals.



45
46
47
# File 'lib/postrunner/SleepCycle.rb', line 45

def totals
  @totals
end

Instance Method Details

#adjust_cycle_boundaries(phases) ⇒ Object

Initially, we use the high/low heart rate transition to mark the end of the cycle. But it’s really the end of the REM phase that marks the end of a sleep cycle. If we find a REM phase, we use its end to adjust the sleep cycle boundaries.

Parameters:

  • phases (Array)

    List of symbols that describe the sleep phase at at the minute corresponding to the Array index.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/postrunner/SleepCycle.rb', line 102

def adjust_cycle_boundaries(phases)
  end_of_rem_phase_idx = nil
  @start_idx.upto(@end_idx) do |i|
    end_of_rem_phase_idx = i if phases[i] == :rem
  end
  if end_of_rem_phase_idx
    # We have found a REM phase. Adjust the end_idx of this cycle
    # accordingly.
    @end_idx = end_of_rem_phase_idx
    if @next_cycle
      # If we have a successor phase, we also adjust the start.
      @next_cycle.start_idx = end_of_rem_phase_idx + 1
    end
  end
end

#detect_phases(phases) ⇒ Object

Gather a list of SleepPhase objects that describe the sequence of sleep phases in the provided Array.

Parameters:

  • phases (Array)

    List of symbols that describe the sleep phase at at the minute corresponding to the Array index.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/postrunner/SleepCycle.rb', line 122

def detect_phases(phases)
  @phases = []
  current_phase = phases[0]
  current_phase_start = @start_idx

  @start_idx.upto(@end_idx) do |i|
    if (current_phase && current_phase != phases[i]) || i == @end_idx
      # We found a transition in the sequence. Create a SleepPhase object
      # that describes the prepvious segment and add it to the @phases
      # list.
      @phases << (p = SleepPhase.new(idx_to_time(current_phase_start),
                                     idx_to_time(i == @end_idx ? i + 1 : i),
                                     current_phase))
      # Add the duration of the phase to the corresponding sum in the
      # @total_seconds Hash.
      @total_seconds[current_phase] += p.duration

      # Update the variables that track the start and kind of the
      # currently read phase.
      current_phase_start = i
      current_phase = phases[i]
    end
  end
end

#from_timeTime

The start time of the cycle as Time object

Returns:

  • (Time)


80
81
82
# File 'lib/postrunner/SleepCycle.rb', line 80

def from_time
  idx_to_time(@start_idx)
end

#has_deep_sleep_phase?Boolean

Check if the cycle has a deep sleep phase.

Returns:

  • (Boolean)

    True of one of the phases is NREM3 phase. False otherwise.



160
161
162
163
164
165
166
167
# File 'lib/postrunner/SleepCycle.rb', line 160

def has_deep_sleep_phase?
  # A real deep sleep phase must be at least 10 minutes long.
  @phases.each do |p|
    return true if p.phase == :nrem3 && p.duration > 10 * 60
  end

  false
end

#has_leading_deep_sleep_phase?Boolean

Check if any of the previous cycles that are directly attached have a deep sleep cycle.

Returns:

  • (Boolean)

    True if it has a leading sleep cycle.



172
173
174
175
176
177
# File 'lib/postrunner/SleepCycle.rb', line 172

def has_leading_deep_sleep_phase?
  return false if @prev_cycle.nil? || @start_idx != @prev_cycle.end_idx + 1

  @prev_cycle.has_deep_sleep_phase? ||
    @prev_cycle.has_leading_deep_sleep_phase?
end

#has_trailing_deep_sleep_phase?Boolean

Check if any of the trailing cycles that are directly attached have a deep sleep cycle.

Returns:

  • (Boolean)

    True if it has a trailing sleep cycle.



182
183
184
185
186
187
# File 'lib/postrunner/SleepCycle.rb', line 182

def has_trailing_deep_sleep_phase?
  return false if @next_cycle.nil? || @end_idx + 1 != @next_cycle.start_idx

  @next_cycle.has_deep_sleep_phase? ||
    @next_cycle.has_trailing_deep_sleep_phase?
end

#is_wake_cycle?Boolean

Check if this cycle is really a sleep cycle or not. A sleep cycle must have at least one deep sleep phase or must be part of a directly attached series of cycles that contain a deep sleep phase.

Returns:

  • (Boolean)

    True if not a sleep cycle, false otherwise.



151
152
153
154
# File 'lib/postrunner/SleepCycle.rb', line 151

def is_wake_cycle?
  !has_deep_sleep_phase? && !has_leading_deep_sleep_phase? &&
    !has_trailing_deep_sleep_phase?
end

#to_timeTime

The end time of the cycle as Time object.

Returns:

  • (Time)


86
87
88
# File 'lib/postrunner/SleepCycle.rb', line 86

def to_time
  idx_to_time(@end_idx + 1)
end

Remove this cycle from the cycle chain.



91
92
93
94
# File 'lib/postrunner/SleepCycle.rb', line 91

def unlink
  @prev_cycle.next_cycle = @next_cycle if @prev_cycle
  @next_cycle.prev_cycle = @prev_cycle if @next_cycle
end