2021-12-26

The idea is to construct a spiral (Archimedean spiral or others) around a right-cone simulating a Christmas tree. We want to model the situation and understand how many lights or how long the strip(s) should be to wrap the proper amount of loops around the tree. This blog will establish the basic model and mathematics. This article will walk you through the mathematical derivation and the calculations. The derivations are for completeness. An understanding of the process is not required to use the results.

The full Jupyter notebooks can be found here. This section can be found here.

We want to construct a set of methods and equations that can describe an LED strip arranged in a spiral around a right-circular cone with base radius \(r\) and height, \(h\).

The Archimedean spiral is the simplest of the spirals. It is defined, in polar coordinates, by:

\[ \begin{equation} \tag{1} r(\theta) = b \theta + p \end{equation} \]

Where:

- \(r(\theta)\) - The distance to a point on the curve defined by \(\theta\)
- \(\theta\) - An angle
- \(b\) - A constant defining the distance between successive intersection points between the curve and the axis
- \(p\) - A constant, the initial radius of the curve, the starting point

NOTE: The Archimedean spiral is also an arithmetic spiral.

NOTE: The distance between two successive turns is \(d = 2 \pi b\). In other words, the distances of intersection points along a line through the origin and the spiral are the same.

NOTE: For this article we are going to assume \(p = 0\).

The general parametric equations are:

\[ \begin{matrix} x(\theta) = r \sin \theta \\ y(\theta) = r \cos \theta \end{matrix} \]

Convert the polar equation (1) to parametric form:

\[ \begin{matrix} x(\theta) = b \theta \sin \theta \\ y(\theta) = b \theta \cos \theta \end{matrix} \]

We can extend the parametric system to three dimensions:

\[ \begin{equation} \tag{2} \begin{matrix} x(\theta) = b \theta \sin \theta \\ y(\theta) = b \theta \cos \theta \\ z(\theta) = z_0 + m \theta \end{matrix} \end{equation} \]

Where:

- \(z_0\) - The initial starting position of the spiral
- \(m\) - The slope of the cone with respect to the
`XY`

plane

NOTE: We’ll assume that \(\theta \ge 0\)

I want the spiral to look like a tree. The spiral opens upwards and looks like an inverted tree given positive angles. I want the spiral to open downwards, starting from the top of the cone to the base. It should also end with the radius that matches the radius of the cone itself. Let’s re-work the model.

We will use the same set of equations as defined in (2). We will use negative angles and starting from \(z_0 = h\) to achieve the effect:

\[ \theta \le 0\]

We also want to stop the spiral when:

\[ b \theta \le r\]

If we know \(b\) and \(r\), we can solve for the stop angle, \(\theta\):

\[ \begin{equation} \tag{3} \theta = \frac{r}{b} \end{equation} \]

We also need to solve for \(m\) to determine the proper slope of the cone so the spiral fits within our constraints.

\[ \begin{split} z(\theta) & = z_0 + m \theta \\ z(\theta) & = z_0 + m \frac{r}{b} \\ z(\theta) - z_0 & = m \frac{r}{b} \end{split} \]|

\[ \begin{equation} \tag{4} m = \frac{b}{r} \left( z(\theta) - z_0 \right) \end{equation} \]

In our case, \(z(\theta) = 0\) and \(z_0 = h\), simplifying (4) to:

\[ \begin{equation} \tag{5} \therefore m =-h\frac{b}{r} \end{equation} \]

```
Cone Radius (r) = 2.0000
Cone Height (h) = 5.0000
```

In the previous section, `0 - conical spirals.ipynb`

, we developed the model to trace the Archimedean spiral around the cone. In this section, we explore the derivation of the equations to calculate the arc length of the spiral (Archimedean spiral). In practical terms, if we use LED strips to create a spiral around the cone, we can determine the required LED strip length to get the job done.

The full Jupyter notebooks can be found in this repository. This section can be found here.

Now that we can plot the spiral, how long is it? Essentially, we want to determine the length of the vector function:

\[ \begin{equation} \tag{6} \large \vec r \left(t \right) = \left \langle f(t), g(t), h(t) \right \rangle \end{equation} \]

Writing the vector function in parametric form:

\[ \begin{matrix} \large x = f(t) \\ \large y = g(t) \\ \large z = h(t) \end{matrix} \]

In 3 Dimensions, the length of the curve, $ r (t )$ on the interval \(m_1 \le t \le m_2\) is:

\[ \large \begin{equation} \tag{7} L = \int_{m_1}^{m_1} \sqrt{\left[f'(t)\right]^2 + \left[h'(t)\right]^2 + \left[g'(t) \right]^2} \; \frac{\mathrm{d} }{\mathrm{d} t} \end{equation} \]

NOTE: In the notebook, 0 - conical spirals.ipynb, we represented the angle as \(\theta\). For this derivation, consider \(t = \theta\). We will continue to use \(t\) to represent the polar angle.

The parametric equations for the spiral are:

\[ \large \begin{equation} \tag{8} \begin{matrix} x(t) = bt \cos t \\ y(t) = bt \sin t \\ z(t) = kt + z_0 \end{matrix} \end{equation} \]

Where \(b\) and \(k\) are constants that control the shape and height of the spiral and \(t\) is the angle in radians controlling the number of `loops`

.

NOTE: In the notebook, 0 - conical spirals.ipynb, we represented the shape of the cone with \(m\). For the rest of this document, consider \(k = m\), from this point forward.

Take the derivative of each equation:

\[ \large \begin{equation} \tag{9} \begin{matrix} x'(t) = b \left( \cos(t) - t \sin(t) \right) \\ y'(t) = b \left(\sin(t) + t \cos(t) \right) \\ z'(t) = k \end{matrix} \end{equation} \]

Let’s rearrange things to make dealing with the polynomial and the radical easier:

\[ \left[ f'(x) \right]^2 = \left[ b \cdot \left(\cos(t) - t \sin(t) \right) \right]^2\]

\[\left[ f'(x) \right]^2 = b^2 \cos^2 t - b^2 t \sin(2 t) + b^2 t^2 \sin^2 t\]

Where:

\[\sin(2 t) = 2 \sin(t) \cos(t)\]

\[\sin(t) \cos(t) = \frac{1}{2} \cdot \sin(2 t)\]

\[\sin^2 t + \cos^2 t = 1\]

\[ [h'(t)]^2 = \left( b \cdot \left(\sin(t) + t \cdot \cos(t) \right) \right)^2\]

\[ [h'(t)]^2 = b^2 \sin^2 t + b^2 t \sin (2 t) + b^2 t^2 \cos^2 t\]

\[ [g'(t)]^2 = k^2\]

\[ [f'(t)]^2 + h'(t)^2 + g'(t)^2 = b^2 + b^2 t^2 + k^2\]

\[ \large \begin{equation} \tag{10} L = \int_{m_1}^{m_1} \sqrt{\left[f'(t)\right]^2 + \left[h'(t)\right]^2 + \left[g'(t) \right]^2} \; \frac{\mathrm{d} }{\mathrm{d} t} \end{equation} \]

NOTE: Solving the integral we made use of http://www.integral-calculator.com/

Solve the integral and Substitute:

\[t=\frac{\sqrt{k^2+b^2}\tan\left(u\right)}{b} \rightarrow u=\arctan\left(\frac{bt}{\sqrt{k^2+b^2}}\right)\]

\[ \frac{\mathrm{d}t}{\mathrm{d}u}=\frac{\sqrt{k^2+b^2}\sec^2\left(u\right)}{b} \]

\[ ={\int}\frac{\sqrt{k^2+b^2}\sec^2\left(u\right)\sqrt{\left(k^2+b^2\right)\tan^2\left(u\right)+k^2+b^2}}{b}\,\mathrm{d}u\]

Simplify using:

\[\left(k^2+b^2\right)\tan^2\left(u\right)+k^2+b^2=\left(k^2+b^2\right)\sec^2\left(u\right)\]

Becomes:

\[=\class{steps-node}{\cssId{steps-node-1}{\frac{k^2+b^2}{b}}}{\int}\sec^3\left(u\right)\,\mathrm{d}u\]

Now Solving:

\[{\int}\sec^3\left(u\right)\,\mathrm{d}u\]

Apply reduction formula with \(n=3\):

\[\small{{\int}\sec^{\mathtt{n}}\left(u\right)\,\mathrm{d}u=\class{steps-node}{\cssId{steps-node-2}{\frac{\mathtt{n}-2}{\mathtt{n}-1}}}{\int}\sec^{\mathtt{n}-2}\left(u\right)\,\mathrm{d}u+\frac{\sec^{\mathtt{n}-2}\left(u\right)\tan\left(u\right)}{\mathtt{n}-1}}\]

\[=\class{steps-node}{\cssId{steps-node-3}{\frac{1}{2}}}{\int}\sec\left(u\right)\,\mathrm{d}u+\frac{\sec\left(u\right)\tan\left(u\right)}{2}\]

Now Solving:

\[{\int}\sec\left(u\right)\,\mathrm{d}u =\ln\left(\tan\left(u\right)+\sec\left(u\right)\right)\]

The solution is a standard integral. Plugin solved integrals:

\[\class{steps-node}{\cssId{steps-node-4}{\frac{1}{2}}}{\int}\sec\left(u\right)\,\mathrm{d}u+\frac{\sec\left(u\right)\tan\left(u\right)}{2} =\frac{\ln\left(\tan\left(u\right)+\sec\left(u\right)\right)}{2}+\frac{\sec\left(u\right)\tan\left(u\right)}{2}\]

\[\class{steps-node}{\cssId{steps-node-5}{\frac{k^2+b^2}{b}}}{\int}\sec^3\left(u\right)\,\mathrm{d}u =\frac{\left(k^2+b^2\right)\ln\left(\tan\left(u\right)+\sec\left(u\right)\right)}{2b}+\frac{\left(k^2+b^2\right)\sec\left(u\right)\tan\left(u\right)}{2b}\]

Undo substitution:

\[u=\arctan\left(\frac{bt}{\sqrt{k^2+b^2}}\right)\]

Use:

\[\tan\left(\class{steps-node}{\cssId{steps-node-6}{\arctan\left(\frac{bt}{\sqrt{k^2+b^2}}\right)}}\right)=\frac{bt}{\sqrt{k^2+b^2}}\]

\[\sec\left(\class{steps-node}{\cssId{steps-node-7}{\arctan\left(\frac{bt}{\sqrt{k^2+b^2}}\right)}}\right)=\sqrt{\frac{b^2t^2}{k^2+b^2}+1}\]

\[=\frac{\left(k^2+b^2\right)\ln\left(\sqrt{\frac{b^2t^2}{k^2+b^2}+1}+\frac{bt}{\sqrt{k^2+b^2}}\right)}{2b}+\frac{\sqrt{k^2+b^2}t\sqrt{\frac{b^2t^2}{k^2+b^2}+1}}{2}\]

Solution:

\[ \small \begin{equation} \tag{11} \begin{split} \int \sqrt{b^2t^2+k^2+b^2}\,\mathrm{d}t & = \\ & = \frac{\left(k^2+b^2\right)\ln\left(\left|\sqrt{\frac{b^2t^2}{k^2+b^2}+1}+\frac{bt}{\sqrt{k^2+b^2}}\right|\right)}{2b}+\frac{\sqrt{k^2+b^2}t\sqrt{\frac{b^2t^2}{k^2+b^2}+1}}{2}+C & \\ & =\frac{\left(k^2+b^2\right)\ln\left(\left|\sqrt{b^2\left(t^2+1\right)+k^2}+bt\right|\right)+bt\sqrt{b^2\left(t^2+1\right)+k^2}}{2b}+C \end{split} \end{equation} \]

NOTE: Applying the limits will eliminate the constant…

```
Cone Radius (r) = 2.0000
Cone Height (h) = 5.0000
```

Given a conical (Archimedean) spiral, calculate where we could place supports. We’ll assume they will be at integer intervals. If the height is 5 units, we’ll want support at 1, 2, 3 and 4. The way this works, we specify the support interval delta along with the cone’s height, `Z`

, and it will calculate the locations and lengths of the support struts.

The full Jupyter notebooks can be found in this repository. This section can be found here.

We want to find the points on the curve that intersect specific elevations between \(0\) and \(h\). Our current model:

\[ \begin{equation} \tag{12} \begin{matrix} x(\theta) = b \theta \sin \theta \\ y(\theta) = b \theta \cos \theta \\ z(\theta) = z_0 + m \theta \end{matrix} \end{equation} \]

The angles where the curve passes through specific elevations can be calculated with:

\[ \begin{equation} \tag{13} \theta = \frac{z(\theta) - z_0}{m} \end{equation} \]

Essentially, we must calculate the particular angles that coincide with the heights of interest. In our case, we’ll simply use the integer values from \(0\) to \(h\).

```
Cone Radius (r) = 2.0000
Cone Height (h) = 5.0000
Strut Offset Height (Δh) = 0.5
------
Support Length (@ Z=0.0) = 2.00
Support Length (@ Z=0.5) = 1.80
Support Length (@ Z=1.0) = 1.60
Support Length (@ Z=1.5) = 1.40
Support Length (@ Z=2.0) = 1.20
Support Length (@ Z=2.5) = 1.00
Support Length (@ Z=3.0) = 0.80
Support Length (@ Z=3.5) = 0.60
Support Length (@ Z=4.0) = 0.40
Support Length (@ Z=4.5) = 0.20
```

The result is quite lovely. The support points are calculated at specific `Z`

values, and the support strut lengths are calculated and plotted for those heights.

We could adjust the model to place supports at regular angles instead of regular elevations. For example, we could add supports at the regular angles (0, pi/2, pi, 3pi/2 + etc.). It would mean that we would trace the curve and put supports on the north, east, south and west sides. This might be easier to fabricate.

The other sections will calculate the arc length of the spiral based on the cone width and height. This section will alter those parameters to fit a given arc length. Why do this? The primary use case is to fit a design to a specific LED strip length. For example, how big of a cone can you build using a 5m strip without cutting the strip or adding extra pieces.

The full Jupyter notebooks can be found in this repository. This section can be found here.

```
Cone Radius (r) = 2.0000
Cone Height (h) = 5.0000
```

We want to determine the cone radius, height and loop spacing with a fixed spiral arc length. For example, we have a 5m strip, and we do not want to cut it. What radius, height, and loop spacing values will give us this fixed length? We’ll use the Scipy.Optimize modules to determine this.

Let’s construct a target function below, formulated in a compatible way with the optimize module.

```
def objective_fixed_length(x, L:float):
"""
This method defines the objective function, the function we want to
minimize. This particular instance will search for r, h and d, generating a
particular arc length, L.
# args
x - 1-D array with shape (n,) of variables to solve for
- x[0] - r - cone base radius
- x[1] - h - cone height
- x[2] - d - horizontal spacing between loops
L - The arc length we are attempting to find.
# NOTE
From the Scipy.Optimization docs:
The objective function to be minimized.
fun(x, *args) -> float
where x is an 1-D array with shape (n,) and args is a tuple of the
fixed parameters needed to completely specify the function.
We have to construct the target function using this method to accommodate
the optimize module It uses an array to specify the target variables that
we want to optimize and separate arguments for the constants we don't want
to optimize.
"""
# r, h, d = x
# return np.abs(L - spiral_arc_length_range(r, h, d))
return np.abs(L - spiral_arc_length_range(*x))
```

Let’s use the minimize method and see what we can do.

NOTE: We have to provide an initial guess for the other variables. The initial guess will affect the outcome. Most likely, there are many solutions to the problem. It is recommended to use values close to the dimensions of interest. The optimization uses the initial values to guide the process. In most cases, it will converge to the closest values. There may be many different solutions.

```
= 5 # m
arc_length = 1 # m
r_init = 3 # m
h_init = 0.5 # m
d_init
= optimize.minimize(
result
objective_fixed_length,
(r_init, h_init, d_init), =(arc_length,),
args='Nelder-Mead',
method=1e-8,
tol
)
print(result)
print('-------')
print('Optimal Values:')
print(f'r = {result.x[0]:.4f}')
print(f'h = {result.x[1]:.4f}')
print(f'd = {result.x[2]:.4f}')
```

```
final_simplex: (array([[0.79863094, 3.15843977, 0.57331696],
[0.79863094, 3.15843976, 0.57331696],
[0.79863094, 3.15843976, 0.57331696],
[0.79863094, 3.15843977, 0.57331696]]), array([1.97077732e-09, 3.20198268e-09, 3.69157505e-09, 5.59641045e-09]))
fun: 1.9707773191157685e-09
message: 'Optimization terminated successfully.'
nfev: 165
nit: 83
status: 0
success: True
x: array([0.79863094, 3.15843977, 0.57331696])
-------
Optimal Values:
r = 0.7986
h = 3.1584
d = 0.5733
```

Let’s construct a method that can do the optimization and plot the results, `plot_optimal`

:

```
def find_optimal(func, arc_length, r_init, h_init, d_init):
"""
# args
ax - matplotlib axis
func - target function for optimization
arc_length - the arc length we are interested in finding
r_init - the initial guess for radius
h_init - the initial guess for height
d_init - the initial guess for loop horizontal spacing
# Return
A tuple containing the optimal radius, height and loop spacing (r, h, d)
for the given arc length
"""
= optimize.minimize(
result
func,
(r_init, h_init, d_init), =(arc_length,),
args='Nelder-Mead',
method=1e-8,
tol
)
return result.x
```

The code below demonstrates how to use the above function:

```
= create_standard_figure(
fig, ax 'Archimedean Spiral',
'x',
'y',
'z',
='3d',
projection=(8, 8),
figsize=(0.1, 0.1, 0.85, 0.85),
axes_rect
)
= 5 # m
arc_length = 2 # m
r_init = 3 # m
h_init = 0.5 # m
d_init
= find_optimal(
results
objective_fixed_length,
arc_length,
r_init,
h_init,
d_init,
)
*results)
plot_cone_and_spiral(ax,
fig.show()
```

```
Cone Radius (r) = 0.8185
Cone Height (h) = 3.5565
```

From the above output, we can see that attempting to optimize for all variables at once leads to a lot of sub-optimal solutions. It might be better to simply hold more variables constant and vary only one at a time.

Given that there are many solutions when we are able to vary the radius, height and loop width at the same time. What happens if we constrain the methods to only solve for radius and height? We’ll need to construct a new target function and a new optimal search routine.

```
# We have to construct the target function using this method to accommodate the
# optimize module It uses an array to specify the target variables that we want
# to optimize and separate arguments for the constants we don't want to
# optimize.
def target_function_rh(x, L, d):
"""
This method defines the objective function, the function we want to
minimize. This particular instance will search for r, h that generates a
particular arc length, L with loop spacing, d.
# args
x - 1-D array with shape (n,) of variables to solve for
- x[0] - r - cone base radius
- x[1] - h - cone height
L - The arc length we are attempting to find.
d - The horizontal loop spacing we are interested in.
# NOTE
From the Scipy.Optimization docs:
The objective function to be minimized.
fun(x, *args) -> float
where x is an 1-D array with shape (n,) and args is a tuple of the
fixed parameters needed to completely specify the function.
"""
# r, h, d = x
# return np.abs(L - spiral_arc_length_range(r, h, d))
return np.abs(L - spiral_arc_length_range(*x, d))
```

```
def find_optimal_rh(func, arc_length, d, r_init, h_init):
"""
# args
ax - matplotlib axis
func - target function for optimization
arc_length - the arc length we are interested in finding
d - the initial guess for loop horizontal spacing
r_init - the initial guess for radius
h_init - the initial guess for height
# Return
A tuple containing the optimal radius, height and loop spacing (r, h, d) for
the given arc length
"""
= optimize.minimize(
result
func,
(r_init, h_init), =(arc_length, d),
args='Nelder-Mead',
method=1e-8,
tol
)
return result.x
```

```
= create_standard_figure(
fig, ax 'Archimedean Spiral',
'x',
'y',
'z',
='3d',
projection=(8, 8),
figsize=(0.1, 0.1, 0.85, 0.85),
axes_rect
)
= 5 # m
arc_length = 0.25 # m
d
= 1 # m
r_init = 1 # m
h_init
= find_optimal_rh(target_function_rh, arc_length, d, r_init, h_init)
results
*results, d)
plot_cone_and_spiral(ax,
fig.show()
```

```
Cone Radius (r) = 0.6129
Cone Height (h) = 1.1321
```

By fixing the arc length and the d value, we can achieve better designs with less trial and error.

In this case, we will keep the radius fixed and the loop spacing fixed, and we’ll simple vary the height.

```
# We have to construct the target function using this method to accommodate the
# optimize module It uses an array to specify the target variables that we want
# to optimize and separate arguments for the constants we don't want to
# optimize.
def target_function_h(x, L, r, d):
"""
This method defines the objective function, the function we want to minimize.
This particular instance will search for h that generates a particular
arc length, L with loop spacing, d.
# args
x - 1-D array with shape (n,) of variables to solve for
- x[0] - h - cone height
L - The arc length we are attempting to find.
r - The radius of the base of the cone
d - The horizontal loop spacing we are interested in.
# NOTE
From the Scipy.Optimization docs:
The objective function to be minimized.
fun(x, *args) -> float
where x is an 1-D array with shape (n,) and args is a tuple of the
fixed parameters needed to completely specify the function.
"""
return np.abs(L - spiral_arc_length_range(r, *x, d))
```

```
def find_optimal_h(func, arc_length, d, r, h_init):
"""
# args
ax - matplotlib axis
func - target function for optimization
arc_length - the arc length we are interested in finding
d - the initial guess for loop horizontal spacing
r - the initial guess for radius
h_init - the initial guess for height
# Return
A tuple containing the optimal radius, height and loop spacing (r, h, d) for
the given arc length
"""
= optimize.minimize(
result
func,
(h_init, ), =(arc_length, r, d),
args='Nelder-Mead',
method=1e-8,
tol
)
return result.x
```

```
Cone Radius (r) = 0.5000
Cone Height (h) = 3.5841
```

Given an LED conical spiral of a given length, determine various electrical attributes about the setup. This could be multiple strips, including partials.

LED Details:

- How many LED strips?
- How many LEDs per strip? Per unit length?
- How many amps per LED?
- How many watts per LED?
- LED strip voltage?

Questions?

- Power requirements?
- Power injection?
- Power wire gauge?
- Draw (amps) from mains?

The full Jupyter notebooks can be found in this repository. This section can be found here.

\[ \begin{equation} \tag{14} V = I \cdot R \end{equation} \]

Where:

- \(V\) - Voltage (Volts)
- \(I\) - Amperage (Amps)
- \(R\) - Resistance (Ohms)

\[ \begin{equation} \tag{15} P = V \cdot I \end{equation} \]

\[ \begin{equation} \tag{16} P = I^2 \cdot R \end{equation} \]

\[ \begin{equation} \tag{17} P = V^2 \cdot R \end{equation} \]

Where:

- \(P\) - Power (Watts)
- \(V\) - Voltage (Volts)
- \(I\) - Amperage (Amps)
- \(R\) - Resistance (Ohms)

Using the strip from here as a reference. Strip details:

- APA102
- 5m
- 60 LED/m
- 5V DC
- 18 Watt/m

```
import numpy as np
from scipy import optimize
```

```
= 5 # m
length = 60 # LED/m
LED_count = 18 # watts per m
watt_per_unit_length = 5 # volts
strip_voltage
= LED_count*length
LED_total_count
= watt_per_unit_length*length
total_power
= total_power/strip_voltage
total_current
= 120 # V
mains_voltage = total_power/mains_voltage
total_current_mains
print('Strip Details:')
print(f'LED Strip Length = {length} m')
print(f'Voltage = {strip_voltage} V')
print(f'LED Count = {LED_count} per m')
print(f'LED Count (tot) = {LED_total_count}')
print()
print('Strip Power Requirements:')
print(f'Watts per m = {watt_per_unit_length} W/m')
print(f'Total Watts = {total_power} W')
print(f'Watts per LED = {total_power/LED_total_count} W')
print()
print(f'Strip Amps @ {strip_voltage} V:')
print(f'Max Amps = {total_current} A')
print(f'Max Amps per LED = {total_current/LED_total_count} A ({1000*total_current/LED_total_count} mA)')
print()
print(f'Mains Power Requirements {total_power} W @ {mains_voltage} V:')
print(f'Max Amps = {total_current_mains} A')
```

```
Strip Details:
LED Strip Length = 5 m
Voltage = 5 V
LED Count = 60 per m
LED Count (tot) = 300
Strip Power Requirements:
Watts per m = 18 W/m
Total Watts = 90 W
Watts per LED = 0.3 W
Strip Amps @ 5 V:
Max Amps = 18.0 A
Max Amps per LED = 0.06 A (60.0 mA)
Mains Power Requirements 90 W @ 120 V:
Max Amps = 0.75 A
```

The power requirement for this strip is 90 Watts. This means we have to provide a power supply that can produce 90 Watts at 5 V. You would probably want to double that wattage and look for something in the 150 W to 180 W range. Essentially, the power supply should produce 30 A to 40 A at 5 V. That would be 1.25 A to 1.5 A at 120 V.

Electrical resistivity (also called specific electrical resistance or volume resistivity) is a fundamental property of a material that measures how strongly it resists electric current. A low resistivity indicates a material that readily allows electric current. Resistivity is commonly represented by the Greek letter ρ (rho). The SI unit of electrical resistivity is the ohm-meter (Ω⋅m).For example, if a 1 m3 solid cube of material has sheet contacts on two opposite faces, and the resistance between these contacts is 1 Ω, then the resistivity of the material is 1 Ω⋅m.

Resistivity, \(\rho\), is in units of ohm meters, \(\left( \Omega \cdot m \right)\). The SI base units are:

\[\text{kg} \cdot m^3 \cdot s^{-3} \cdot A^{-2}\]

or

\[\frac{\text{kg} \cdot m^3}{s^3 \cdot A^2} \]

From Wikipedia, this table shows the resistivity (ρ), conductivity and temperature coefficient of various materials at 20 °C (68 °F; 293 K).

Material | Resistivity, ρ, at 20 °C (Ω·m) | Conductivity, σ, at 20 °C (S/m) | Temperature coefficient(c)(K−1) |
---|---|---|---|

Silver[d] | 1.59×10−8 | 6.30×107 | 3.80×10−3 |

Copper[e] | 1.68×10−8 | 5.96×107 | 4.04×10−3 |

Annealed copper[f] | 1.72×10−8 | 5.80×107 | 3.93×10−3 |

Gold[g] | 2.44×10−8 | 4.11×107 | 3.40×10−3 |

Aluminium[h] | 2.65×10−8 | 3.77×107 | 3.90×10−3 |

Calcium | 3.36×10−8 | 2.98×107 | 4.10×10−3 |

Tungsten | 5.60×10−8 | 1.79×107 | 4.50×10−3 |

Zinc | 5.90×10−8 | 1.69×107 | 3.70×10−3 |

Cobalt[i] | 6.24×10−8 | 1.60×107 | 7.00×10−3[30][unreliable source?] |

Nickel | 6.99×10−8 | 1.43×107 | 6.00×10−3 |

Ruthenium[i] | 7.10×10−8 | 1.41×107 | |

Lithium | 9.28×10−8 | 1.08×107 | 6.00×10−3 |

Iron | 9.70×10−8 | 1.03×107 | 5.00×10−3 |

Platinum | 10.6×10−8 | 9.43×106 | 3.92×10−3 |

Tin | 10.9×10−8 | 9.17×106 | 4.50×10−3 |

Gallium | 14.0×10−8 | 7.10×106 | 4.00×10−3 |

Niobium | 14.0×10−8 | 7.00×106 | |

Carbon steel (1010) | 14.3×10−8 | 6.99×106 | |

Lead | 22.0×10−8 | 4.55×106 | 3.90×10−3 |

Galinstan | 28.9×10−8 | 3.46×106 | |

Titanium | 42.0×10−8 | 2.38×106 | 3.80×10−3 |

Grain oriented electrical steel | 46.0×10−8 | 2.17×106 | |

Manganin | 48.2×10−8 | 2.07×106 | 0.002×10−3 |

Constantan | 49.0×10−8 | 2.04×106 | 0.008×10−3 |

Stainless steel[j] | 69.0×10−8 | 1.45×106 | 0.94×10−3 |

Mercury | 98.0×10−8 | 1.02×106 | 0.90×10−3 |

Manganese | 144×10−8 | 6.94×105 | |

Nichrome[k] | 110×10−8 | 6.70×105 [citation needed] | 0.40×10−3 |

The effective temperature coefficient varies with temperature and purity level of the material. The 20 °C value is only an approximation when used at other temperatures. For example, the coefficient becomes lower at higher temperatures for copper, and the value 0.00427 is commonly specified at 0 °C.

```
# Resistivity of selected elements (Ω·m)
# Source https://en.wikipedia.org/wiki/Electrical_resistivity_and_conductivity
# All values are in Ω·m:
= {
resistivity 'Copper': 1.72E-8,
'Aluminum': 2.65E-8,
'Carbon Steel': 14.3E-8,
'Electrical Steel': 46.0E-8,
'Gold': 2.44E-8,
'Nichrome': 110.0E-8,
'Nickel': 6.99E-8,
'Silver': 1.59E-8,
'Copper (elemental)': 1.68E-8,
}
```

To effectively perform the wire voltage drop calculations, we need to understand the resistance of the wire based on its material property and its cross-sectional area \(\left(\text{mm}^2\right)\). We’ll use resistivity (above), and we’ll use the American Wire Gauge standard for the cross-sectional area.

American Wire Gauge (AWG), also known as the Brown & Sharpe wire gauge, is a logarithmic stepped standardized wire gauge system used since 1857, predominantly in North America, for the diameters of round, solid, nonferrous, electrically conducting wire. Dimensions of the wires are given in ASTM standard B 258. The cross-sectional area of each gauge is an important factor for determining its current-carrying ampacity.

Increasing gauge numbers denote decreasing wire diameters, which is similar to many other non-metric gauging systems such as British Standard Wire Gauge (SWG), but unlike IEC 60228, the metric wire-size standard used in most parts of the world. This gauge system originated in the number of drawing operations used to produce a given gauge of wire. Very fine wire (for example, 30 gauge) required more passes through the drawing dies than 0 gauge wire did. Manufacturers of wire formerly had proprietary wire gauge systems; the development of standardized wire gauges rationalized selection of wire for a particular purpose.

The AWG tables are for a single, solid and round conductor. The AWG of a stranded wire is determined by the cross-sectional area of the equivalent solid conductor. Because there are also small gaps between the strands, a stranded wire will always have a slightly larger overall diameter than a solid wire with the same AWG.

AWG goes from n = -3 to 40, including `0000`

, `000`

, `00`

. Smaller values of `n`

are larger in diameter.

\[ \begin{equation} \tag{18} d_n = 0.127 \cdot 92^{\frac{36 - n}{39}} \end{equation} \]

Where:

- \(d_n\) - AWG wire diameter in milimeters \(\left( \text{mm} \right)\)
- \(n\) - AWG index number from -3 to 40.
- n = -1 for
`00`

, n=-2 for`000`

, n = -3 for`0000`

- For example,
- for n = 0, the gauge used
`AWG 0`

, as-is - for n = − 1, the gauge is either
`00`

or`2/0`

- for n = − 2, either
`000`

or`3/0`

- for n = − 3, either
`0000`

or`4/0`

- for n = 0, the gauge used

- n = -1 for

```
def awg_diameter(n:int) -> float:
"""
Given an AWG gauge number, n, return the diameter in millimeters (mm).
"""
return 0.127*92**((36 - n)/39)
```

```
= 5
n = awg_diameter(n)
d print(f'AWG {n} = {d:.3f} mm')
```

`AWG 5 = 4.621 mm`

\[ \begin{equation} \tag{19} A_n = \frac{\pi}{4} \cdot d_n^2 \end{equation} \]

```
def awg_area(n:int) -> float:
"""
Given an AWG gauge number, n, return the cross-sectional
area in mm^2.
"""
= awg_diameter(n)
d
return (np.pi/4)*d**2
```

```
= 5
n = awg_area(n)
a print(f'AWG {n} = {a:.3f} mm^2')
```

`AWG 5 = 16.773 mm^2`

We use the resistivity of the material and divide it by the cross-sectional area:

\[ \begin{equation} \tag{20} R_n = \frac{\rho}{A_n} \end{equation} \]

Where:

- \(R_n\) - Wire resistance per unit length \(\left( \frac{\Omega}{m} \right)\)

```
def wire_resistance(n:int, resistivity:float) -> float:
"""
Given the AWG gauge index and the conductor resistivity,
determine the resistance per unit length (Ω/m).
# args
n - The index number representing the AWG wire gauge.
resistivity - The resistivity of the conductor in Ω/m.
"""
# determine the cross-sectional area (square millimeters (mm^2)) of the
# AWG wire gauge, n
= awg_area(n)*1E-6 # convert the area to m^2
a
return resistivity/a
```

```
= wire_resistance(5, resistivity["Copper"])
q print(f'Resistance per unit length = {q:.3e} (Ω/m)')
print(f'Resistance per unit length = {q*1E3:.3f} (mΩ/m)')
```

```
Resistance per unit length = 1.025e-03 (Ω/m)
Resistance per unit length = 1.025 (mΩ/m)
```

The voltage drop is, $ V = I R$, where:

- \(V\) - Voltage drop
- \(I\) - Current in amps
- \(R\) - Resistance in Ohms \(\left ( \Omega \right)\)

The resistance of the power wires is calculated by the length of the wire and its resistivity.

NOTE: For power lines, we normally double the length, one length for the hot wire, the other for the ground wire. The following calculation will make that assumption.

```
= 5
n = 10 # one-way length in meters
l = 5 # volts
target_v = 1 # amp
current
= wire_resistance(n, resistivity["Copper"])
r_wire
= current*(2*l*r_wire)
v_drop
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/target_v:.7%}')
print(f'Wire Resistance one-way = {r_wire:.7f}Ω')
print(f'Wire Resistance two-way = {2*r_wire:.7f}Ω')
```

```
Voltage Drop = 0.0205089V
% Voltage Drop = 0.4101777%
Wire Resistance one-way = 0.0010254Ω
Wire Resistance two-way = 0.0020509Ω
```

For a length of power line that is 10 meters (20 meters of total length hot + ground), At 5 volts and 1 amp, the voltage drop is 0.021 volts. That is a 0.41% drop in voltage across that total length. This result matches the online calculator here.

```
def voltage_drop(n, current, wire_length, resistivity):
"""
# args
n - The index number representing the AWG wire gauge.
current - The current (Amps) we will run through the wire
wire_length - Total length of wire run involved. For power lines, it will be
typically twice the distance because we have to run two lines for ground
and hot (m)
resistivity - The resistivity of the conductor in Ω/m.
# Return
The voltage drop (Volts) across the wire
"""
return current*wire_length*wire_resistance(n, resistivity)
```

```
= 5
n = 10 # one-way length in meters
l = 5 # volts
target_v = 1 # amp
current
= voltage_drop(n, current, 2*l, resistivity["Copper"])
v_drop
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/target_v:.7%}')
print(f'Wire Resistance one-way = {r_wire:.7f}Ω')
print(f'Wire Resistance two-way = {2*r_wire:.7f}Ω')
```

```
Voltage Drop = 0.0205089V
% Voltage Drop = 0.4101777%
Wire Resistance one-way = 0.0010254Ω
Wire Resistance two-way = 0.0020509Ω
```

Given the target voltage, find an AWG diameter that produces a voltage drop that is less than or equal to a pre-defined percent voltage drop. We need to create an objective function for the optimization process.

```
def objective_function(
x, float,
voltage:float,
current:float,
length:float,
resistivity:float,
voltage_drop_ratio:
):"""
This method defines the objective function, the function we want to
minimize. This particular instance will search for r, h and d that
generates a particular arc length, L.
# args
x - 1-D array with shape (n,) of variables to solve for
- x[0] - n - AWG wire gauge index
- x[1] -
- x[2] -
voltage - The voltage (Volts) we want to maintain i.e. 5 volts
current - The current (Amps) we will run through the wire
length - Total length of wire run involved. For power lines, it will be
typically twice the distance because we have to run two lines for
ground and hot
resistivity - The resistivity (Ω·m) of the conductor material in the wire
voltage_drop_ratio - Ratio of voltage_drop/target_voltage that is acceptable
for the length, 0.1 (10%) for example
# NOTE
From the Scipy.Optimization docs:
The objective function to be minimized.
fun(x, *args) -> float
where x is an 1-D array with shape (n,) and args is a tuple of the
fixed parameters needed to completely specify the function.
"""
= voltage_drop(x, current, length, resistivity)
v_drop
return np.abs(voltage_drop_ratio - v_drop/voltage)
```

```
# One-way length = 10 m, we'll use 20m
# Our target voltage is 5 V
# Current = 1 amp
# We'll use copper wires
# percentage drop is 10% (0.1)
= 20
l = 5
target_v = 1
current = 0.1
drop_ratio
# This yields the same answer as the minimize routine
= optimize.minimize_scalar(
result
objective_function, 0, ),
(=(target_v, current, l, resistivity['Copper'], drop_ratio),
args=(-3, 40),
bounds='bounded',
method
)
# print(result)
# print('-------')
print('Optimal Values:')
print(f'n = {result.x:.4f}')
# print(f'h = {result.x[1]:.4f}')
# print(f'd = {result.x[2]:.4f}')
print('---------')
= int(result.x)
n
= voltage_drop(n, current, l, resistivity["Copper"])
v_drop
print(f'n = {n}')
print(f'Voltage = {target_v}')
print(f'Current = {current}')
print(f'Length = {l}')
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/target_v:.7%}')
print(f'Wire Resistance one-way = {r_wire:.7f}Ω')
print(f'Wire Resistance two-way = {2*r_wire:.7f}Ω')
```

```
Optimal Values:
n = 18.7729
---------
n = 18
Voltage = 5
Current = 1
Length = 20
Voltage Drop = 0.4179592V
% Voltage Drop = 8.3591841%
Wire Resistance one-way = 0.0010254Ω
Wire Resistance two-way = 0.0020509Ω
```

NOTE: The optimize method returns a float. We can cast that to an integer and get good results. I don’t think we need to use SciPy for this. A simple brute force algorithm should suffice.

```
# Brute force search
= 20
l = 5
target_v = 1
current = 0.1
drop_ratio
= None
best_n
for n in range(-3, 40):
= voltage_drop(n, current, l, resistivity["Copper"])
v_drop
if v_drop/target_v <= drop_ratio:
# print(f'n={n:>3}; drop={v_drop:>8.4f}V; % drop = {v_drop/target_v:>10.4%}')
= n
best_n else:
break
print('----')
print(f'n = {best_n}')
print(f'Voltage = {target_v}')
print(f'Current = {current}')
print(f'Length = {l}')
= voltage_drop(best_n, current, l, resistivity["Copper"])
v_drop
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/target_v:.7%}')
print(f'Wire Resistance one-way = {r_wire:.7f}Ω')
print(f'Wire Resistance two-way = {2*r_wire:.7f}Ω')
```

```
----
n = 18
Voltage = 5
Current = 1
Length = 20
Voltage Drop = 0.4179592V
% Voltage Drop = 8.3591841%
Wire Resistance one-way = 0.0010254Ω
Wire Resistance two-way = 0.0020509Ω
```

```
def search_awg_voltage_drop(
float,
voltage:float,
current:float,
length:float,
resistivity:float,
voltage_drop_ratio:
):"""
Search all AWG wire gauges for the gauge that provides the closest match to
the voltage drop ratio.
# args
voltage - The voltage (Volts) we want to maintain i.e. 5 volts
current - The current (Amps) we will run through the wire
length - Total length of wire run involved. For power lines, it will be
typically twice the distance because we have to run two lines for
ground and hot
resistivity - The resistivity (Ω·m) of the conductor material in the wire
voltage_drop_ratio - Ratio of voltage_drop/target_voltage that is acceptable
for the length, 0.1 (10%) for example
# Return
The AWG wire gauge index, n,that is just below the voltage drop ratio
specified.
"""
= None
best_n
for n in range(-3, 40):
= voltage_drop(n, current, length, resistivity)
drop
if drop/voltage <= voltage_drop_ratio:
= n
best_n else:
break
return best_n
```

It is probably more pragmatic to simply search the space till we find the proper value.

First Data set:

```
= 20
length = 5
voltage = 1
current = 0.1
drop_ratio
= search_awg_voltage_drop(
best_n
voltage,
current,
length, "Copper"],
resistivity[
drop_ratio,
)
print(f'n = {best_n}')
print(f'Voltage = {target_v}')
print(f'Current = {current}')
print(f'Length = {l}')
= voltage_drop(best_n, current, length, resistivity["Copper"])
v_drop
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/voltage:.7%}')
```

```
n = 18
Voltage = 5
Current = 1
Length = 20
Voltage Drop = 0.4179592V
% Voltage Drop = 8.3591841%
```

We want to find a suitable wire gauge for 5 volts and 1 amp of current for a 20 meter total run. It looks like AWG 18 is less is suitable for the task. It has a voltage drop of about 8% over that length.

Second Data set:

```
= 20 # x2 for the ground and power wires
length = 12
voltage = 1
current = 0.1
drop_ratio
= search_awg_voltage_drop(
best_n
voltage,
current,
length, "Copper"],
resistivity[
drop_ratio,
)
print(f'n = {best_n}')
print(f'Voltage = {target_v}')
print(f'Current = {current}')
print(f'Length = {length}')
= voltage_drop(best_n, current, length, resistivity["Copper"])
v_drop
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/voltage:.7%}')
```

```
n = 22
Voltage = 5
Current = 1
Length = 20
Voltage Drop = 1.0567254V
% Voltage Drop = 8.8060454%
```

If we use the same set of data, expect use 12 volts instead of 5 volts the AWG is 22, a smaller diameter than with 5 volts. If we look at the same AWG as in the 5 volt case, we get:

```
= voltage_drop(18, current, length, resistivity["Copper"])
v_drop
print(f'Voltage Drop = {v_drop:.7f}V')
print(f'% Voltage Drop = {v_drop/voltage:.7%}')
```

```
Voltage Drop = 0.4179592V
% Voltage Drop = 3.4829934%
```

The voltage drop for 12 volts using the same AWG as the 5 volt case is less then 4%. The voltage drop is less than half when using 12 volts versus 5 volts.

This is quite a long article with a lot of math. The important sections is the first section defining the spiral model. That will help design the cone and get a nice spiral pattern for your LED strips. The electrical part will be expanded somewhat in future articles and cover the power injection points along with the proper gauge of wire to use.

- https://en.wikipedia.org/wiki/Cone
- https://en.wikipedia.org/wiki/Conical_spiral
- https://en.wikipedia.org/wiki/Archimedean_spiral
- http://www.mathematische-basteleien.de/spiral.htm
- http://jwilson.coe.uga.edu/EMT668/EMT668.Folders.F97/Anderson/writeup11/writeup11.html
- http://www.intmath.com/blog/mathematics/length-of-an-archimedean-spiral-6595
- http://www.intmath.com/applications-integration/12-arc-length-curve-parametric-polar.php
- http://tutorial.math.lamar.edu/Classes/CalcIII/VectorArcLength.aspx
- https://www.waveformlighting.com/home-residential/how-to-choose-a-power-supply-for-your-led-strip-project
- https://www.rapidtables.com/calc/wire/voltage-drop-calculator.html
- https://www.rapidtables.com/calc/wire/wire-gauge-chart.html
- https://www.reddit.com/r/WLED/comments/q7oxz4/so_glad_to_never_have_to_put_up_holiday_lights/
- https://www.reddit.com/r/WLED/comments/pp9g0e/power_supply_injection_for_ws2812b_700_leds/
- https://www.reddit.com/r/WLED/comments/kplt41/double_check_my_numbers_for_me_please/
- https://www.reddit.com/r/WLED/comments/qmp7fy/power_injection_one_run_with_multiple_injects_or/
- https://www.reddit.com/r/WLED/comments/qx9efb/voltage_drop_of_power_injection_wire/
- https://quinled.info/2020/09/26/power-injection-livestream/