Every question from here on builds tensors, autograd, and neural networks on top of NumPy, mirroring real PyTorch's own API surface (torch itself is never imported, the same fidelity discipline 01-classical-ml's linear held to when it mirrored torch.nn.functional.linear exactly). Before any of that can work, the most basic operation of all, "make an array," has to actually produce the same numbers a real PyTorch program would.
That sounds trivial until you notice NumPy and PyTorch quietly disagree about it. Ask NumPy for an array of floats and it hands you float64. Ask PyTorch the same thing and you get float32. If tensor-creation helpers here silently kept NumPy's own default, every later question's numbers would be technically different from what real PyTorch produces, small precision differences that compound across a training loop. Getting this one gotcha right up front is what lets everything built afterward be trusted.
Implement four creation helpers, make_tensor, zeros, ones, arange, each mirroring its torch.* namesake. The core idea from Theory, PyTorch overrides floating-point defaults to float32 but leaves integer/boolean inference alone, applies identically in all four; make_tensor and arange need to inspect an inferred dtype to decide whether to override it, zeros/ones don't need any inference at all since their own dtype parameter already defaults to the right value.
make_tensor(data, dtype=None): infer from data like np.array would, unless dtype is given explicitly.dtype is given and the inferred dtype is floating-point, use float32, never NumPy's own float64 default.dtype is given and the inferred dtype is integer or boolean, keep NumPy's own inference unchanged.zeros(shape, dtype=np.float32) / ones(shape, dtype=np.float32): default dtype is float32, matching torch.zeros/torch.ones, not NumPy's own np.zeros/np.ones defaults.arange(start, stop, step=1, dtype=None): same float32-override rule as make_tensor, applied to np.arange's own inferred dtype.dtype argument always wins over any inference, in all four functions.Open one at a time. Each gives away a little more than the last.
np.issubdtype(array.dtype, np.floating) answers "did NumPy infer a floating type," regardless of whether that's float32, float64, or something else. That's the check you need before deciding whether to override.
Both make_tensor and arange follow the exact same shape: compute the array first with NumPy's own rules (np.array(data) or np.arange(start, stop, step)), then apply one shared decision — explicit dtype wins, otherwise override only if floating, otherwise leave untouched.
Click "Run Tests" to test your implementation