Cloning
Clonable
¶
A base class allowing inheriting classes define how they should be cloned.
Any class inheriting from Clonable gains these behaviors:
(i) A new method named .clone()
becomes available;
(ii) __deepcopy__
and __copy__
work as aliases for .clone()
;
(iii) A new method, _get_cloned_state(self, *, memo: dict)
is now
defined and needs to be implemented by the inheriting class.
The method _get_cloned_state(...)
expects a dictionary named memo
,
which maps from the ids of already cloned objects to their clones.
If _get_cloned_state(...)
is to use deep_clone(...)
or deepcopy(...)
within itself, this memo
dictionary can be passed to these functions.
The return value of _get_cloned_state(...)
is a dictionary, which will
be used as the __dict__
of the newly made clone.
Source code in evotorch/tools/cloning.py
clone(*, memo=None)
¶
Get a clone of this object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
memo
|
Optional[dict]
|
Optionally a dictionary which maps from the ids of the already cloned objects to their clones. In most scenarios, when this method is called from outside, this can be left as None. |
None
|
Source code in evotorch/tools/cloning.py
ReadOnlyClonable
¶
Bases: Clonable
Clonability base class for read-only and/or immutable objects.
This is a base class specialized for the immutable containers of EvoTorch. These immutable containers have two behaviors for cloning: one where the read-only attribute is preserved and one where a mutable clone is created.
Upon being copied or deep-copied (using the standard Python functions),
the newly made clones are also read-only. However, when copied using the
clone(...)
method, the newly made clone is mutable by default
(unless the clone(...)
method was used with preserve_read_only=True
).
This default behavior of the clone(...)
method was inspired by the
copy()
method of numpy arrays (the inspiration being that the .copy()
of a read-only numpy array will not be read-only anymore).
Subclasses of evotorch.immutable.ImmutableContainer
inherit from
ReadOnlyClonable
.
Source code in evotorch/tools/cloning.py
clone(*, memo=None, preserve_read_only=False)
¶
Get a clone of this read-only object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
memo
|
Optional[dict]
|
Optionally a dictionary which maps from the ids of the already cloned objects to their clones. In most scenarios, when this method is called from outside, this can be left as None. |
None
|
preserve_read_only
|
bool
|
Whether or not to preserve the read-only behavior in the clone. |
False
|
Source code in evotorch/tools/cloning.py
Serializable
¶
Bases: Clonable
Base class allowing the inheriting classes become Clonable and picklable.
Any class inheriting from Serializable
becomes Clonable
(since
Serializable
is a subclass of Clonable
) and therefore is expected to
define its own _get_cloned_state(...)
(see the documentation of the
class Clonable
for details).
A Serializable
class gains a behavior for its __getstate__
. In this
already defined and implemented __getstate__
method, the resulting
dictionary of _get_cloned_state(...)
is used as the state dictionary.
Therefore, for Serializable
objects, the behavior defined in their
_get_cloned_state(...)
methods affect how they are pickled.
Classes inheriting from Serializable
are evotorch.Problem
,
evotorch.Solution
, evotorch.SolutionBatch
, and
evotorch.distributions.Distribution
. In their _get_cloned_state(...)
implementations, these classes use deep_clone(...)
on themselves to make
sure that their contained PyTorch tensors are copied using the .clone()
method, ensuring that those tensors are detached from their old storages
during the cloning operation. Thanks to being Serializable
, their
contained tensors are detached from their old storages both at the moment
of copying/cloning AND at the moment of pickling.
Source code in evotorch/tools/cloning.py
deep_clone(x, *, otherwise_deepcopy=False, otherwise_return=False, otherwise_fail=False, memo=None)
¶
A recursive cloning function similar to the standard deepcopy
.
The difference between deep_clone(...)
and deepcopy(...)
is that
deep_clone(...)
, while recursively traversing, will run the .clone()
method on the PyTorch tensors it encounters, so that the cloned tensors
are forcefully detached from their storages (instead of cloning those
storages as well).
At the moment of writing this documentation, the current behavior of
PyTorch tensors upon being deep-copied is to clone themselves AND their
storages. Therefore, if a PyTorch tensor is a slice of a large tensor
(which has a large storage), then the large storage will also be
deep-copied, and the newly made clone of the tensor will point to a newly
made large storage. One might instead prefer to clone tensors in such a
way that the newly made tensor points to a newly made storage that
contains just enough data for the tensor (with the unused data being
dropped). When such a behavior is desired, one can use this
deep_clone(...)
function.
Upon encountering a read-only and/or immutable data, this function will
NOT modify the read-only behavior. For example, the deep-clone of a
ReadOnlyTensor is still a ReadOnlyTensor, and the deep-clone of a
read-only numpy array is still a read-only numpy array. Note that this
behavior is different than the clone()
method of a ReadOnlyTensor
and the copy()
method of a numpy array. The reason for this
protective behavior is that since this is a deep-cloning operation,
the encountered tensors and/or arrays might be the components of the root
object, and changing their read-only attributes might affect the integrity
of this root object.
The deep_clone(...)
function needs to know what to do when an object
of unrecognized type is encountered. Therefore, the user is expected to
set one of these arguments as True (and leave the others as False):
otherwise_deepcopy
, otherwise_return
, otherwise_fail
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
Any
|
The object which will be deep-cloned. This object can be a standard
Python container (i.e. list, tuple, dict, set), an instance of
Problem, Solution, SolutionBatch, ObjectArray, TensorFrame,
ImmutableContainer, Clonable, and also any other type of object if
either the argument |
required |
otherwise_deepcopy
|
bool
|
Setting this as True means that, when an
unrecognized object is encountered, that object will be
deep-copied. To handle shared and cyclic-referencing objects,
the |
False
|
otherwise_return
|
bool
|
Setting this as True means that, when an unrecognized object is encountered, that object itself will be returned (i.e. will be a part of the created clone). |
False
|
otherwise_fail
|
bool
|
Setting this as True means that, when an unrecognized object is encountered, a TypeError will be raised. |
False
|
memo
|
Optional[dict]
|
Optionally a dictionary. In most scenarios, when this function is called from outside, this is expected to be left as None. |
None
|
Source code in evotorch/tools/cloning.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
|