pygx.patching¶
Systematic patching on symbolic values.
patching
¶
Systematic patching on symbolic values.
As pygx.Symbolic.rebind provides a flexible programming
interface for modifying symbolic values, why bother to have this module?
Here are the motivations:
-
Provide user friendly methods for addressing the most common patching patterns.
-
Provide a systematic solution for
- Patch semantic groups.
- Enable combination of these groups.
- Provide an interface that patching can be invoked from the command line.
Patcher
¶
Patcher(
*args: Any,
root_path: KeyPath | None = None,
override_args: bool = False,
ignore_extra_args: bool = False,
**kwargs: Any
)
Bases: Functor
Class that patches a symbolic value by returning a rebind dict.
To accomondate reusable patching, we introduced the concept of patcher,
which is a symbolic function that takes an input value with a list of
optional arguments and produces a patching rule (see pygx.patch)
or a tuple of (patching_rule, validation_rule). Validation rule is a callable
object that validate the patched object's integrity if it's being patched
by others later. A patcher can be created from a URI-like string, to
better serve the command-line interface.
A patcher can be created via pg.patcher decorator::
@pg.patcher([ ('lr', pg.typing.Float(min_value=0.0)) ]) def learning_rate(trainer, lr): return { 'training.learning_rate': lr }
OR:
@pg.patcher([ ('lr', pg.typing.Float(min_value=0.0)) ]) def learning_rate(trainer, lr): def _rebind_fn(k, v, p): if k and k.key == 'learning_rate' and isinstance(v, float): return lr return v return _rebind_fn
OR a composition of rules.
@pg.patcher([
('lr', pg.typing.Float(min_value=0.0)),
('weight_decay', pg.typing.Float(min_value=0.0))
])
def complex(trainer, lr, weight_decay):
# change_lr and change_weight_decay are instances of other patchers.
return [
change_lr(lr),
change_weight_decay(weight_decay)
]
After registration, a patcher object can be obtained from a URL-like string::
patcher = pg.patching.from_uri('cosine_decay?lr=0.1')
Then the user can use the patcher to patch an object::
patched_trainer = patcher.patch(trainer)
A list of patchers can be applied sequentially to accomondate combination of
semantic groups. A patcher in the sequence can propose updates to the original
value or generate a replacement to the original value. pg.patching.patch
is introduced for making it convenient to chain multiple patchers using
URI-like strings::
pg.patching.patch(trainer, [ 'cosine_decay?lr=0.1', 'some_other_patcher_string', ])
The user can lookup all registered patchers via::
print(pg.patching.patcher_names)
Source code in pygx/symbolic/_functor.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | |
patch
¶
patch(x: Symbolic) -> Any
Patches an input and return the input itself unless fully replaced.
Source code in pygx/patching/_rule_based.py
validate
¶
validate(x: Symbolic) -> None
Validates an input's integrity.
This method will be called in pygx.patch when a chain of patchers
have been applied, as to validate the patched object in chain still
conforms to the patcher's plan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Symbolic
|
The input after modification. |
required |
Source code in pygx/patching/_rule_based.py
ObjectFactory
¶
ObjectFactory(
value_type: type[Symbolic],
base_value: Symbolic | Callable[[], Symbolic] | str,
patches: PatchType | None = None,
params_override: dict[str, Any] | str | None = None,
) -> Any
A factory to create symbolic object from a base value and patches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_type
|
type[Symbolic]
|
Type of return value. |
required |
base_value
|
Symbolic | Callable[[], Symbolic] | str
|
An instance of |
required |
patches
|
PatchType | None
|
Optional patching rules. See |
None
|
params_override
|
dict[str, Any] | str | None
|
A rebind dict (or a JSON string as serialized rebind dict) as an additional patch to the value, |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
Value after applying |
Source code in pygx/patching/_object_factory.py
from_maybe_serialized
¶
Load value from maybe serialized form (e.g. JSON file or JSON string).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any | str
|
Source of value. It can be value (non-string type) itself, or a filepath, or a JSON string from where the value will be loaded. |
required |
value_type
|
type[Any] | None
|
An optional type to constrain the value. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
Value from source. |
Source code in pygx/patching/_object_factory.py
patch_on_key
¶
patch_on_key(
src: Symbolic,
regex: str,
value: Any = None,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched keys (leaf-node names).
Example::
d = pg.Dict(a=0, b=2) print(pg.patching.patch_on_key(d, 'a', value=3)) # {a=3, b=2}
print(pg.patching.patch_on_key(d, '.', value=3)) # {a=3, b=3}
class A(pg.Object): x: int
def on_sym_post_init(self):
super().on_sym_post_init()
self._num_changes = 0
def on_sym_change(self, updates):
super().on_sym_change(updates)
self._num_changes += 1
a = A() pg.patching.patch_on_key(a, 'x', value=2) # a._num_changes is 1.
pg.patching.patch_on_key(a, 'x', value=3) # a._num_changes is 2.
pg.patching.patch_on_keys(a, 'x', value=4, skip_notification=True) # a._num_changes is still 2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
symbolic value to patch. |
required |
regex
|
str
|
Regex for key name. |
required |
value
|
Any
|
New value for field that satisfy |
None
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces new value based on old value.
If not None, |
None
|
skip_notification
|
bool | None
|
If True, |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_member
¶
patch_on_member(
src: Symbolic,
cls: type[Any] | tuple[type[Any], ...],
name: str,
value: Any = None,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values that are the requested member of classes.
Example::
d = pg.Dict(a=A(x=1), b=2) print(pg.patching.patch_on_member(d, A, 'x', 2) # {a=A(x=2), b=4}
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
symbolic value to patch. |
required |
cls
|
type[Any] | tuple[type[Any], ...]
|
In which class the member belongs to. |
required |
name
|
str
|
Member name. |
required |
value
|
Any
|
New value for field that satisfy |
None
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces new value based on old value.
If not None, |
None
|
skip_notification
|
bool | None
|
If True, |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_path
¶
patch_on_path(
src: Symbolic,
regex: str,
value: Any = None,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched paths.
Example::
d = pg.Dict(a={'x': 1}, b=2) print(pg.patching.patch_on_path(d, '.*x', value=3)) # {a={x=1}, b=2}
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
symbolic value to patch. |
required |
regex
|
str
|
Regex for key path. |
required |
value
|
Any
|
New value for field that satisfy |
None
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces new value based on old value.
If not None, |
None
|
skip_notification
|
bool | None
|
If True, |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_type
¶
patch_on_type(
src: Symbolic,
value_type: type[Any] | tuple[type[Any], ...],
value: Any = None,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched types.
Example::
d = pg.Dict(a={'x': 1}, b=2) print(pg.patching.patch_on_type(d, int, value_fn=lambda x: x * 2)) # {a={x=2}, b=4}
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
symbolic value to patch. |
required |
value_type
|
type[Any] | tuple[type[Any], ...]
|
Value type to match. |
required |
value
|
Any
|
New value for field that satisfy |
None
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces new value based on old value.
If not None, |
None
|
skip_notification
|
bool | None
|
If True, |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_value
¶
patch_on_value(
src: Symbolic,
old_value: Any,
value: Any = None,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched values.
Example::
d = pg.Dict(a={'x': 1}, b=1) print(pg.patching.patch_on_value(d, 1, value=3)) # {a={x=3}, b=3}
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
symbolic value to patch. |
required |
old_value
|
Any
|
Old value to match. |
required |
value
|
Any
|
New value for field that satisfy |
None
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces new value based on old value.
If not None, |
None
|
skip_notification
|
bool | None
|
If True, |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
allow_repeated_patcher_registration
¶
If True, allow registration with the same patch name.
Source code in pygx/patching/_rule_based.py
from_uri
¶
from_uri(uri: str) -> Patcher
Create a Patcher object from a URI-like string.
Source code in pygx/patching/_rule_based.py
patch
¶
patch(value: Symbolic, rule: PatchType) -> Any
Apply patches to a symbolic value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Symbolic
|
A symbolic value to patch. |
required |
rule
|
PatchType
|
A patching rule is one of the following:
1) A dict of symbolic paths to the new values.
2) A rebind function defined by signature (k, v) or (k, v, p).
See |
required |
Returns:
| Type | Description |
|---|---|
Any
|
Value after applying the patchers. If any patcher returned a new value |
Any
|
(by returning a single-item dict that containing '' as key), the return |
Any
|
value will be a different object other than |
Any
|
will be returned after applying the patches. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Error if the patch name and arguments cannot be parsed successfully. |
Source code in pygx/patching/_rule_based.py
patcher
¶
patcher(
args: list[tuple[str, ValueSpec]] | None = None,
name: str | None = None,
auto_typing: bool = False,
auto_doc: bool = False,
) -> Any
Decorate a function into a Patcher and register it.
A patcher function is defined as:
<patcher_fun> := <fun_name>(<target>, [parameters])
The signature takes at least one argument as the patching target, with additional arguments as patching parameters to control the details of this patch.
Example::
@pg.patching.patcher([ ('x': pg.typing.Int()) ]) def increment(v, x=1): return pg.symbolic.get_rebind_dict( lambda k, v, p: v + x if isinstance(v, int) else v)
# This patcher can be called via: # pg.patching.apply(v, [increment(x=2)]) # or pg.patching.apply(v, ['increment?x=2'])
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
list[tuple[str, ValueSpec]] | None
|
A list of (arg_name, arg_value_spec) to schematize patcher arguments. |
None
|
name
|
str | None
|
String to be used as patcher name in URI. If None, function name will be used as patcher name. |
None
|
auto_typing
|
bool
|
If True, automatically inference the typing from the function signature. |
False
|
auto_doc
|
bool
|
If True, use the docstring of the function as the docstring of the patcher. |
False
|
Returns:
| Type | Description |
|---|---|
Any
|
A decorator that converts a function into a Patcher subclass. |
Source code in pygx/patching/_rule_based.py
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | |
options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2