"""AI01 lab: a scalar autograd engine. You write every method in this file.Each docstring is a contract, and the tests check exactly these contracts.Work through the lab steps in the book (AI01, section 3) in order, and run`python check.py N` after finishing step N.Ground rules- Standard library only. `math` is all you need.- Do not define __eq__ on Value. backward() keeps Values in a set, and a class that defines __eq__ without __hash__ becomes unhashable (bug B10 in AI01).- Write each local derivative on paper before you type it (the Modular Workbench): what is d(out)/d(input), in terms of numbers you already have?"""importmathclassValue:"""One number in a computation, plus how much the final output depends on it. Attributes (the tests and tools/viz.py rely on these exact names): data float. The number itself, computed in the forward pass. grad float. d(final output)/d(this value). Starts at 0.0; backward() fills it in. _prev the Values this one was computed from (its children in the graph), empty for a leaf. Store a tuple: it keeps the inputs in order, so every run adds gradients in the same order and prints the same digits. (A set also works, but it orders Values by memory address, so the last digits drift between runs.) _op str naming the operation that made this Value ('+', '*', '**2', 'tanh', ...); '' for a leaf. label str, an optional name used in pictures ('a', 'loss', ...). _backward a function taking no arguments. Calling it adds this Value's contribution to each child's grad (local derivative times self.grad). For a leaf it does nothing. """# ------------------------------------------------------------------# STEP 1: hold a number and build the graph going forward.# ------------------------------------------------------------------def__init__(self,data,_children=(),_op="",label=""):# TODO(human): Step 1. Store data (as a float) and start grad at 0.0.# Record _prev, _op and label, and give the node a _backward that does nothing.raiseNotImplementedError("Step 1: Value.__init__")def__repr__(self):"""Return text like 'Value(data=2.5, grad=0.0)'."""raiseNotImplementedError("Step 1: Value.__repr__")def__add__(self,other):"""self + other. If `other` is a plain int or float, wrap it in a Value first. Forward (step 1): return a new Value with data self.data + other.data, children (self, other), and op '+'. Backward (step 2): attach a _backward to the result. The local derivative of a + b with respect to each input is 1. """raiseNotImplementedError("Step 1: Value.__add__")def__mul__(self,other):"""self * other. Wrap plain numbers; op '*'. Backward (step 2): the local derivative of a * b with respect to a is b's data, and with respect to b it is a's data. """raiseNotImplementedError("Step 1: Value.__mul__")# ------------------------------------------------------------------# STEP 3: the whole backward pass.# ------------------------------------------------------------------defbackward(self):"""Fill in .grad for every Value this one depends on. 1. Put every node of the graph in topological order: each node after all of its children. Use a depth-first search with a visited set. 2. Seed: the output's gradient with respect to itself is 1. 3. Visit the nodes in REVERSE topological order and call each _backward. Assumes every grad in the graph starts at 0.0. To backpropagate again, zero the grads first (or build a fresh graph). A recursive search is fine for steps 1-8. The optional stretch test (python check.py --stretch) uses a 5000-node chain, deeper than Python's limit of about 1000 nested calls, so it needs an explicit stack. """raiseNotImplementedError("Step 3: Value.backward")# ------------------------------------------------------------------# STEP 5: more operations. Each needs a forward value AND a _backward.# Reuse the operations you already have wherever you can.# ------------------------------------------------------------------def__pow__(self,n):"""self ** n, where n is a plain int or float (not a Value). Op f'**{n}'. Local derivative: n * self.data ** (n - 1). Watch the sign of n - 1 when n is negative (a classic slip: -48x^4 written for -48x^(-4)). """raiseNotImplementedError("Step 5: Value.__pow__")def__neg__(self):"""-self."""raiseNotImplementedError("Step 5: Value.__neg__")def__radd__(self,other):"""other + self, when `other` is a plain number (for example 2 + a)."""raiseNotImplementedError("Step 5: Value.__radd__")def__sub__(self,other):"""self - other."""raiseNotImplementedError("Step 5: Value.__sub__")def__rsub__(self,other):"""other - self, when `other` is a plain number (for example 5 - a)."""raiseNotImplementedError("Step 5: Value.__rsub__")def__rmul__(self,other):"""other * self, when `other` is a plain number (for example 2 * a)."""raiseNotImplementedError("Step 5: Value.__rmul__")def__truediv__(self,other):"""self / other. Hint: a / b = a * b**(-1)."""raiseNotImplementedError("Step 5: Value.__truediv__")def__rtruediv__(self,other):"""other / self, when `other` is a plain number (for example 4 / a)."""raiseNotImplementedError("Step 5: Value.__rtruediv__")defexp(self):"""e ** self. Op 'exp'. Local derivative: e ** x, which is out.data itself."""raiseNotImplementedError("Step 5: Value.exp")deftanh(self):"""Hyperbolic tangent. Op 'tanh'. Local derivative: 1 - tanh(x)**2. Must work for large inputs: tanh of 1000 is 1.0 with gradient 0.0, and tanh of -1000 is -1.0, with no OverflowError. """raiseNotImplementedError("Step 5: Value.tanh")defrelu(self):"""max(0, x). Op 'relu'. Local derivative: 1 where x > 0, and 0 where x <= 0. At exactly 0 the true derivative does not exist; 0 is a convention (PyTorch uses it too). """raiseNotImplementedError("Step 5: Value.relu")