Протестировал Fable5, Opus 4.8, Sonnet 5 и GLM 5.2

от автора

Доброго дня всем! Протестировал выше упомянутые модели одной задачей. Делал это за завтраком перед работой, писал сюда по дороге в автобусе.

. Оценки от меня не будет, только от Qwen 3.7 plus, для лентяев вроде меня, ну и исходники для самостоятельной оценки.

Задача:

Напиши на Python движок для парсинга и вычисления математических выражений, переданных в виде текстовой строки.

Требования и архитектурные ограничения:

  1. Строго без eval(): Движок должен самостоятельно токенизировать строку и строить Абстрактное Синтаксическое Дерево (AST) с помощью алгоритма рекурсивного спуска (Recursive Descent) или сортировочной станции (Shunting-yard).

  2. Поддержка операций: Базовые арифметические (+, -, *, /, ^ для степени), скобки для приоритета, а также встроенные функции (sin, cos).

  3. Переменные (Контекст): Возможность передавать словарь переменных для вычисления. Например: evaluate("x^2 + 2*y", {x: 3, y: 4}).

  4. Символьное дифференцирование (Главная сложность): Реализуй метод differentiate(variable), который принимает построенное AST, дифференцирует его по заданной переменной (применяя правила производной суммы, произведения, сложной функции) и возвращает новое AST.

  5. Упрощение дерева: После дифференцирования полученное дерево часто получается избыточным. Реализуй базовую оптимизацию AST (например, ветки x 0 должны сворачиваться в 0, y + 0 в y, 1 x в x, 5 + 3 в 8).

  6. Обработка ошибок: Информативные исключения при синтаксических ошибках (например, «Неожиданный токен на позиции 5» или «Несбалансированные скобки») и защите от деления на ноль.

Ожидаемый результат: Код должен быть production-ready. Ожидается строгая типизация, грамотное использование ООП (например, паттерн Visitor или полиморфизм узлов дерева) или чистого функционального подхода, а также небольшой блок unit-тестов, проверяющих вычисление, дифференцирование и обработку ошибок.

Оценка от Qwen 3.7 plus.

Я проанализировал предоставленные реализации математического движка. Это отличная подборка, демонстрирующая разные архитектурные подходы и уровни проработки от различных языковых моделей.

Ниже представлен подробный разбор и оценка каждой модели (Opus, Sonnet, GLM, Fable) по критериям: архитектура, полнота математического аппарата, качество кода и тестирование.

1. Fable (Fable5) 🏆 Лидер (Production-Ready)

Архитектура: Классический и строгий паттерн Посетитель (Visitor). Узлы AST (Node) хранят только данные (они frozen, то есть неизменяемы), а вся логика (вычисление, дифференцирование, упрощение, печать) вынесена в отдельные классы-посетители.

  • Плюсы:

    • Полнота математики: Реализовано общее степенное правило (логарифмическое дифференцирование для u^v).

    • Умный принтер: Отдельный класс _Printer с системой приоритетов. Он расставляет скобки минимально необходимо, а не вокруг каждого узла.

    • Расширяемость: Наличие register_function позволяет добавлять новые функции (с их численной реализацией и правилами дифференцирования) на лету.

    • Тесты: Вынесены в отдельный файл. Включают метод конечных разностей для проверки символьных производных и тесты на структурный round-trip (парсинг -> печать -> парсинг).

  • Минусы: Код более многословен из-за разделения на Visitor’ы, но для сложного движка это скорее плюс (соответствует принципу Open/Closed).

  • Вердикт: 10/10. Энтерпрайз-уровень. Код готов к использованию в реальных проектах (например, в CAS-системах или образовательных платформах).


2. Opus (opus4.8) 🥈 Отличная ООП-реализация

Архитектура: Полиморфизм в узлах AST. Каждый узел (Number, BinaryOp и т.д.) сам знает, как себя вычислить, продифференцировать и напечатать.

  • Плюсы:

    • Инкапсуляция: Очень чистый ООП-подход. Не нужно плодить сущности-посетители, если логика узлов не будет часто меняться.

    • Полнота математики: Также реализовано общее степенное правило (u^v).

    • Упрощение: Использует итеративный подход (fixpoint), что позволяет сворачивать каскадные тождества (например, (x 0 + y) 1 -> y).

    • Тесты: Отличное покрытие, включая проверки на мутабельность и числовую сходимость.

  • Минусы:

    • Монолитность: Тесты и исходный код находятся в одном файле. Для продакшена их лучше разделять.

    • Нарушение SRP (Single Responsibility Principle): Узлы AST отвечают и за хранение данных, и за рендеринг, и за вычисления. Если понадобится добавить новую операцию (например, интеграцию), придется править каждый класс узла.

  • Вердикт: 8.5/10. Очень сильный, элегантный код. Отличный выбор для прототипа или проекта, где набор операций фиксирован и не будет расширяться.


3. GLM (glm_math_engine) 🥉 Академический подход

Архитектура: Строгий Паттерн Посетитель (Visitor), но с рядом критических упущений.

  • Плюсы:

    • Чистое разделение данных и поведения (как у Fable).

    • Хорошая обработка ошибок лексера и парсера с указанием позиции.

  • Минусы:

    • Нет модуля печати (Printer): В коде вообще нет метода str или класса для преобразования AST обратно в строку. Вы не сможете красиво вывести результат дифференцирования.

    • Обрезанное дифференцирование: Не поддерживает дифференцирование переменной в степени (x^y или x^x). Выбрасывает ошибку.

    • Упрощение за один проход: Не использует итеративное упрощение до неподвижной точки. Каскадные тождества (например, 0 * x + y) могут не свернуться до конца.

  • Вердикт: 6/10. Хорошая заготовка, демонстрирующая понимание паттерна Visitor, но функционально неполноценна из-за отсутствия принтера и урезанной математики.


4. Sonnet (sonet5) 📉 Сырой прототип

Архитектура: Полиморфизм в узлах (как у Opus), но с использованием dataclass(frozen=True).

  • Плюсы:

    • Токенизатор на Regex: Очень элегантное и компактное решение для разбора строки на токены. Это быстрее и короче, чем посимвольный разбор.

    • Отличные сообщения об ошибках с точными позициями.

  • Минусы:

    • Ужасный Принтер: Метод _render в BinaryOp всегда оборачивает операнды в скобки, если их приоритет ниже или равен текущему. В результате выражение x + y z напечатается как (x + (y z)), а производная превратится в нечитаемую “капусту” из скобок.

    • Обрезанное дифференцирование: Явно отказывается дифференцировать f(x)^{g(x)}, выбрасывая DifferentiationError.

    • Смешивание типов: Использование Union[int, float] и возврат int там, где ожидается float, может привести к багам в строгих математических вычислениях.

  • Вердикт: 5/10. Токенизатор хорош, но отсутствие нормального принтера и неполная поддержка математического аппарата (отказ от общего степенного правила) делают движок непригодным для символьных вычислений.


Сводная таблица сравнения

Критерий

Fable (Fable5)

Opus (opus4.8)

GLM

Sonnet (sonet5)

Архитектура

Visitor (Идеально)

Полиморфизм (Хорошо)

Visitor (Хорошо)

Полиморфизм (Средне)

Дифф. u^v (общее)

✅ Есть (логарифм)

✅ Есть (логарифм)

❌ Нет (только u^c)

❌ Нет (только u^c)

Печать (AST -> str)

✅ Умная (мин. скобок)

✅ Хорошая

Отсутствует

❌ Плохая (лишние скобки)

Упрощение

✅ Итеративное (fixpoint)

✅ Итеративное (fixpoint)

⚠️ За 1 проход

⚠️ За 1 проход

Расширяемость

register_function

❌ Захардкожено

❌ Захардкожено

❌ Захардкожено

Токенизатор

Посимвольный

Посимвольный

Посимвольный

Regex

Тесты

✅ Отдельный файл, мат. стат.

✅ Встроенные, хорошие

✅ Встроенные, базовые

✅ Встроенные, базовые

Итоговое резюме

  1. Fable5 — безоговорочный победитель. Код написан как для серьезной библиотеки: расширяемый, неизменяемый (immutability), с умным форматированием и строгими тестами через численные методы.

  2. Opus — второе место. Очень достойная ООП-реализация, но архитектурно она менее гибкая для добавления новых операций по сравнению с Visitor-подходом Fable.

  3. GLM — крепкий середняк. Понимает паттерны проектирования, но не доводит задачу до конца (забыл написать Printer).

  4. Sonnet — аутсайдер. Несмотря на красивый Regex-токенизатор, модель не справилась с задачей вывода строк и ограничила математический аппарат.

    Github: https://github.com/Paradoxdov/fable-opus-sonnet-glm-test

Оценкавычисление, дифференцирование и обработку ошибок.

ссылка на оригинал статьи https://habr.com/ru/articles/1054648/