From 3c43c327d2332020c762392441c686ace7c12f70 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Tue, 7 Jan 2025 17:15:00 -0800 Subject: [PATCH 1/8] added optional variables for context (e.g. krns_delta, num_digits) and tests --- kerngen/high_parser/types.py | 53 ++++++++++++++++++++++++++++++----- kerngen/tests/test_kerngen.py | 33 +++++++++++++++++++++- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/kerngen/high_parser/types.py b/kerngen/high_parser/types.py index 5cca8d7c..9baa6e51 100644 --- a/kerngen/high_parser/types.py +++ b/kerngen/high_parser/types.py @@ -141,6 +141,46 @@ class EmptyLine(BaseModel): NATIVE_POLY_SIZE = 8192 MIN_POLY_SIZE = 16384 MAX_POLY_SIZE = 131072 +MAX_KRNS_DELTA = 128 +MAX_DIGIT = 3 +MIN_KRNS_DELTA = MIN_DIGIT = 0 + + +def _parse_optional(optionals: list[str]): + """Parse optional key/value pairs""" + krns_delta = None + num_digits = None + + def valid_num_option(value: str, min_val: int, max_val: int): + """Validate numeric options with min/max range""" + if value.isnumeric() and int(value) > min_val and int(value) < max_val: + return True + return False + + for option in optionals: + try: + key, value = option.split("=") + match key: + case "krns_delta": + if not valid_num_option(value, MIN_KRNS_DELTA, MAX_KRNS_DELTA): + raise ValueError( + f"krns_delta must be in range ({MIN_KRNS_DELTA}, {MAX_KRNS_DELTA}): krns_delta={krns_delta}" + ) + krns_delta = int(value) + case "num_digits": + if not valid_num_option(value, MIN_DIGIT, MAX_DIGIT): + raise ValueError( + f"num_digits must be in range ({MIN_DIGIT}, {MAX_DIGIT}): num_digits={num_digits}" + ) + num_digits = int(value) + case _: + raise KeyError(f"Invalid optional key for Context: {key}") + except ValueError as err: + raise ValueError( + f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" + ) from err + + return krns_delta, num_digits class Context(BaseModel): @@ -149,18 +189,15 @@ class Context(BaseModel): scheme: str poly_order: int # the N max_rns: int + # optional vars for context key_rns: int | None + num_digits: int | None @classmethod def from_string(cls, line: str): """Construct context from a string""" scheme, poly_order, max_rns, *optional = line.split() - try: - krns, *rest = optional - except ValueError: - krns = None - if optional != [] and rest != []: - raise ValueError(f"too many parameters for context given: {line}") + krns_delta, num_digits = _parse_optional(optional) int_poly_order = int(poly_order) if ( int_poly_order < MIN_POLY_SIZE @@ -172,12 +209,14 @@ def from_string(cls, line: str): ) int_max_rns = int(max_rns) - int_key_rns = int_max_rns + int(krns) if krns else None + int_key_rns = int_max_rns + krns_delta if krns_delta else None + int_num_digits = num_digits if num_digits else None return cls( scheme=scheme.upper(), poly_order=int_poly_order, max_rns=int_max_rns, key_rns=int_key_rns, + num_digits=int_num_digits, ) @property diff --git a/kerngen/tests/test_kerngen.py b/kerngen/tests/test_kerngen.py index b5b113f7..933e1f6e 100644 --- a/kerngen/tests/test_kerngen.py +++ b/kerngen/tests/test_kerngen.py @@ -69,6 +69,37 @@ def test_multiple_contexts(kerngen_path): assert result.returncode != 0 +def test_context_optional_without_key(kerngen_path): + """Test kerngen raises an exception when more than one context is given""" + input_string = "CONTEXT BGV 16384 4 1\nData a 2\n" + result = execute_process( + [kerngen_path], + data_in=input_string, + ) + assert not result.stdout + assert ( + "ValueError: Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '1'" + in result.stderr + ) + assert result.returncode != 0 + + +@pytest.mark.parametrize("invalid", [-1, 256, 0.1, "str"]) +def test_context_optional_invalid_values(kerngen_path, invalid): + """Test kerngen raises an exception if value is out of range for correct key""" + input_string = f"CONTEXT BGV 16384 4 krns_delta={invalid}\nData a 2\n" + result = execute_process( + [kerngen_path], + data_in=input_string, + ) + assert not result.stdout + assert ( + f"ValueError: Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): 'krns_delta={invalid}'" + in result.stderr + ) + assert result.returncode != 0 + + def test_unrecognised_opname(kerngen_path): """Test kerngen raises an exception when receiving an unrecognised opname""" @@ -99,7 +130,7 @@ def test_invalid_scheme(kerngen_path): @pytest.mark.parametrize("invalid_poly", [16000, 2**12, 2**13, 2**18]) def test_invalid_poly_order(kerngen_path, invalid_poly): """Poly order should be powers of two >= 2^14 and <= 2^17""" - input_string = "CONTEXT BGV " + str(invalid_poly) + " 4 2\nADD a b c\n" + input_string = "CONTEXT BGV " + str(invalid_poly) + " 4\nADD a b c\n" result = execute_process( [kerngen_path], data_in=input_string, From 97f547fa0a31c9572164bcb8d571c205bb67be39 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Wed, 15 Jan 2025 13:27:41 -0800 Subject: [PATCH 2/8] Refactored optional context parameters to make code extendable in the future --- kerngen/high_parser/optional.py | 128 ++++++++++++++++++++++++++++++++ kerngen/high_parser/types.py | 52 +++---------- 2 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 kerngen/high_parser/optional.py diff --git a/kerngen/high_parser/optional.py b/kerngen/high_parser/optional.py new file mode 100644 index 00000000..4e0fa0fa --- /dev/null +++ b/kerngen/high_parser/optional.py @@ -0,0 +1,128 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""A module to process optional context parameters""" + +from abc import ABC, abstractmethod +from typing import Dict + + +class OptionalContext(ABC): + """Abstract class to hold optional parameters for context""" + + op_name: str = "" + op_value = None + + @abstractmethod + def validate(self, value): + """Abstract method, which defines how to valudate a value""" + + +class OptionalInt(OptionalContext): + """Holds a key/value pair for optional context parameters of type Int""" + + def __init__(self, name: str, min_val: int, max_val: int): + self.min_val = min_val + self.max_val = max_val + self.op_name = name + + def validate(self, value: int): + """Validate numeric options with min/max range""" + if int(value) > self.min_val and int(value) < self.max_val: + return True + return False + + @property + def op_value(self): + """Get op_value""" + return self.op_name + + @op_value.setter + def op_value(self, value: int): + """Set op_value""" + if self.validate(value): + self.op_value = int(value) + else: + raise ValueError( + "{self.op_name} must be in range ({self.min_val}, {self.max_val}): {self.op_name}={self.op_value}" + ) + + +class OptionalIntMinMax: + """Holds min/max values for optional context parameters for type Int""" + + int_min: int + int_max: int + + def __init__(self, int_min: int, int_max: int): + self.int_min = int_min + self.int_max = int_max + + +class OptionalFactor(ABC): + """Abstract class that creates OptionaContext objects""" + + @staticmethod + @abstractmethod + def create(name: str, value: str) -> OptionalContext: + """Abstract method, to define how to create an OptionalContext""" + + +MAX_KRNS_DELTA = 128 +MAX_DIGIT = 3 +MIN_KRNS_DELTA = MIN_DIGIT = 0 + + +class OptionalIntFactory(ABC): + """Optional context parameter factory for Int types""" + + optionals = { + "krns_delta": OptionalIntMinMax(MIN_KRNS_DELTA, MAX_KRNS_DELTA), + "num_digits": OptionalIntMinMax(MIN_DIGIT, MAX_DIGIT), + } + + @staticmethod + def create(name: str, value: int) -> OptionalInt: + """Create a OptionalInt object based on key/value pair""" + if name in OptionalIntFactory.optionals: + optional_int = OptionalInt( + name, + OptionalIntFactory.optionals[name].int_min, + OptionalIntFactory.optionals[name].int_max, + ) + optional_int.op_value = value + else: + raise KeyError(f"Invalid optional key for Context: {name}") + return optional_int + + +class OptionalFactoryDispatcher: + """An object dispatcher based on key/value pair for comptional context parameters""" + + @staticmethod + def create(name, value) -> OptionalContext: + """Creat an OptionalContext object based on the type of value passed in""" + match value: + case int(): + return OptionalIntFactory.create(name, int(value)) + # add other optional types + case _: + raise ValueError(f"Current type '{type(value)}' is not supported.") + + +class OptionalsParser: + """Parses key/value pairs and returns a dictionary of optiona parameters""" + + @staticmethod + def parse(optionals: list[str]): + """Parse the optional parameter list and return a dictionary with values""" + output_dict: Dict[str, int | str | None] = {} + for option in optionals: + try: + key, value = option.split("=") + output_dict[key] = OptionalFactoryDispatcher.create(key, value).op_value + except ValueError as err: + raise ValueError( + f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" + ) from err + return output_dict diff --git a/kerngen/high_parser/types.py b/kerngen/high_parser/types.py index 9baa6e51..1c09e7ba 100644 --- a/kerngen/high_parser/types.py +++ b/kerngen/high_parser/types.py @@ -13,6 +13,8 @@ from .pisa_operations import PIsaOp +from .optional import OptionalsParser + class PolyOutOfBoundsError(Exception): """Exception for Poly attributes being out of bounds""" @@ -141,46 +143,6 @@ class EmptyLine(BaseModel): NATIVE_POLY_SIZE = 8192 MIN_POLY_SIZE = 16384 MAX_POLY_SIZE = 131072 -MAX_KRNS_DELTA = 128 -MAX_DIGIT = 3 -MIN_KRNS_DELTA = MIN_DIGIT = 0 - - -def _parse_optional(optionals: list[str]): - """Parse optional key/value pairs""" - krns_delta = None - num_digits = None - - def valid_num_option(value: str, min_val: int, max_val: int): - """Validate numeric options with min/max range""" - if value.isnumeric() and int(value) > min_val and int(value) < max_val: - return True - return False - - for option in optionals: - try: - key, value = option.split("=") - match key: - case "krns_delta": - if not valid_num_option(value, MIN_KRNS_DELTA, MAX_KRNS_DELTA): - raise ValueError( - f"krns_delta must be in range ({MIN_KRNS_DELTA}, {MAX_KRNS_DELTA}): krns_delta={krns_delta}" - ) - krns_delta = int(value) - case "num_digits": - if not valid_num_option(value, MIN_DIGIT, MAX_DIGIT): - raise ValueError( - f"num_digits must be in range ({MIN_DIGIT}, {MAX_DIGIT}): num_digits={num_digits}" - ) - num_digits = int(value) - case _: - raise KeyError(f"Invalid optional key for Context: {key}") - except ValueError as err: - raise ValueError( - f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" - ) from err - - return krns_delta, num_digits class Context(BaseModel): @@ -197,7 +159,7 @@ class Context(BaseModel): def from_string(cls, line: str): """Construct context from a string""" scheme, poly_order, max_rns, *optional = line.split() - krns_delta, num_digits = _parse_optional(optional) + optional_dict = OptionalsParser.parse(optional) int_poly_order = int(poly_order) if ( int_poly_order < MIN_POLY_SIZE @@ -209,8 +171,12 @@ def from_string(cls, line: str): ) int_max_rns = int(max_rns) - int_key_rns = int_max_rns + krns_delta if krns_delta else None - int_num_digits = num_digits if num_digits else None + int_key_rns = int_max_rns + int_num_digits = None + if len(optional_dict) > 0: + int_key_rns += optional_dict.get("krns_delta", 0) + int_num_digits = optional_dict.get("num_digits", None) + return cls( scheme=scheme.upper(), poly_order=int_poly_order, From c7c826832ba19f037e3b2125865adba91274f50d Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Wed, 22 Jan 2025 13:04:14 -0800 Subject: [PATCH 3/8] Refactored optional module and integrated updates into types. Updated test methods. --- kerngen/high_parser/optional.py | 69 +++++++++++++++++++-------------- kerngen/high_parser/types.py | 11 ++---- kerngen/tests/test_kerngen.py | 12 ++++++ 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/kerngen/high_parser/optional.py b/kerngen/high_parser/optional.py index 4e0fa0fa..7e1243c7 100644 --- a/kerngen/high_parser/optional.py +++ b/kerngen/high_parser/optional.py @@ -4,7 +4,6 @@ """A module to process optional context parameters""" from abc import ABC, abstractmethod -from typing import Dict class OptionalContext(ABC): @@ -24,24 +23,24 @@ class OptionalInt(OptionalContext): def __init__(self, name: str, min_val: int, max_val: int): self.min_val = min_val self.max_val = max_val - self.op_name = name + self._op_name = name def validate(self, value: int): """Validate numeric options with min/max range""" - if int(value) > self.min_val and int(value) < self.max_val: + if self.min_val < value < self.max_val: return True return False @property def op_value(self): """Get op_value""" - return self.op_name + return self._op_value @op_value.setter def op_value(self, value: int): """Set op_value""" if self.validate(value): - self.op_value = int(value) + self._op_value = int(value) else: raise ValueError( "{self.op_name} must be in range ({self.min_val}, {self.max_val}): {self.op_name}={self.op_value}" @@ -53,46 +52,48 @@ class OptionalIntMinMax: int_min: int int_max: int + default: int | None - def __init__(self, int_min: int, int_max: int): + def __init__(self, int_min: int, int_max: int, default: int | None): self.int_min = int_min self.int_max = int_max + self.default = default -class OptionalFactor(ABC): +class OptionalFactory(ABC): """Abstract class that creates OptionaContext objects""" + MAX_KRNS_DELTA = 128 + MAX_DIGIT = 3 + MIN_KRNS_DELTA = MIN_DIGIT = 0 + optionals = { + "krns_delta": OptionalIntMinMax(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), + "num_digits": OptionalIntMinMax(MIN_DIGIT, MAX_DIGIT, None), + } + @staticmethod @abstractmethod - def create(name: str, value: str) -> OptionalContext: + def create(name: str, value) -> OptionalContext: """Abstract method, to define how to create an OptionalContext""" -MAX_KRNS_DELTA = 128 -MAX_DIGIT = 3 -MIN_KRNS_DELTA = MIN_DIGIT = 0 - - -class OptionalIntFactory(ABC): +class OptionalIntFactory(OptionalFactory): """Optional context parameter factory for Int types""" - optionals = { - "krns_delta": OptionalIntMinMax(MIN_KRNS_DELTA, MAX_KRNS_DELTA), - "num_digits": OptionalIntMinMax(MIN_DIGIT, MAX_DIGIT), - } - @staticmethod def create(name: str, value: int) -> OptionalInt: """Create a OptionalInt object based on key/value pair""" if name in OptionalIntFactory.optionals: - optional_int = OptionalInt( - name, - OptionalIntFactory.optionals[name].int_min, - OptionalIntFactory.optionals[name].int_max, - ) - optional_int.op_value = value + if isinstance(OptionalIntFactory.optionals[name], OptionalIntMinMax): + optional_int = OptionalInt( + name, + OptionalIntFactory.optionals[name].int_min, + OptionalIntFactory.optionals[name].int_max, + ) + optional_int.op_value = value + # add other optional types here else: - raise KeyError(f"Invalid optional key for Context: {name}") + raise KeyError(f"Invalid optional name for Context: '{name}'") return optional_int @@ -100,12 +101,13 @@ class OptionalFactoryDispatcher: """An object dispatcher based on key/value pair for comptional context parameters""" @staticmethod - def create(name, value) -> OptionalContext: + def create(name: str, value) -> OptionalContext: """Creat an OptionalContext object based on the type of value passed in""" + if value.isnumeric(): + value = int(value) match value: case int(): - return OptionalIntFactory.create(name, int(value)) - # add other optional types + return OptionalIntFactory.create(name, value) case _: raise ValueError(f"Current type '{type(value)}' is not supported.") @@ -113,10 +115,17 @@ def create(name, value) -> OptionalContext: class OptionalsParser: """Parses key/value pairs and returns a dictionary of optiona parameters""" + @staticmethod + def __default_values(): + default_dict = {} + for key, val in OptionalFactory.optionals.items(): + default_dict[key] = val.default + return default_dict + @staticmethod def parse(optionals: list[str]): """Parse the optional parameter list and return a dictionary with values""" - output_dict: Dict[str, int | str | None] = {} + output_dict = OptionalsParser.__default_values() for option in optionals: try: key, value = option.split("=") diff --git a/kerngen/high_parser/types.py b/kerngen/high_parser/types.py index 1c09e7ba..a0ee879b 100644 --- a/kerngen/high_parser/types.py +++ b/kerngen/high_parser/types.py @@ -158,8 +158,8 @@ class Context(BaseModel): @classmethod def from_string(cls, line: str): """Construct context from a string""" - scheme, poly_order, max_rns, *optional = line.split() - optional_dict = OptionalsParser.parse(optional) + scheme, poly_order, max_rns, *optionals = line.split() + optional_dict = OptionalsParser.parse(optionals) int_poly_order = int(poly_order) if ( int_poly_order < MIN_POLY_SIZE @@ -172,17 +172,14 @@ def from_string(cls, line: str): int_max_rns = int(max_rns) int_key_rns = int_max_rns - int_num_digits = None - if len(optional_dict) > 0: - int_key_rns += optional_dict.get("krns_delta", 0) - int_num_digits = optional_dict.get("num_digits", None) + int_key_rns += optional_dict.pop("krns_delta") return cls( scheme=scheme.upper(), poly_order=int_poly_order, max_rns=int_max_rns, key_rns=int_key_rns, - num_digits=int_num_digits, + **optional_dict, ) @property diff --git a/kerngen/tests/test_kerngen.py b/kerngen/tests/test_kerngen.py index 933e1f6e..4b86c9ed 100644 --- a/kerngen/tests/test_kerngen.py +++ b/kerngen/tests/test_kerngen.py @@ -84,6 +84,18 @@ def test_context_optional_without_key(kerngen_path): assert result.returncode != 0 +def test_context_unsupported_optional_variable(kerngen_path): + """Test kerngen raises an exception when more than one context is given""" + input_string = "CONTEXT BGV 16384 4 test=3\nData a 2\n" + result = execute_process( + [kerngen_path], + data_in=input_string, + ) + assert not result.stdout + assert "Invalid optional name for Context: 'test'" in result.stderr + assert result.returncode != 0 + + @pytest.mark.parametrize("invalid", [-1, 256, 0.1, "str"]) def test_context_optional_invalid_values(kerngen_path, invalid): """Test kerngen raises an exception if value is out of range for correct key""" From ad33005982cb49f27e970b1591c6f3f88c16d79c Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Wed, 29 Jan 2025 10:46:23 -0800 Subject: [PATCH 4/8] renamed optional.py to options_handler.py --- kerngen/high_parser/options_handler.py | 137 +++++++++++++++++++++++++ kerngen/high_parser/types.py | 2 +- 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 kerngen/high_parser/options_handler.py diff --git a/kerngen/high_parser/options_handler.py b/kerngen/high_parser/options_handler.py new file mode 100644 index 00000000..7e1243c7 --- /dev/null +++ b/kerngen/high_parser/options_handler.py @@ -0,0 +1,137 @@ +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""A module to process optional context parameters""" + +from abc import ABC, abstractmethod + + +class OptionalContext(ABC): + """Abstract class to hold optional parameters for context""" + + op_name: str = "" + op_value = None + + @abstractmethod + def validate(self, value): + """Abstract method, which defines how to valudate a value""" + + +class OptionalInt(OptionalContext): + """Holds a key/value pair for optional context parameters of type Int""" + + def __init__(self, name: str, min_val: int, max_val: int): + self.min_val = min_val + self.max_val = max_val + self._op_name = name + + def validate(self, value: int): + """Validate numeric options with min/max range""" + if self.min_val < value < self.max_val: + return True + return False + + @property + def op_value(self): + """Get op_value""" + return self._op_value + + @op_value.setter + def op_value(self, value: int): + """Set op_value""" + if self.validate(value): + self._op_value = int(value) + else: + raise ValueError( + "{self.op_name} must be in range ({self.min_val}, {self.max_val}): {self.op_name}={self.op_value}" + ) + + +class OptionalIntMinMax: + """Holds min/max values for optional context parameters for type Int""" + + int_min: int + int_max: int + default: int | None + + def __init__(self, int_min: int, int_max: int, default: int | None): + self.int_min = int_min + self.int_max = int_max + self.default = default + + +class OptionalFactory(ABC): + """Abstract class that creates OptionaContext objects""" + + MAX_KRNS_DELTA = 128 + MAX_DIGIT = 3 + MIN_KRNS_DELTA = MIN_DIGIT = 0 + optionals = { + "krns_delta": OptionalIntMinMax(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), + "num_digits": OptionalIntMinMax(MIN_DIGIT, MAX_DIGIT, None), + } + + @staticmethod + @abstractmethod + def create(name: str, value) -> OptionalContext: + """Abstract method, to define how to create an OptionalContext""" + + +class OptionalIntFactory(OptionalFactory): + """Optional context parameter factory for Int types""" + + @staticmethod + def create(name: str, value: int) -> OptionalInt: + """Create a OptionalInt object based on key/value pair""" + if name in OptionalIntFactory.optionals: + if isinstance(OptionalIntFactory.optionals[name], OptionalIntMinMax): + optional_int = OptionalInt( + name, + OptionalIntFactory.optionals[name].int_min, + OptionalIntFactory.optionals[name].int_max, + ) + optional_int.op_value = value + # add other optional types here + else: + raise KeyError(f"Invalid optional name for Context: '{name}'") + return optional_int + + +class OptionalFactoryDispatcher: + """An object dispatcher based on key/value pair for comptional context parameters""" + + @staticmethod + def create(name: str, value) -> OptionalContext: + """Creat an OptionalContext object based on the type of value passed in""" + if value.isnumeric(): + value = int(value) + match value: + case int(): + return OptionalIntFactory.create(name, value) + case _: + raise ValueError(f"Current type '{type(value)}' is not supported.") + + +class OptionalsParser: + """Parses key/value pairs and returns a dictionary of optiona parameters""" + + @staticmethod + def __default_values(): + default_dict = {} + for key, val in OptionalFactory.optionals.items(): + default_dict[key] = val.default + return default_dict + + @staticmethod + def parse(optionals: list[str]): + """Parse the optional parameter list and return a dictionary with values""" + output_dict = OptionalsParser.__default_values() + for option in optionals: + try: + key, value = option.split("=") + output_dict[key] = OptionalFactoryDispatcher.create(key, value).op_value + except ValueError as err: + raise ValueError( + f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" + ) from err + return output_dict diff --git a/kerngen/high_parser/types.py b/kerngen/high_parser/types.py index a0ee879b..7794c68a 100644 --- a/kerngen/high_parser/types.py +++ b/kerngen/high_parser/types.py @@ -13,7 +13,7 @@ from .pisa_operations import PIsaOp -from .optional import OptionalsParser +from .options_handler import OptionalsParser class PolyOutOfBoundsError(Exception): From 73fa18cfabff917515a0c95ef1703e3e35056d87 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Wed, 29 Jan 2025 10:49:14 -0800 Subject: [PATCH 5/8] removed optional.py --- kerngen/high_parser/optional.py | 137 -------------------------------- 1 file changed, 137 deletions(-) delete mode 100644 kerngen/high_parser/optional.py diff --git a/kerngen/high_parser/optional.py b/kerngen/high_parser/optional.py deleted file mode 100644 index 7e1243c7..00000000 --- a/kerngen/high_parser/optional.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -"""A module to process optional context parameters""" - -from abc import ABC, abstractmethod - - -class OptionalContext(ABC): - """Abstract class to hold optional parameters for context""" - - op_name: str = "" - op_value = None - - @abstractmethod - def validate(self, value): - """Abstract method, which defines how to valudate a value""" - - -class OptionalInt(OptionalContext): - """Holds a key/value pair for optional context parameters of type Int""" - - def __init__(self, name: str, min_val: int, max_val: int): - self.min_val = min_val - self.max_val = max_val - self._op_name = name - - def validate(self, value: int): - """Validate numeric options with min/max range""" - if self.min_val < value < self.max_val: - return True - return False - - @property - def op_value(self): - """Get op_value""" - return self._op_value - - @op_value.setter - def op_value(self, value: int): - """Set op_value""" - if self.validate(value): - self._op_value = int(value) - else: - raise ValueError( - "{self.op_name} must be in range ({self.min_val}, {self.max_val}): {self.op_name}={self.op_value}" - ) - - -class OptionalIntMinMax: - """Holds min/max values for optional context parameters for type Int""" - - int_min: int - int_max: int - default: int | None - - def __init__(self, int_min: int, int_max: int, default: int | None): - self.int_min = int_min - self.int_max = int_max - self.default = default - - -class OptionalFactory(ABC): - """Abstract class that creates OptionaContext objects""" - - MAX_KRNS_DELTA = 128 - MAX_DIGIT = 3 - MIN_KRNS_DELTA = MIN_DIGIT = 0 - optionals = { - "krns_delta": OptionalIntMinMax(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), - "num_digits": OptionalIntMinMax(MIN_DIGIT, MAX_DIGIT, None), - } - - @staticmethod - @abstractmethod - def create(name: str, value) -> OptionalContext: - """Abstract method, to define how to create an OptionalContext""" - - -class OptionalIntFactory(OptionalFactory): - """Optional context parameter factory for Int types""" - - @staticmethod - def create(name: str, value: int) -> OptionalInt: - """Create a OptionalInt object based on key/value pair""" - if name in OptionalIntFactory.optionals: - if isinstance(OptionalIntFactory.optionals[name], OptionalIntMinMax): - optional_int = OptionalInt( - name, - OptionalIntFactory.optionals[name].int_min, - OptionalIntFactory.optionals[name].int_max, - ) - optional_int.op_value = value - # add other optional types here - else: - raise KeyError(f"Invalid optional name for Context: '{name}'") - return optional_int - - -class OptionalFactoryDispatcher: - """An object dispatcher based on key/value pair for comptional context parameters""" - - @staticmethod - def create(name: str, value) -> OptionalContext: - """Creat an OptionalContext object based on the type of value passed in""" - if value.isnumeric(): - value = int(value) - match value: - case int(): - return OptionalIntFactory.create(name, value) - case _: - raise ValueError(f"Current type '{type(value)}' is not supported.") - - -class OptionalsParser: - """Parses key/value pairs and returns a dictionary of optiona parameters""" - - @staticmethod - def __default_values(): - default_dict = {} - for key, val in OptionalFactory.optionals.items(): - default_dict[key] = val.default - return default_dict - - @staticmethod - def parse(optionals: list[str]): - """Parse the optional parameter list and return a dictionary with values""" - output_dict = OptionalsParser.__default_values() - for option in optionals: - try: - key, value = option.split("=") - output_dict[key] = OptionalFactoryDispatcher.create(key, value).op_value - except ValueError as err: - raise ValueError( - f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" - ) from err - return output_dict From e8eb0722f11c2ec90c07f7932b7f24d1d83ca518 Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Mon, 3 Feb 2025 11:35:53 -0800 Subject: [PATCH 6/8] Refactored class names in optional_handler.py, modified comments to reflect changes, and updated test case. --- kerngen/high_parser/options_handler.py | 66 +++++++++++++------------- kerngen/high_parser/types.py | 4 +- kerngen/tests/test_kerngen.py | 2 +- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/kerngen/high_parser/options_handler.py b/kerngen/high_parser/options_handler.py index 7e1243c7..5cced49b 100644 --- a/kerngen/high_parser/options_handler.py +++ b/kerngen/high_parser/options_handler.py @@ -1,13 +1,13 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -"""A module to process optional context parameters""" +"""A module to process optional key/value dictionary parameters""" from abc import ABC, abstractmethod -class OptionalContext(ABC): - """Abstract class to hold optional parameters for context""" +class OptionalDict(ABC): + """Abstract class to hold optional key/value pairs""" op_name: str = "" op_value = None @@ -17,8 +17,8 @@ def validate(self, value): """Abstract method, which defines how to valudate a value""" -class OptionalInt(OptionalContext): - """Holds a key/value pair for optional context parameters of type Int""" +class OptionalIntDict(OptionalDict): + """Holds a key/value pair for optional parameters of type Int""" def __init__(self, name: str, min_val: int, max_val: int): self.min_val = min_val @@ -47,8 +47,8 @@ def op_value(self, value: int): ) -class OptionalIntMinMax: - """Holds min/max values for optional context parameters for type Int""" +class OptionalIntBounds: + """Holds min/max/default values for optional parameters for type Int""" int_min: int int_max: int @@ -60,76 +60,78 @@ def __init__(self, int_min: int, int_max: int, default: int | None): self.default = default -class OptionalFactory(ABC): - """Abstract class that creates OptionaContext objects""" +class OptionalDictFactory(ABC): + """Abstract class that creates OptionalDict objects""" MAX_KRNS_DELTA = 128 MAX_DIGIT = 3 MIN_KRNS_DELTA = MIN_DIGIT = 0 optionals = { - "krns_delta": OptionalIntMinMax(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), - "num_digits": OptionalIntMinMax(MIN_DIGIT, MAX_DIGIT, None), + "krns_delta": OptionalIntBounds(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), + "num_digits": OptionalIntBounds(MIN_DIGIT, MAX_DIGIT, None), } @staticmethod @abstractmethod - def create(name: str, value) -> OptionalContext: - """Abstract method, to define how to create an OptionalContext""" + def create(name: str, value) -> OptionalDict: + """Abstract method, to define how to create an OptionalDict""" -class OptionalIntFactory(OptionalFactory): - """Optional context parameter factory for Int types""" +class OptionalIntDictFactory(OptionalDictFactory): + """OptionalDict parameter factory for Int types""" @staticmethod - def create(name: str, value: int) -> OptionalInt: + def create(name: str, value: int) -> OptionalIntDict: """Create a OptionalInt object based on key/value pair""" - if name in OptionalIntFactory.optionals: - if isinstance(OptionalIntFactory.optionals[name], OptionalIntMinMax): - optional_int = OptionalInt( + if name in OptionalIntDictFactory.optionals: + if isinstance(OptionalIntDictFactory.optionals[name], OptionalIntBounds): + optional_int = OptionalIntDict( name, - OptionalIntFactory.optionals[name].int_min, - OptionalIntFactory.optionals[name].int_max, + OptionalIntDictFactory.optionals[name].int_min, + OptionalIntDictFactory.optionals[name].int_max, ) optional_int.op_value = value # add other optional types here else: - raise KeyError(f"Invalid optional name for Context: '{name}'") + raise KeyError(f"Invalid optional name: '{name}'") return optional_int -class OptionalFactoryDispatcher: - """An object dispatcher based on key/value pair for comptional context parameters""" +class OptionalDictFactoryDispatcher: + """An object dispatcher based on key/value pair""" @staticmethod - def create(name: str, value) -> OptionalContext: - """Creat an OptionalContext object based on the type of value passed in""" + def create(name: str, value) -> OptionalDict: + """Creat an OptionalDict object based on the type of value passed in""" if value.isnumeric(): value = int(value) match value: case int(): - return OptionalIntFactory.create(name, value) + return OptionalIntDictFactory.create(name, value) case _: raise ValueError(f"Current type '{type(value)}' is not supported.") -class OptionalsParser: - """Parses key/value pairs and returns a dictionary of optiona parameters""" +class OptionalDictParser: + """Parses key/value pairs and returns a dictionary of optional parameters""" @staticmethod def __default_values(): default_dict = {} - for key, val in OptionalFactory.optionals.items(): + for key, val in OptionalDictFactory.optionals.items(): default_dict[key] = val.default return default_dict @staticmethod def parse(optionals: list[str]): """Parse the optional parameter list and return a dictionary with values""" - output_dict = OptionalsParser.__default_values() + output_dict = OptionalDictParser.__default_values() for option in optionals: try: key, value = option.split("=") - output_dict[key] = OptionalFactoryDispatcher.create(key, value).op_value + output_dict[key] = OptionalDictFactoryDispatcher.create( + key, value + ).op_value except ValueError as err: raise ValueError( f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" diff --git a/kerngen/high_parser/types.py b/kerngen/high_parser/types.py index 7794c68a..e4b98831 100644 --- a/kerngen/high_parser/types.py +++ b/kerngen/high_parser/types.py @@ -13,7 +13,7 @@ from .pisa_operations import PIsaOp -from .options_handler import OptionalsParser +from .options_handler import OptionalDictParser class PolyOutOfBoundsError(Exception): @@ -159,7 +159,7 @@ class Context(BaseModel): def from_string(cls, line: str): """Construct context from a string""" scheme, poly_order, max_rns, *optionals = line.split() - optional_dict = OptionalsParser.parse(optionals) + optional_dict = OptionalDictParser.parse(optionals) int_poly_order = int(poly_order) if ( int_poly_order < MIN_POLY_SIZE diff --git a/kerngen/tests/test_kerngen.py b/kerngen/tests/test_kerngen.py index 4b86c9ed..5577e17f 100644 --- a/kerngen/tests/test_kerngen.py +++ b/kerngen/tests/test_kerngen.py @@ -92,7 +92,7 @@ def test_context_unsupported_optional_variable(kerngen_path): data_in=input_string, ) assert not result.stdout - assert "Invalid optional name for Context: 'test'" in result.stderr + assert "Invalid optional name: 'test'" in result.stderr assert result.returncode != 0 From ea0f5d75d0468e64741bf3dc213f6581e37abc6e Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Mon, 3 Feb 2025 12:42:06 -0800 Subject: [PATCH 7/8] renamed 'optionals' to 'options', updated comments, and testing functions --- kerngen/high_parser/options_handler.py | 78 +++++++++++++------------- kerngen/high_parser/types.py | 4 +- kerngen/tests/test_kerngen.py | 6 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/kerngen/high_parser/options_handler.py b/kerngen/high_parser/options_handler.py index 5cced49b..af627535 100644 --- a/kerngen/high_parser/options_handler.py +++ b/kerngen/high_parser/options_handler.py @@ -6,8 +6,8 @@ from abc import ABC, abstractmethod -class OptionalDict(ABC): - """Abstract class to hold optional key/value pairs""" +class OptionsDict(ABC): + """Abstract class to hold the options key/value pairs""" op_name: str = "" op_value = None @@ -17,8 +17,8 @@ def validate(self, value): """Abstract method, which defines how to valudate a value""" -class OptionalIntDict(OptionalDict): - """Holds a key/value pair for optional parameters of type Int""" +class OptionsIntDict(OptionsDict): + """Holds a key/value pair for options of type Int""" def __init__(self, name: str, min_val: int, max_val: int): self.min_val = min_val @@ -47,8 +47,8 @@ def op_value(self, value: int): ) -class OptionalIntBounds: - """Holds min/max/default values for optional parameters for type Int""" +class OptionsIntBounds: + """Holds min/max/default values for options of type Int""" int_min: int int_max: int @@ -60,80 +60,80 @@ def __init__(self, int_min: int, int_max: int, default: int | None): self.default = default -class OptionalDictFactory(ABC): - """Abstract class that creates OptionalDict objects""" +class OptionsDictFactory(ABC): + """Abstract class that creates OptionsDict objects""" MAX_KRNS_DELTA = 128 MAX_DIGIT = 3 MIN_KRNS_DELTA = MIN_DIGIT = 0 - optionals = { - "krns_delta": OptionalIntBounds(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), - "num_digits": OptionalIntBounds(MIN_DIGIT, MAX_DIGIT, None), + options = { + "krns_delta": OptionsIntBounds(MIN_KRNS_DELTA, MAX_KRNS_DELTA, 0), + "num_digits": OptionsIntBounds(MIN_DIGIT, MAX_DIGIT, None), } @staticmethod @abstractmethod - def create(name: str, value) -> OptionalDict: - """Abstract method, to define how to create an OptionalDict""" + def create(name: str, value) -> OptionsDict: + """Abstract method, to define how to create an OptionsDict""" -class OptionalIntDictFactory(OptionalDictFactory): - """OptionalDict parameter factory for Int types""" +class OptionsIntDictFactory(OptionsDictFactory): + """OptionsDict parameter factory for Int types""" @staticmethod - def create(name: str, value: int) -> OptionalIntDict: - """Create a OptionalInt object based on key/value pair""" - if name in OptionalIntDictFactory.optionals: - if isinstance(OptionalIntDictFactory.optionals[name], OptionalIntBounds): - optional_int = OptionalIntDict( + def create(name: str, value: int) -> OptionsIntDict: + """Create a OptionsInt object based on key/value pair""" + if name in OptionsIntDictFactory.options: + if isinstance(OptionsIntDictFactory.options[name], OptionsIntBounds): + options_int = OptionsIntDict( name, - OptionalIntDictFactory.optionals[name].int_min, - OptionalIntDictFactory.optionals[name].int_max, + OptionsIntDictFactory.options[name].int_min, + OptionsIntDictFactory.options[name].int_max, ) - optional_int.op_value = value - # add other optional types here + options_int.op_value = value + # add other options types here else: - raise KeyError(f"Invalid optional name: '{name}'") - return optional_int + raise KeyError(f"Invalid options name: '{name}'") + return options_int -class OptionalDictFactoryDispatcher: +class OptionsDictFactoryDispatcher: """An object dispatcher based on key/value pair""" @staticmethod - def create(name: str, value) -> OptionalDict: - """Creat an OptionalDict object based on the type of value passed in""" + def create(name: str, value) -> OptionsDict: + """Creat an OptionsDict object based on the type of value passed in""" if value.isnumeric(): value = int(value) match value: case int(): - return OptionalIntDictFactory.create(name, value) + return OptionsIntDictFactory.create(name, value) case _: raise ValueError(f"Current type '{type(value)}' is not supported.") -class OptionalDictParser: - """Parses key/value pairs and returns a dictionary of optional parameters""" +class OptionsDictParser: + """Parses key/value pairs and returns a dictionary of options""" @staticmethod def __default_values(): default_dict = {} - for key, val in OptionalDictFactory.optionals.items(): + for key, val in OptionsDictFactory.options.items(): default_dict[key] = val.default return default_dict @staticmethod - def parse(optionals: list[str]): - """Parse the optional parameter list and return a dictionary with values""" - output_dict = OptionalDictParser.__default_values() - for option in optionals: + def parse(options: list[str]): + """Parse the options list and return a dictionary with values""" + output_dict = OptionsDictParser.__default_values() + for option in options: try: key, value = option.split("=") - output_dict[key] = OptionalDictFactoryDispatcher.create( + output_dict[key] = OptionsDictFactoryDispatcher.create( key, value ).op_value except ValueError as err: raise ValueError( - f"Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" + f"Options must be key/value pairs (e.g. krns_delta=1, num_digits=3): '{option}'" ) from err return output_dict diff --git a/kerngen/high_parser/types.py b/kerngen/high_parser/types.py index e4b98831..ade46c5d 100644 --- a/kerngen/high_parser/types.py +++ b/kerngen/high_parser/types.py @@ -13,7 +13,7 @@ from .pisa_operations import PIsaOp -from .options_handler import OptionalDictParser +from .options_handler import OptionsDictParser class PolyOutOfBoundsError(Exception): @@ -159,7 +159,7 @@ class Context(BaseModel): def from_string(cls, line: str): """Construct context from a string""" scheme, poly_order, max_rns, *optionals = line.split() - optional_dict = OptionalDictParser.parse(optionals) + optional_dict = OptionsDictParser.parse(optionals) int_poly_order = int(poly_order) if ( int_poly_order < MIN_POLY_SIZE diff --git a/kerngen/tests/test_kerngen.py b/kerngen/tests/test_kerngen.py index 5577e17f..5a1d5b89 100644 --- a/kerngen/tests/test_kerngen.py +++ b/kerngen/tests/test_kerngen.py @@ -78,7 +78,7 @@ def test_context_optional_without_key(kerngen_path): ) assert not result.stdout assert ( - "ValueError: Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): '1'" + "ValueError: Options must be key/value pairs (e.g. krns_delta=1, num_digits=3): '1'" in result.stderr ) assert result.returncode != 0 @@ -92,7 +92,7 @@ def test_context_unsupported_optional_variable(kerngen_path): data_in=input_string, ) assert not result.stdout - assert "Invalid optional name: 'test'" in result.stderr + assert "Invalid options name: 'test'" in result.stderr assert result.returncode != 0 @@ -106,7 +106,7 @@ def test_context_optional_invalid_values(kerngen_path, invalid): ) assert not result.stdout assert ( - f"ValueError: Optional variables must be key/value pairs (e.g. krns_delta=1, num_digits=3): 'krns_delta={invalid}'" + f"ValueError: Options must be key/value pairs (e.g. krns_delta=1, num_digits=3): 'krns_delta={invalid}'" in result.stderr ) assert result.returncode != 0 From 563d8db94b31e5ee02aa316631bcdb850a9f49fc Mon Sep 17 00:00:00 2001 From: christopherngutierrez Date: Mon, 3 Feb 2025 12:44:21 -0800 Subject: [PATCH 8/8] Renamed testing fuctions --- kerngen/tests/test_kerngen.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kerngen/tests/test_kerngen.py b/kerngen/tests/test_kerngen.py index 5a1d5b89..19dc69c9 100644 --- a/kerngen/tests/test_kerngen.py +++ b/kerngen/tests/test_kerngen.py @@ -69,7 +69,7 @@ def test_multiple_contexts(kerngen_path): assert result.returncode != 0 -def test_context_optional_without_key(kerngen_path): +def test_context_options_without_key(kerngen_path): """Test kerngen raises an exception when more than one context is given""" input_string = "CONTEXT BGV 16384 4 1\nData a 2\n" result = execute_process( @@ -84,7 +84,7 @@ def test_context_optional_without_key(kerngen_path): assert result.returncode != 0 -def test_context_unsupported_optional_variable(kerngen_path): +def test_context_unsupported_options_variable(kerngen_path): """Test kerngen raises an exception when more than one context is given""" input_string = "CONTEXT BGV 16384 4 test=3\nData a 2\n" result = execute_process( @@ -97,7 +97,7 @@ def test_context_unsupported_optional_variable(kerngen_path): @pytest.mark.parametrize("invalid", [-1, 256, 0.1, "str"]) -def test_context_optional_invalid_values(kerngen_path, invalid): +def test_context_option_invalid_values(kerngen_path, invalid): """Test kerngen raises an exception if value is out of range for correct key""" input_string = f"CONTEXT BGV 16384 4 krns_delta={invalid}\nData a 2\n" result = execute_process(