-
Notifications
You must be signed in to change notification settings - Fork 2
feat all: add AIG parser and RemoveConstantGates minimization #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
spefk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Оставил разных комментариев (и минорных, и важных).
В целом, лучше приносить такие большие ПРы частями, потому что 4к кода сложно править и ревьюить (и вообще, по опыту количетсво строк в ПРе как минимум квадратично влияет на время проверки!). В этом ПРе как раз 2 несвязанные части есть, парсер и алгоритм минимизации, которые можно было бы разделить чтобы одно за другое не цеплялось.
| "pebble >= 5.0.7", | ||
| "graphviz >= 0.20.3", | ||
| "sortedcontainers (>=2.4.0,<3.0.0)", | ||
| "notebook (>=7.4.7,<8.0.0)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это лишняя зависимость для либы т.к. непосредственно не используется (и вообще, скорее однозначно пользовательская) -- нужно убрать и откатить poetry.lock на предыдущую версию (т.е. не update, а честный revert по истории)
И в целом, добавление/обновление зависимостей всегда лучше приносить отдельным ПРом, т.к. так проще отслеживать проблемы.
| @staticmethod | ||
| def from_aig_string(string: str) -> "Circuit": | ||
| """ | ||
| Initialize the circuit from an AIG format string. | ||
| Only ASCII AIG format (.aag) is supported for string input. Only combinational | ||
| circuits (without latches) are supported. | ||
| :param string: string containing AIG data in ASCII format. | ||
| :return: parsed Circuit object. | ||
| """ | ||
| from cirbo.core.parser.aig import AIGParser | ||
|
|
||
| parser = AIGParser() | ||
| return parser.parse_string(string) | ||
|
|
||
| @staticmethod | ||
| def from_aig_bytes(data: bytes) -> "Circuit": | ||
| """ | ||
| Initialize the circuit from AIG format bytes. | ||
| Supports both ASCII (.aag) and binary (.aig) AIGER formats. Only combinational | ||
| circuits (without latches) are supported. | ||
| :param data: bytes containing AIG data. | ||
| :return: parsed Circuit object. | ||
| """ | ||
| from cirbo.core.parser.aig import AIGParser | ||
|
|
||
| parser = AIGParser() | ||
| return parser.parse_bytes(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Минорно: правда ли на уровне парсера (и схемы соотв.) должны быть разные методы parse_bytes и parse_string, если это можно простой проверкой isinstance вывести динамически? Отдельный parse_file нужен чтобы отличить строку с путём от строки с данными
| __all__ = ['AIGParser'] | ||
|
|
||
|
|
||
| class AIGParseError(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Нужно отнаследовать от CirboError
| pass | ||
|
|
||
|
|
||
| class AIGParser: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Не нравится, что эта штука не наследуется от AbstractParser. Получаем две отдельные абстракции, из-за чего целостность теряется. Давай отнаследуем её и как базовый сценарий поддержим работу со строковым входом?
| """ | ||
| path = pathlib.Path(file_path) | ||
| if path.suffix == '.aag': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Минорно: чуть чище будет с endswith
| nonlocal _new_circuit, _label_remap, _gate_types | ||
|
|
||
| # remap operands to their simplified labels | ||
| remapped_operands = tuple(_get_remapped_label(op) for op in _gate.operands) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Минорно: тот относительно редкий случай, когда можно tuple(map(_get_remapped_label, _gate.operands))
| def _get_const_value(gate_type: gate.GateType) -> int: | ||
| return 1 if gate_type == gate.ALWAYS_TRUE else 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Немного течет интерфейс -- конст. гейты задаются в _CONST_GATES, а здесь проверяется только gate.ALWAYS_TRUE и else. Чище будет честно проверять оба if == gate.ALWAYS_TRUE и if == gate.ALWAYS_TRUE, и бросать ошибку иначе.
Как вариант, можно заменить _CONST_GATES на дикт где ключи -- типы, а значения -- константные значения этих типов гейтов.
Сами значения правильнее вычислять через вызов .operator (т.е. ALWAYS_TRUE.operator())
| const_operand_infos.append((idx, _get_const_value(op_type))) | ||
|
|
||
| # simplify if we have constant operands | ||
| if const_operand_infos and current_type not in (gate.INPUT, *_CONST_GATES): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
На каждой итерации создается новый тапл в (gate.INPUT, *_CONST_GATES), что дорого.
| return new_type, new_operands | ||
|
|
||
|
|
||
| class RemoveConstantGates(Transformer): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
У нас аналогичное упрощение написано в Simplifier'e, и перетащено в cirbo-search сейчас. Я точно не помно, но там вроде поддержано всякое, в т.ч. не только бинарные гейты. Если интересно, можно посмотреть как оно реализовано и поспрашивать Вику, если будет необходимость: https://github.com/SPbSAT/cirbo-search/blob/main/src/minimization/low_effort/constant_gate_reducer.hpp
В целом, в будущем планируем cirbo-search подсоединить в cirbo биндингами, и вызывать упрощения из него, поэтому доработки в рамках упрощений cirbo скорее ограниченно актуальны.
| if original_gate.gate_type == gate.IFF: | ||
| # IFF(const) = const | ||
| new_type = operand_gate_type | ||
| elif original_gate.gate_type == gate.NOT: | ||
| # NOT(ALWAYS_TRUE) = ALWAYS_FALSE, NOT(ALWAYS_FALSE) = ALWAYS_TRUE | ||
| new_type = gate.ALWAYS_FALSE if const_val == 1 else gate.ALWAYS_TRUE | ||
| else: | ||
| raise ValueError(f"Unsupported unary gate type: {original_gate.gate_type}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Здесь тоже идет хардкод конкретных видов гейтов. Имхо, правильнее вызывать operator в нужных местах и довериться полиморфизму.
This PR adds two new features to cirbo: