Coverage for src/cgse_dummy/sim_data.py: 78%

1 import time

2

3 import numpy as np

4 from datetime import datetime, timedelta

5

6

7 def _simulate_temperature_data(

8 start_temp=25, # Room temperature (ºC)

9 first_target=-70, # First cooling target (ºC)

10 second_target=-40, # Heating target (ºC)

11 final_target=-180, # Final cooling target (ºC)

12 total_time=720, # Total time in minutes (12 hours)

13 sample_rate=10, # Seconds between measurements

14) -> tuple[list[datetime], list[float]]:

15 """

16 Simulate temperature sensor data with a plateau phase and a cooling period.

17

18 The default arguments will generate temperature data for twelve hours cooling

19 down from room temperature to -180ºC. We introduced two plateau of one hour

20 duration, (1) the first plateau at -70ºC starts after one hour of cooling and

21 (2) the second plateau at -40ºC starts after heating for one hour and twenty

22 minutes. After the second plateau we cool down until -180ºC.

23

24 This code was generated by Claude 3.5 Sonnet with small adjustments.

25

26 Returns:

27 - timestamp and temperature as a tuple.

28 """

29 # Calculate number of samples

30 n_samples = int((total_time * 60) / sample_rate)

31

32 # Create time array in minutes

33 time = np.linspace(0, total_time, n_samples)

34

35 # Initialize temperature array

36 temp = np.zeros(n_samples)

37

38 # Define phase transition points (in minutes)

39 phase1_end = 60 # First cooling phase

40 phase2_end = 120 # First plateau

41 phase3_end = 200 # Heating phase

42 phase4_end = 260 # Second plateau

43

44 # Find indices for different phases

45 idx1 = np.where(time >= phase1_end)[0][0]

46 idx2 = np.where(time >= phase2_end)[0][0]

47 idx3 = np.where(time >= phase3_end)[0][0]

48 idx4 = np.where(time >= phase4_end)[0][0]

49

50 # Phase 1: Initial cooling to -70°C (exponential)

51 initial_time = time[:idx1]

52 temp[:idx1] = start_temp + (first_target - start_temp) * (

53 1 - np.exp(-initial_time / 15) # Time constant for first cooling

54 )

55

56 # Phase 2: First plateau at -70°C

57 temp[idx1: idx2] = first_target

58

59 # Phase 3: Heating to -40°C (exponential approach)

60 heating_time = time[idx2: idx3] - time[idx2]

61 temp[idx2: idx3] = first_target + (second_target - first_target) * (

62 1 - np.exp(-heating_time / 10) # Time constant for heating

63 )

64

65 # Phase 4: Second plateau at -40°C

66 temp[idx3: idx4] = second_target

67

68 # Phase 5: Final cooling to -180°C

69 final_time = time[idx4:] - time[idx4]

70 temp[idx4:] = second_target + (final_target - second_target) * (

71 1 - np.exp(-final_time / 100) # Time constant for final cooling

72 )

73

74 # Add different levels of noise for each phase

75 base_noise = np.random.normal(0, 0.2, n_samples)

76

77 # Add occasional spikes

78 spike_locations = np.random.choice(

79 n_samples, size=int(n_samples * 0.005), replace=False

80 )

81 spike_noise = np.zeros(n_samples)

82 spike_noise[spike_locations] = np.random.normal(0, 3, size=len(spike_locations))

83

84 # Add slow drift

85 drift = np.sin(np.linspace(0, 8 * np.pi, n_samples)) * 0.8

86

87 # Combine all noise components

88 total_noise = base_noise + spike_noise + drift

89 temp += total_noise

90

91 # Create timestamps

92 start_time = datetime.now()

93 timestamps = [

94 start_time + timedelta(seconds=i * sample_rate) for i in range(n_samples)

95 ]

96

97 return timestamps, temp.tolist()

98

99

100 class SimulatedTemperature:

101 """

102 This is an iterator over simulated temperature data.

103

104 Generate simulate temperature sensor data with a plateau phase and a cooling period.

105

106 The default arguments will generate temperature data for twelve hours cooling

107 down from room temperature to -180ºC. We introduced two plateau of one hour

108 duration, (1) the first plateau at -70ºC starts after one hour of cooling and

109 (2) the second plateau at -40ºC starts after heating for one hour and twenty

110 minutes. After the second plateau we cool down until -180ºC.

111

112 """

113

114 def __init__(self):

115 self.timestamps: list[datetime]

116 self.temperatures: list[float]

117

118 self.timestamps, self.temperatures = _simulate_temperature_data()

119

120 self.count: int = len(self.timestamps)

121 """The number of data points."""

122 self.idx: int = 0

123 """A pointer in the list of data points, the next data point to return."""

124

125 def __iter__(self):

126 return self

127

128 def __next__(self):

129 x, y = self.timestamps[self.idx], self.temperatures[self.idx]

130 if self.idx >= self.count:

131 raise StopIteration

132 self.idx += 1

133 return x, y

134

135

136 if __name__ == "__main__": 136 ↛ 142line 136 didn't jump to line 142 because the condition on line 136 was never true

137

138 # for timestamp, temperature in SimulatedTemperature():

139 # print(f"{timestamp = }, {temperature = }")

140 # time.sleep(1.0)

141

142 import matplotlib.pyplot as plt

143

144 x, y = _simulate_temperature_data()

145

146 plt.plot(x, y)

147 plt.show()