I recommend that you work through a tutorial on for loops -- the information you need is there, as well as other hints on using controlled iteration. To solve your immediate need, simply increase your upper bound by one loop increment:
inc = 2*np.pi/360
theta = np.arange(0, angle/2 + inc, inc)
Answer from Prune on Stack OverflowI recommend that you work through a tutorial on for loops -- the information you need is there, as well as other hints on using controlled iteration. To solve your immediate need, simply increase your upper bound by one loop increment:
inc = 2*np.pi/360
theta = np.arange(0, angle/2 + inc, inc)
You could write a function like this
def arange_endpoint(start,stop,step):
L = stop-start
n = int(L/step)
stop_ = start+n*step
return np.linspace(start,stop_,n+1)
It will adapt the stop (endpoint) to the closest one smaller or equal to the provided one, see these examples:
arange_endpoint(3, 10.4, .5)
>>> array([ 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5, 8. ,
8.5, 9. , 9.5, 10. ])
arange_endpoint(3, 10.5, .5)
>>> array([ 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5, 8. ,
8.5, 9. , 9.5, 10. , 10.5])
A simpler approach to get the desired output is to add the step size in the upper limit. For instance,
np.arange(start, end + step, step)
would allow you to include the end point as well. In your case:
np.arange(0.0, 0.6 + 0.2, 0.2)
would result in
array([0. , 0.2, 0.4, 0.6]).
Update 2023-04-21
Had a bug in the code for stop - start being a non-integer number of steps => fixed
In short / TLDR
unexpected behavior:
>>> np.arange(1, 1.3, .1) # UNEXPECTED
array([1. , 1.1, 1.2, 1.3])
fix:
>>> from arange_cust import *
>>> np_arange_closed(1, 1.3, .1)
array([1. , 1.1, 1.2, 1.3])
>>> np_arange_open(1, 1.3, .1)
array([1. , 1.1, 1.2])
Background information
I had your problem a few times as well. I usually quick-fixed it with adding a small value to stop. As mentioned by Kasrâmvd in the comments, the issue is a bit more complex, as floating point rounding errors can occur in numpy.arange (see here and here).
Unexpected behavior can be found in this example:
>>> np.arange(1, 1.3, 0.1)
array([1. , 1.1, 1.2, 1.3])
To clear up things a bit for myself, I decided to be very careful with np.arange.
Code
arange_cust.py:
import numpy as np
def np_arange_cust(
*args, rtol: float=1e-05, atol: float=1e-08, include_start: bool=True, include_stop: bool = False, **kwargs
):
"""
Combines numpy.arange and numpy.isclose to mimic open, half-open and closed intervals.
Avoids also floating point rounding errors as with
>>> np.arange(1, 1.3, 0.1)
array([1., 1.1, 1.2, 1.3])
Parameters
----------
*args : float
passed to np.arange
rtol : float
if last element of array is within this relative tolerance to stop and include[0]==False, it is skipped
atol : float
if last element of array is within this relative tolerance to stop and include[1]==False, it is skipped
include_start: bool
if first element is included in the returned array
include_stop: bool
if last elements are included in the returned array if stop equals last element
kwargs :
passed to np.arange
Returns
-------
np.ndarray :
as np.arange but eventually with first and last element stripped/added
"""
# process arguments
if len(args) == 1:
start = 0
stop = args[0]
step = 1
elif len(args) == 2:
start, stop = args
step = 1
else:
assert len(args) == 3
start, stop, step = tuple(args)
arr = np.arange(start, stop, step, **kwargs)
if not include_start:
arr = np.delete(arr, 0)
if include_stop:
if np.isclose(arr[-1] + step, stop, rtol=rtol, atol=atol):
arr = np.c_[arr, arr[-1] + step]
else:
if np.isclose(arr[-1], stop, rtol=rtol, atol=atol):
arr = np.delete(arr, -1)
return arr
def np_arange_closed(*args, **kwargs):
return np_arange_cust(*args, **kwargs, include_start=True, include_stop=True)
def np_arange_open(*args, **kwargs):
return np_arange_cust(*args, **kwargs, include_start=True, include_stop=False)
Pytests
To avoid bugs in future, here is a testing module. In case we find something again, lets add a testcase. test_arange_cust.py:
import numpy as np
from arange_cust import np_arange_cust, np_arange_closed, np_arange_open
import pytest
class Test_np_arange_cust:
paras_minimal_working_example = {
"arange simple": {
"start": 0, "stop": 7, "step": 1, "include_start": True, "include_stop": False,
"res_exp": np.array([0, 1, 2, 3, 4, 5, 6])
},
"stop not on grid": {
"start": 0, "stop": 6.5, "step": 1, "include_start": True, "include_stop": False,
"res_exp": np.array([0, 1, 2, 3, 4, 5, 6])
},
"arange failing example: stop excl": {
"start": 1, "stop": 1.3, "step": .1, "include_start": True, "include_stop": False,
"res_exp": np.array([1., 1.1, 1.2])
},
"arange failing example: stop incl": {
"start": 1, "stop": 1.3, "step": .1, "include_start": True, "include_stop": True,
"res_exp": np.array([1., 1.1, 1.2, 1.3])
},
"arange failing example: stop excl + start excl": {
"start": 1, "stop": 1.3, "step": .1, "include_start": False, "include_stop": False,
"res_exp": np.array([1.1, 1.2])
},
"arange failing example: stop incl + start excl": {
"start": 1, "stop": 1.3, "step": .1, "include_start": False, "include_stop": True,
"res_exp": np.array([1.1, 1.2, 1.3])
},
}
@pytest.mark.parametrize(
argnames=next(iter(paras_minimal_working_example.values())).keys(),
argvalues=[tuple(paras.values()) for paras in paras_minimal_working_example.values()],
ids=paras_minimal_working_example.keys(),
)
def test_minimal_working_example(self, start, stop, step, include_start, include_stop, res_exp):
res = np_arange_cust(start, stop, step, include_start=include_start, include_stop=include_stop)
assert np.allclose(res, res_exp), f"Unexpected result: {res=}, {res_exp=}"