Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ЛР-7 / lab7

.py
Скачиваний:
0
Добавлен:
06.06.2026
Размер:
14.86 Кб
Скачать
    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()
Соседние файлы в папке ЛР-7