Добавил:
omninoy
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:ЛР-7 / lab7
.py from dataclasses import dataclass, replace
from typing import Callable, List, Tuple, Optional, Any, NamedTuple
from functools import reduce
from multiprocessing import Pool, cpu_count
import random
# 1. ИММУТАБЕЛЬНЫЕ СТРУКТУРЫ ДАННЫХ
@dataclass(frozen=True)
class GeoCoord: #неизменяемые географические координаты фонаря
lat: float
lon: float
@dataclass(frozen=True)
class SunSchedule: #неизменяемый график восхода/захода солнца
sunrise_hour: float
sunset_hour: float
@dataclass(frozen=True)
class LampConfig: #неизменяемая конфигурация фонаря
lamp_id: str
location: GeoCoord
max_lumens: int
power_watts: float
sun_schedule: SunSchedule
@dataclass(frozen=True)
class SensorReading: #неизменяемое показание датчика
lamp_id: str
timestamp: float
lux: float
is_raining: bool
@dataclass(frozen=True)
class LampState: #неизменяемое состояние фонаря (результат вычислений)
lamp_id: str
brightness_percent: float
power_consumption: float
is_emergency: bool
# 2. МОНАДА MAYBE (обработка ошибок)
class Maybe: #монада Maybe для безопасной работы с отсутствующими данными
def __init__(self, value: Any):
self._value = value
def bind(self, func: Callable[[Any], 'Maybe']) -> 'Maybe': #монадическое связывание (flatMap)
if self._value is None:
return Maybe(None)
return func(self._value)
def map(self, func: Callable[[Any], Any]) -> 'Maybe': #функтор map
if self._value is None:
return Maybe(None)
try:
return Maybe(func(self._value))
except Exception:
return Maybe(None)
def get_or_else(self, default: Any) -> Any:
return self._value if self._value is not None else default
def is_nothing(self) -> bool:
return self._value is None
def is_just(self) -> bool:
return self._value is not None
def __repr__(self):
return f"Just({self._value})" if self.is_just() else "Nothing"
# 3. ЧИСТЫЕ ФУНКЦИИ (глобальные для сериализации в multiprocessing)
def calculate_brightness(lux: float, is_raining: bool, sunset_hour: float,
timestamp: float, max_lumens: int) -> float: #чистая функция: вычисление процента яркости светодиодной лампы в зависимости от уровня естественного света.
if lux < 0:
lux = 0
base_brightness = max(0, min(100, 100 - (lux / 10)))
rain_multiplier = 1.3 if is_raining else 1.0
hours_after_sunset = max(0, timestamp - sunset_hour)
night_boost = 1.0 + (hours_after_sunset * 0.1)
final_brightness = base_brightness * rain_multiplier * night_boost
return min(100, final_brightness)
def calculate_power_consumption(brightness_percent: float, max_power: float) -> float: #чистая функция: расчёт потребляемой мощности
return (brightness_percent / 100) * max_power
# 4. ЛЯМБДА-ФУНКЦИИ (фильтры)
CRITICAL_DARK = 5.0
is_emergency_dark = lambda reading: reading.lux < CRITICAL_DARK
is_night_time = lambda reading: reading.timestamp < 7 or reading.timestamp > 19
is_critical_post = lambda reading: reading.lux < CRITICAL_DARK
# 5. ФУНКЦИЯ ВЫСШЕГО ПОРЯДКА (ФВП)
def manage_grid(lux_filter: Callable[[SensorReading], bool],
dimming_strategy: Callable[[List[LampState]], List[LampState]],
lamps: Tuple[LampConfig, ...],
readings: Tuple[SensorReading, ...]) -> List[LampState]: #ФВП: контроллер энергосбережения принимает функцию-фильтр и функцию-стратегию диммирования.
filtered_readings = list(filter(lux_filter, readings))
lamp_states = []
for reading in filtered_readings:
lamp_config = next(
(l for l in lamps if l.lamp_id == reading.lamp_id), None
)
if lamp_config:
brightness = calculate_brightness(
reading.lux, reading.is_raining,
lamp_config.sun_schedule.sunset_hour,
reading.timestamp, lamp_config.max_lumens
)
power = calculate_power_consumption(brightness, lamp_config.power_watts)
state = LampState(
lamp_id=reading.lamp_id,
brightness_percent=brightness,
power_consumption=power,
is_emergency=reading.lux < CRITICAL_DARK
)
lamp_states.append(state)
return dimming_strategy(lamp_states)
# 6. КАРРИРОВАНИЕ (Currying)
def apply_holiday_profile(profile_name: str): #каррированная функция: фиксация праздничного режима
def with_dimming_curve(dimming_curve: str):
def with_lamp_id(lamp_id: str) -> dict:
return {
"profile": profile_name,
"curve": dimming_curve,
"lamp": lamp_id,
"brightness_override": 80 if profile_name == "New_Year" else 100
}
return with_lamp_id
return with_dimming_curve
# 7. РЕКУРСИЯ (обход вложенных структур)
@dataclass(frozen=True)
class PowerLineNode: #узел электропередачи (для каскадного отключения)
node_id: str
children: Tuple['PowerLineNode', ...] = ()
is_active: bool = True
def cascade_disconnect(node: PowerLineNode,
fault_node_id: str) -> PowerLineNode: #рекурсивная функция: каскадное отключение линий электропередачи при обнаружении короткого замыкания.
if node.node_id == fault_node_id:
return replace(node, is_active=False, children=tuple(
replace(child, is_active=False) for child in node.children
))
new_children = tuple(
cascade_disconnect(child, fault_node_id)
for child in node.children
)
any_child_disabled = any(not c.is_active for c in new_children)
return replace(node, children=new_children, is_active=not any_child_disabled)
# 8. REDUCE (свёртка)
def reduce_grid_consumption(states: List[LampState]) -> dict: # Reduce: cжатие данных о потреблении всей сети фонарей в итоговую метрику.
if not states:
return {"total_kwh": 0, "avg_brightness": 0, "emergency_count": 0}
total_power = reduce(lambda acc, s: acc + s.power_consumption, states, 0)
avg_brightness = reduce(lambda acc, s: acc + s.brightness_percent, states, 0) / len(states)
emergency_count = reduce(lambda acc, s: acc + (1 if s.is_emergency else 0), states, 0)
total_kwh = total_power / 1000
return {
"total_kwh": total_kwh,
"avg_brightness": avg_brightness,
"emergency_count": emergency_count,
"savings_percent": max(0, 100 - avg_brightness)
}
# 9. ПАРАЛЛЕЛЬНЫЙ MAP/REDUCE
class WorkerInput(NamedTuple): #иммутабельный вход для воркера
readings: List[SensorReading]
lamps: List[LampConfig]
def process_chunk_worker(worker_input: WorkerInput) -> List[LampState]: #чистая глобальная функция-маппер для воркера.
results = []
lamps_dict = {l.lamp_id: l for l in worker_input.lamps}
for reading in worker_input.readings:
lamp = lamps_dict.get(reading.lamp_id)
if lamp:
brightness = calculate_brightness(
reading.lux, reading.is_raining,
lamp.sun_schedule.sunset_hour,
reading.timestamp, lamp.max_lumens
)
power = calculate_power_consumption(brightness, lamp.power_watts)
results.append(LampState(
lamp_id=reading.lamp_id,
brightness_percent=brightness,
power_consumption=power,
is_emergency=reading.lux < CRITICAL_DARK
))
return results
def parallel_process_lamps(lamps: Tuple[LampConfig, ...],
readings: Tuple[SensorReading, ...],
num_workers: Optional[int] = None) -> List[LampState]: #параллельная обработка через чистый Map/Reduce
if num_workers is None:
num_workers = min(cpu_count(), 4)
readings_list = list(readings)
lamps_list = list(lamps)
chunk_size = max(1, len(readings_list) // num_workers)
chunks = [
WorkerInput(readings_list[i:i + chunk_size], lamps_list)
for i in range(0, len(readings_list), chunk_size)
]
with Pool(num_workers) as pool:
mapped_results = pool.map(process_chunk_worker, chunks)
all_states = reduce(lambda acc, chunk: acc + chunk, mapped_results, [])
return all_states
# ГЕНЕРАЦИЯ ТЕСТОВЫХ ДАННЫХ
def generate_test_data(num_lamps: int = 100) -> Tuple[Tuple[LampConfig, ...], Tuple[SensorReading, ...]]: #чистая функция генерации тестовых данных
random.seed(42)
lamps = tuple(
LampConfig(
f"lamp_{i:03d}",
GeoCoord(56.0 + i * 0.001, 84.0 + i * 0.001),
5000 + (i % 3) * 1000,
50.0 + (i % 3) * 10,
SunSchedule(6.0 + (i % 2) * 0.5, 19.0 + (i % 2) * 0.5)
)
for i in range(num_lamps)
)
readings = tuple(
SensorReading(
f"lamp_{i:03d}",
22.0 if i % 3 == 0 else 14.0,
random.uniform(0.5, 200.0),
random.choice([True, False])
)
for i in range(num_lamps)
)
return lamps, readings
# ДЕМОНСТРАЦИЯ РАБОТЫ СИСТЕМЫ
def run_demo():
#базовые данные
lamps = (
LampConfig("lamp_001", GeoCoord(56.49, 84.98), 5000, 50.0, SunSchedule(6.5, 19.5)),
LampConfig("lamp_002", GeoCoord(56.50, 84.99), 6000, 60.0, SunSchedule(6.5, 19.5)),
LampConfig("lamp_003", GeoCoord(56.51, 85.00), 4500, 45.0, SunSchedule(6.5, 19.5)),
LampConfig("lamp_004", GeoCoord(56.52, 85.01), 7000, 70.0, SunSchedule(6.5, 19.5)),
LampConfig("lamp_005", GeoCoord(56.53, 85.02), 5500, 55.0, SunSchedule(6.5, 19.5)),
)
readings = (
SensorReading("lamp_001", 22.0, 2.0, False),
SensorReading("lamp_002", 22.0, 150.0, False),
SensorReading("lamp_003", 22.0, 1.0, True),
SensorReading("lamp_004", 14.0, 800.0, False),
SensorReading("lamp_005", 22.0, 3.0, False),
)
print("\n1. ИММУТАБЕЛЬНЫЕ ДАННЫЕ:")
print(f" Лампы: {len(lamps)} шт. (frozen dataclass)")
print(f" Показания: {len(readings)} шт. (frozen dataclass)")
print(f" Попытка изменения вызовет AttributeError")
print("\n2. МОНАДА MAYBE (обработка сбоя датчика):")
sensor_broken = Maybe(None)
sensor_ok = Maybe(150.0)
print(f" Сбой датчика: {sensor_broken} -> яркость: {sensor_broken.map(lambda x: calculate_brightness(x, False, 19.5, 22.0, 5000)).get_or_else(50.0)}%")
print(f" Нормальный: {sensor_ok} -> яркость: {sensor_ok.map(lambda x: calculate_brightness(x, False, 19.5, 22.0, 5000)).get_or_else(50.0)}%")
print("\n3. ЛЯМБДА-ФИЛЬТРЫ:")
print(f" Критически темные (< {CRITICAL_DARK} lux): {[r.lamp_id for r in filter(is_critical_post, readings)]}")
print(f" Ночные посты: {[r.lamp_id for r in filter(is_night_time, readings)]}")
print("\n4. ФВП manage_grid + стратегия диммирования:")
def energy_save_strategy(states: List[LampState]) -> List[LampState]:
return [replace(s, brightness_percent=s.brightness_percent * 0.8,
power_consumption=s.power_consumption * 0.8) for s in states]
grid_states = manage_grid(
lambda r: r.lux < 200,
energy_save_strategy,
lamps, readings
)
for s in grid_states:
print(f" {s.lamp_id}: {s.brightness_percent:.1f}% | {s.power_consumption:.1f}Вт | emergency={s.is_emergency}")
print("\n5. REDUCE (свёртка метрик):")
metrics = reduce_grid_consumption(grid_states)
for k, v in metrics.items():
print(f" {k}: {v:.4f}" if isinstance(v, float) else f" {k}: {v}")
print("\n6. РЕКУРСИЯ (каскадное отключение электросети):")
line_tree = PowerLineNode("main", (
PowerLineNode("sub_1", (PowerLineNode("line_1"), PowerLineNode("line_2"))),
PowerLineNode("sub_2", (PowerLineNode("line_3"), PowerLineNode("line_4", (PowerLineNode("line_5"),)))),
))
def tree_to_str(node, level=0):
lines = [" " * level + f"{node.node_id}: active={node.is_active}"]
for c in node.children:
lines.extend(tree_to_str(c, level + 1))
return lines
print(" До:")
print("\n".join(tree_to_str(line_tree)))
disabled = cascade_disconnect(line_tree, "line_4")
print(" После отключения line_4:")
print("\n".join(tree_to_str(disabled)))
print("\n7. ПАРАЛЛЕЛЬНЫЙ MAP/REDUCE (100 ламп, 4 воркера):")
big_lamps, big_readings = generate_test_data(100)
parallel_result = parallel_process_lamps(big_lamps, big_readings, num_workers=4)
parallel_metrics = reduce_grid_consumption(parallel_result)
print(f" Обработано ламп: {len(parallel_result)}")
print(f" Потребление: {parallel_metrics['total_kwh']:.4f} кВт·ч")
print(f" Средняя яркость: {parallel_metrics['avg_brightness']:.2f}%")
print(f" Аварийных: {parallel_metrics['emergency_count']}")
print("\n8. КАРРИРОВАНИЕ (праздничные профили):")
ny = apply_holiday_profile("New_Year")
print(f" {ny('soft')('lamp_001')}")
print(f" {ny('bright')('lamp_002')}")
if __name__ == '__main__':
run_demo() 