3.029 Spring 2022
Lectures 09 & 10 - 02/28/2022, 03/02/2022

Polymer Microstructures and Statistics

We’ll take a small detour from our thermodynamics lectures, and present some material related to your synthesis class, 3.023

The idea here is to start from simple models, and add complexity as needed to explain various concepts you saw/will see in lecture

Polymers as Random Walks

We will treat polymers effectively as 1-D chains of fixed-length molecular units (embedded in either 2D or 3D)

Using these assumptions, the simplest model of a polymer microstructure is a sequence of uncorrelated (or random) bond angles

This can be modeled with a random walk!

(Aside) Random Walks

Lattice Walk

Imagine we have some random walkers (molecules, vacancies, drunken sailors..)

Which take steps on a square lattice using equal probabilities

First, let’s get a list of the possible steps (including staying still)

steps=Select[Tuples[Range[-1,1],2],Norm[#]<=1&]

At each step, we have an equal probability of choosing one of these steps

squareLatticeStep[]:=RandomChoice[steps]

For example 10 such steps:

Table[squareLatticeStep[],10]

Starting from the origin, we can accumulate these steps to see where our random walker ends up

Accumulate[Prepend[Table[squareLatticeStep[],10],{0,0}]]

Let’s visualize our random-walker’s path by drawing a line through our trajectory

Prepend[Table[squareLatticeStep[],1000],{0,0}]//Accumulate//Line//Graphics

We can make a better-looking visualization by using disks and opacity for our visited locations

With[{locations=Accumulate[Prepend[Table[squareLatticeStep[],1000],{0,0}]]},
Graphics[{Opacity[0.25],Disk[#,1/2]&/@locations}]]

Variable Angle Walk

Let’s relax our lattice-constraint, by allowing our walkers to take unit steps at any angle

randomUnitStep[]:=Module[{θ=RandomReal[{0,2 Pi}]},{Cos[θ],Sin[θ]}]

Prepend[Table[randomUnitStep[],1000],{0,0}]//Accumulate//Line//Graphics

Let’s visualize this slightly better, tracking the walkers’ progress in a periodic box

With[{steps=Mod[Accumulate[Prepend[Table[randomUnitStep[],10000],{0,0}]],50,-25]},

Animate[
Graphics[{Opacity[0.25],Disk[#,1/2]&/@Take[steps,time]},
PlotRange->25{{-1,1},{-1,1}}],{time,1,10000,1},
AnimationRunning->False,AnimationRepetitions->1,AnimationRate->100,Paneled->False]]

Angle Path

At this point, it’s convenient to use the built-in AnglePath to construct these

?AnglePath

RandomReal[{0,2π},1000]//AnglePath//Line//Graphics

Let’s see if we can vary the length of the step

With[{
randomAngles=RandomReal[{0,2π},1000],
randomLengths=RandomReal[{0,1},1000]
},
AnglePath[(({randomLengths,randomAngles})^\[Transpose])]//Line//Graphics
]

A fun application: L-Systems

L-systems or “Lindenmayer Systems” originated from botany and refers to recursive systems used to model variety of organisms using:

An alphabet

An axiom (or initial state)

Rules

Algae growth

Alphabet: “A” & “B”

Axiom: “A”

Rules: (“A” -> “AB”) & (“B”-> “A”)

We can quickly implement this algae-growth as a one-liner:

?StringReplace

StringReplace["ABCD",{"A"->"AB","B"->"A"}]

NestList[StringReplace[#,{"A"->"AB","B"->"A"}]&,"A",4]

Column[NestList[StringReplace[#,{"A"->"AB","B"->"A"}]&,"A",4],Center]

Most operations of interest can be defined using the following “standard” alphabet

F:	Draw line & move forward

G:	Move forward (without drawing a line)

+:	Turn right (by a specified angle)

-:	Turn left (by a specified angle)

This is commonly referred to as “Turtle graphics”.
Imagine a turtle sitting on your computer screen following a limited set of commands.

We can ab(use) AnglePath to quickly specify our turtle’s path!

E.g. for this mysterious rule-set

Short[seq=Last[SubstitutionSystem[{"X"->"X+YF+","Y"->"-FX-Y"},"FX",12]]]

StringCases[seq,
{"F"->{1,0},"X"->{0,0},"Y"->{0,0},"+"->{0,Pi/2},"-"->{0,-Pi/2}}]//Short

StringCases[seq,
{"F"->{1,0},"X"->{0,0},"Y"->{0,0},"+"->{0,Pi/2},"-"->{0,-Pi/2}}
]//AnglePath//Line//Graphics

Or this one

With[{seq=Last[SubstitutionSystem[{"F"->"G+F+G","G"->"F-G-F"},"F",{8}]]},
StringCases[seq,
{"F"->{1,0},"G"->{1,0},"+"->{0,Pi/3},"-"->{0,-Pi/3}}
]//AnglePath//Line//Graphics
]

Or this diffusion-limited-aggregation inspired one

With[{seq=Last[SubstitutionSystem[{"F"->"FF+F++F+F"},"F+F+F+F",{6}]]},
StringCases[seq,
{"F"->{1,0},"+"->{0,Pi/2}}
]//AnglePath//Line//Graphics
]

If you found these fun, there’s more examples here!

Back to Polymers

We can visualize a polymer with 32 monomers of fixed length and random bond-angled in 2D

Note: we will fix the bond-length to be equal to 2 (so that we can visualize monomers as disks/spheres of radius 1)

Block[{steps=AngleVector[{2,#}]&/@RandomReal[{0,2π},32],positions},
positions = Prepend[Accumulate[steps],{0,0}];
Graphics[]
]

This is easy to extend to 3D

We just need take random steps on the surface of a sphere with radius 2

Block[{steps=RandomPoint[Sphere[{0,0,0},2],32],positions},
positions = Prepend[Accumulate[steps],{0,0,0}];
Graphics3D[]
]

Let’s investigate the expected length of such an “uncorrelated” polymer, as a function of the number of molecular units.

We’ll use a Monte-Carlo method to estimate this

I.e., we’ll generate a set of random walkers for a fixed number of units, and then compute the mean displacement from the origin.
We will then repeat this multiple times for each number of molecular units.

We write a simple function to do the following:

compute N random steps (in 1D, 2D, or 3D)

sum up all of the steps (to get to the last point in the walk)

compute how far that point is from the origin

displacement[nUnits_, dimension_/;1<= dimension<= 3]:=
 Norm[
Total[ (*net displacement vector*)
Switch[dimension,
1,RandomChoice[{-1,1},nUnits],
2,RandomPoint[Circle[],nUnits],
3,RandomPoint[Sphere[],nUnits]
]
]
]

Note:  Coding comment - The above is a natural way to write this function.
However, often times it’s cleaner to use “Multiple-dispatch” to specialize a function based on an argument

displacementMultipleDispatch[1][nUnits_]:= Norm[Total[ RandomChoice[{-1,1},nUnits]]]
displacementMultipleDispatch[2][nUnits_]:= Norm[Total[ RandomPoint[Circle[],nUnits]]]
displacementMultipleDispatch[3][nUnits_]:= Norm[Total[ RandomPoint[Sphere[],nUnits]]]

Let’s generate some data for 1D, 2D, and 3D

Takes ~30s

(*
displacementData [1]= Table[{2^units,displacementMultipleDispatch[1][2^units]},{units,10,18},1000];
displacementData [2]= Table[{2^units,displacementMultipleDispatch[2][2^units]},{units,10,18},1000];
*)
displacementData [3]= Table[{2^units,displacementMultipleDispatch[3][2^units]},{units,10,18},1000];

With[{
meanDisplacements = Mean/@displacementData[3],
standardDeviationDisplacements = StandardDeviation/@displacementData[3]
},
Show[
ListPlot[displacementData[3],],ListLinePlot[MapThread[{#1,Around[#2,#3]}&,{First/@meanDisplacements,Last/@meanDisplacements,Last/@standardDeviationDisplacements}],]
]
]

Let’s see if we can discover the relationship between the expected length and the number of units

fitModel[3]=Block[{powerLawModel=a (n^power),meanDisplacements=Mean/@displacementData[3],fit},
fit=Echo[FindFit[meanDisplacements,powerLawModel,{a,power},n]];
powerLawModel/.fit
]

The fit shows that the length increases as

In our simulation, each If each bond had length = 1

If the bond length were a, then the results would have been

With[{meanDisplacements=Mean/@displacementData[3]},
Plot[fitModel[3],{n,1,(2^18)},Epilog->{Directive[Blue,PointSize[0.025]],Point[meanDisplacements]},]
]

Thus, we demonstrated that the expected length polymer with uncorrelated bond direction increases as the square-root of the number of molecular units.

Models of “Stiff” Polymers

Above we called the random-walk polymer as “uncorrelated”

This is because each molecular unit was allowed to take any bond angle with the next molecular unit

Some polymers may be less “floppy”: there will be a smaller range of “bending angle” at each molecular unit.

This would create direction correlations in the polymer chain.

We can model this by creating a correlation in the steps by selecting new directions within a given angle of the previous angle.

In two dimensions, this would be an arc of a circle centered on the previous direction.

In three dimensions, this would be points withing a given solid angle on a sphere.

2D Polymer Correlations

We want to choose the next direction from a uniform distribution of angles around the last direction  Δϕ < θ < Δϕ

In-fact, our friend AnglePath does exactly this

Manipulate[
With[{steps = AnglePath[{2,#}&/@RandomReal[{-range/2,range/2},12]]},
Graphics[

]
],
{range,0,2Pi}
,Paneled->False]

3D Polymer Correlations

For 3D, we need to uniformly sample points from a uniform spatial distribution over a spherical cap

We will show a couple of ways to do this

Random Point

We start using a generalization of how we sampled points on a sphere earlier, using RandomPoint

To do this, we require a region specification of a spherical cap

E.g. using an algebraic description and Implicit Region

We will assume the spherical cap’s radius = 1

With[{height=3/4},
ImplicitRegion[(x^2)+(y^2)+(z^2)==(1^2)&&z>= height,{x,y,z}]//Region]

This works, but the height specification is rather clunky

Instead, we’ll specify this using a solid-angle

Manipulate[
,
{{ϕ,π},0.1,4Pi},Paneled->False
]

The red circle above is  intersection of the cone and the sphere

The circle is located at a height z=Cos[ϕ/4] and has a radius r = Sin[ϕ/4]

Clear[implicitSphericalCap]
implicitSphericalCap[solidAngle_]:=implicitSphericalCap[solidAngle]=DiscretizeRegion[ImplicitRegion[(x^2)+(y^2)+(z^2)==(1^2)&&z>= Cos[solidAngle/4],{x,y,z}]]

implicitSphericalCap[π]

We can now use RandomPoint to sample N points on this surface

Manipulate[
With[{reg=implicitSphericalCap[ϕ]},
Graphics3D[Point[RandomPoint[reg,numberOfPoints]]]],{{ϕ,π},0.1,4π},{{numberOfPoints,1000},100,10000},Paneled->False]

Symbolic Probability Density

This is rather costly

RandomPoint[implicitSphericalCap[π],10000];//RepeatedTiming

Instead, we can construct a symbolic formula for sampling uniformly over a spherical cap

We start by noticing that the probability density of a random point on the sphere lying on the red circle intersection above, is proportional to the circumference

As such, the probability of finding an angle less than a solid angle α is proportional to

With[{r=Sin[ϕ/4]},
Integrate[r,{ϕ,0,α}]]

Which we can normalize by a maximum solid angle α to give:

probability[αMax_][α_]=Integrate[Sin[ϕ/4],{ϕ,0,α}]/(8 ((Sin[((αMax)/8)])^2))

We care to sweep the probability from a uniform 0 < p < 1, and calculate the angle α

So we need to invert this relationship

Solve[p==probability[αMax][α],α]/.C[_]:>0

randomPointSphericalCap[αMax_,n_]:=Block[{p=RandomReal[{0,1},n],ϕ,r,θ=RandomReal[{0,2Pi},n]},
ϕ =8 ArcSin[Sqrt[p]Sin[((αMax)/8)]];
r = Sin[ϕ/4];
(({r Cos[θ],r Sin[θ],Cos[ϕ/4]})^\[Transpose])
]

Manipulate[
Graphics3D[Point[randomPointSphericalCap[ϕ,numberOfPoints]]],{{ϕ,π},0.1,4π},{{numberOfPoints,1000},100,10000},Paneled->False]

This is indeed ~10x faster

randomPointSphericalCap[π,10000];//RepeatedTiming

AnglePath3D

It would of-course be convenient if we could use a function similar to AnglePath but in 3D

The function does exist, but it’s usage is slightly more complicated

?AnglePath3D

We will use the rotation matrices version of the function above

We therefore need the matrix which moves a point from {0,0,1} to the random point we computed above

MatrixForm[rotationMatrix[{r_,θ_,ϕ_}]=Simplify[Simplify[RotationMatrix[{{0,0,1},{r Cos[θ],r Sin[θ],Cos[ϕ/4]}}],{r>0,2π>θ>0,4π>ϕ>0}]/.{((Abs[a_])^2):>(a^2)},{r>0,2π>θ>0,4π>ϕ>0}]]

Let’s modify our function to do this quickly

randomPointSphericalCapRotationMatrix[αMax_,n_]:=Block[{p=RandomReal[{0,1},n],ϕ,r,θ=RandomReal[{0,2Pi},n]},
ϕ =8 ArcSin[Sqrt[p]Sin[((αMax)/8)]];
r = Sin[ϕ/4];
Transpose[rotationMatrix[{r,θ,ϕ}],{2,3,1}]
]

MatrixForm/@randomPointSphericalCapRotationMatrix[Pi/6,3]

And make sure it makes sense visually

Graphics3D[Sphere[#,1/2]&/@AnglePath3D[randomPointSphericalCapRotationMatrix[π,25]]]

From the AnglePath3D documentation
“the next position (Subscript[p, i])={(Subscript[x, i]),(Subscript[y, i]),(Subscript[z, i])} is obtained by (Subscript[p, i])=(Subscript[p, i-1])+(Subscript[f, i]).{(Subscript[r, i]),0,0}, with the frame rotation matrix (Subscript[f, i]) given by (Subscript[f, i])=(Subscript[f, i-1]).(Subscript[mat, i]) and (Subscript[f, 0])=(Subscript[mat, 0]).”

What AnglePath3D does is equivalent to a “Folding” a dot-product over the list of rotations:
  
and then accumulating the vectors in the list:

?AnglePath3D

The built-in method is somewhat slow again

rotations=randomPointSphericalCapRotationMatrix[π,10000];
{timingAP,lastPosAP} =RepeatedTiming[Last[pathAP =AnglePath3D[rotations]]]

Let’s see if we can reproduce this using FoldList

?FoldList

{timingFP1,lastPosFP1} =RepeatedTiming[Last@Accumulate[pathFP1=(#.{1,0,0})&/@FoldList[Dot,rotations]]]

You’ll notice two things:

i) This is indeed much faster, even for  10000 rotations

ii) The end points are the same, meaning the two methods are equivalent

Moreover, the Dot product above    picks out the first column of

Instead of the Dot product, we can grab the column:

{timingFP2,lastPosFP2} =RepeatedTiming[Last@Accumulate[pathFP2=(#[[All,1]])&/@FoldList[Dot,rotations]]]

Note: this isn’t faster since we’re do this for each rotation, but we can also grab all the columns at once

{timingFP3,lastPosFP3} =RepeatedTiming[Last@Accumulate[pathFP3=(FoldList[Dot,rotations][[All,All,1]])]]

let’s turn the above into a function:

randomCorrelatedWalk[units_, α_]:= 
With[{rotations =randomPointSphericalCapRotationMatrix[α,units]},
Accumulate[(FoldList[Dot,rotations][[All,All,1]])]
]

Manipulate[
With[{path=randomCorrelatedWalk[units, α]},
Graphics3D[]],
{{α,π},0.1,4 Pi},
{{units,100},5,1000,1},Paneled->False
]

Or, if we only care about the displacement of the last molecular unit:

randomCorrelatedDisplacement[units_, α_]:= 
With[{rotations = randomPointSphericalCapRotationMatrix[α,units]},
Norm[Total[(FoldList[Dot,rotations][[All,All,1]])]]
]

Monte Carlo Experiments

As before, we can do Monte Carlo experiments to find the expected length of a correlated polymer.

We will do repeated experiments for 24 solid angles from {((π,)/6)(π/3),((3π)/16),…,4π}.

As we will see below, the computation is time-consuming—which is why we spent some effort above making things faster.

To get a sense of how long each experiment will take:

timingData =Table[{2^units,First@AbsoluteTiming[randomCorrelatedDisplacement[2^units, Pi/2]]},{units,2,20}]

ListPlot[timingData[[All,1;;2]], Frame->True, FrameLabel-> {"Number of Units","Computation Time (seconds)"},ImageSize->500, BaseStyle->18]

ListLogLogPlot[timingData[[All,1;;2]], Frame->True, FrameLabel-> {"Number of Units","Computation Time (seconds)"},ImageSize->500, BaseStyle->18]

The total time we need to do 1 experiment for up to (2^20)=1048576 units is about 2 seconds:

Total[timingData[[All,2]]]

If we do all the angles in parallel for (2^(2 )) to (2^20) units (i.e., ParallelTable) it takes about 0.5 minute (4 CPUs)

AbsoluteTiming[result=
ParallelTable[
Table[α->{2^units,randomCorrelatedDisplacement[2^units, α]},{units,2,20}],
{α,Pi/6,4Pi,Pi/6}
];
]

Supposing we had enough patience to wait  1 hour, we could only do 100 repeated experiments.

This is likely insufficient for good statistics.

Another approach would be to do a single experiment for a large number of units, and then use the data from smaller subsequences of steps from that data.

This would produce correlations in the data, so it is not ideal.

So, if our patience limit is 10 minutes, we would do  500 Monte Carlo repetitions for up to (2^16) molecular units

AbsoluteTiming[
With[{monteCarloExperiments =500, nExponent=16},
correlatedRandomWalkData =
Join[<|"Monte Carlo Experiments"->monteCarloExperiments|>, Association[
ParallelTable[
(*Print[{α,DateString[]}];*)
<|α->
With[{walkExperiments =
Table[randomCorrelatedWalk[2^nExponent, α],monteCarloExperiments]
},
Association@Table[
2^n-><|
"Mean"->Mean[Norm/@walkExperiments[[All,2^n]]],"Standard Deviation"-> StandardDeviation[Norm/@walkExperiments[[All,2^n]]],
"Mean Direction"->Mean[walkExperiments[[All,2^n]]-walkExperiments[[All,2^n -1]] ]|>,
{n,1,nExponent}]
]|>,
{α,Pi/6,4Pi,Pi/6}
]
]
];
]
]

Or, if we don’ t want to wait, we can grab that data as follows:

(*cloudData =CloudSave[correlatedRandomWalkData,"correlatedRandomWalkData_n=500_exp=16",Permissions->"Public"]*)

correlatedRandomWalkData=CloudGet[CloudObject[(HyperlinkURL["https://www.wolframcloud.com/obj/gvarnavi/correlatedRandomWalkData_n=500_exp=16", https://www.wolframcloud.com/obj/gvarnavi/correlatedRandomWalkData_n=500_exp=16])]];

Let’s inspect the Keys

Keys[correlatedRandomWalkData]

correlatedRandomWalkData["Monte Carlo Experiments"]

correlatedRandomWalkData[Pi/6]

correlatedRandomWalkData[Pi/6][[All,"Mean"]]

With[{means = correlatedRandomWalkData[Pi/3][[All,"Mean"]],
deviations = correlatedRandomWalkData[Pi/3][[All,"Mean"]]},
ListLinePlot[
MapThread[Around[#1,#2]&,{means,(deviations/2)}],Frame->True,FrameLabel->{"Molecular Units","Mean Length/(Bond Length)"},ImageSize->500,BaseStyle->{FontSize->18,Thick},PlotLabel->"Expected Length for Biased Walk of π/3"]
]

Let’s visualize all of them together

correlatedRandomWalkPlotData[randomWalkData_,α_]:=
MapThread[Around[#1,#2/2]&,{randomWalkData[α][[All,"Mean"]],randomWalkData[α][[All,"Standard Deviation"]]}
]

correlatedRandomWalkPlotData[correlatedRandomWalkData,Pi/6]

DynamicModule[{allMeans=(correlatedRandomWalkData[#1][[All,"Mean"]]&)/@Range[(π/6),4 π,(π/6)],meansAndSDs=Association[(#1->correlatedRandomWalkPlotData[correlatedRandomWalkData,#1]&)/@Range[(π/6),4 π,(π/6)]],background},background=ListLinePlot[allMeans,PlotRange->All,Frame->True,BaseStyle->{FontSize->16},FrameLabel->{"Molecular Units","Mean Length"},AspectRatio->(2/8),ImageSize->500,PlotLabel->"Expected Length for Biased Walk",ImageMargins->0];Manipulate[ListLinePlot[meansAndSDs[α],PlotRange->All,PlotStyle->Black,AspectRatio->(1/2),Frame->True,FrameLabel->{"Molecular Units","Mean Length"},BaseStyle->{FontSize->18},ImageSize->500,PlotLabel->"Expected Length for Biased Walk"],{{α,(π/2),Style["Solid Angle Bias",14]},Range[(π/6),4 π,(π/6)]},Dynamic[Show[background,ListLinePlot[meansAndSDs[α],FrameLabel->{"Molecular Units","Mean Length/(Bond Length)"},PlotStyle->{Black,Thickness[0.005`]}]]],Paneled->False]
]

If the solid angle α = 0, then we would expect the length to grow linearly with the number of units because the polymer would be straight.

How does the fit we found above   change with α?

Let’s rearrange the data into a list:

With[{keys =Keys[correlatedRandomWalkData[Pi/6]], values =Values [correlatedRandomWalkData[Pi/6][[All,"Mean"]]]},
Transpose[{keys, values}]
]

Even for highly correlated bond angles, the expected length scales as :

With[{
keys =Keys[correlatedRandomWalkData[Pi/6]],
values =Values [correlatedRandomWalkData[Pi/6][[All,"Mean"]]],
model = a + b n^p},
FindFit[Transpose[{keys, values}],model, {a,b,p},n]
]

However, if we look at  limited range of data, we can see that the power law exponent shifts from 1 to 1/2.

That is, the polymer morphology has a "correlation length"

DynamicModule[{keys,values,fit,data,fitParameters,a,b,p,n,model},model=a+b (n^p);Manipulate[keys=Keys[correlatedRandomWalkData[α]];values=Values[correlatedRandomWalkData[α][[All,"Mean"]]];data=Transpose[{keys,values}][[1;;last]];fitParameters=FindFit[data,model,{a,b,p},n];fit=model/. fitParameters;Show[ListPlot[data,PlotRange->{{0,(2^last)},All},Frame->True,AxesOrigin->{0,0},ImageSize->500,BaseStyle->{FontSize->16},FrameLabel->{"Number of Molecular Units","Expected Length/(Bond Length)"}],Plot[fit,{n,1,(2^last)}]],{{last,3,Style["Number of Units",16]},(#1->(2^(#1))&)/@Range[16]},Dynamic[Style["Power Law Fit: "<>ToString[p/. fitParameters],16]],{{α,(π/6),Style["Correlation Angle",16]},Range[(π/6),4 π,(π/6)],ControlType->SetterBar},Paneled->False]]

In our association, we also have data for the last bond direction

correlatedRandomWalkData[Pi/6][[All,"Mean Direction"]]

Since the first step is always {1,0,0}, the first component of the mean direction is a measure of the correlation to the first bond.

For α = (π/6), the correlation disappears after about 1000 units.

ListLinePlot[First/@correlatedRandomWalkData[Pi/6][[All,"Mean Direction"]],
Frame->True,PlotRange->{{0,3000},{-0.1`,1}},ImageSize->500,BaseStyle->{FontSize->14},FrameLabel->{"Number of Molecular Units","Correlation Factor"}]

limitedData =With[{keys =Keys[correlatedRandomWalkData[Pi/6]], values =First/@Values [correlatedRandomWalkData[Pi/6][[All,"Mean Direction"]]]},
Transpose[{keys, values}][[1;;11]]
];
limitedDataPlot =ListPlot[limitedData,Frame->True]

We can use this limited data to fit a correlation length

I.e. The length at which correlations decay exponentially

correlationFit =With[{correlationModel = Exp[-n/η]},FindFit[limitedData,correlationModel,η,n]]

The characteristic correlation length is about 420 molecular units

With[{correlationModel = Exp[-n/η]},
Show[ListPlot[limitedData],Plot[correlationModel/.correlationFit,{n,0,2000}],ImageSize->Large,Frame->True, FrameLabel-> {"Molecular Units","Correlation Factor"},
PlotLabel->"Correlation Behavior for α = π/6", BaseStyle->{FontSize->16}]]

For α = 2π (i.e., the polymer never “bends backwards” )

The  correlation has effectively vanished after 3 about steps.

ListLinePlot[With[{keys=Keys[correlatedRandomWalkData[2 π]],values=First/@Values[correlatedRandomWalkData[2 π][[All,"Mean Direction"]]]},Transpose[{keys,values}][[1;;5]]],ImageSize->500,Frame->True,FrameLabel->{"Molecular Units","Correlation Factor"},PlotMarkers->Automatic,PlotLabel->"Correlation Behavior for α = 2π",BaseStyle->{FontSize->16},PlotRange->{{0,(2^5)},{-0.1`,1}}]

If we look at the behavior of a single polymer with α=π/3:

path= randomCorrelatedWalk[(2^12), Pi/3];
Graphics3D[Tube[path]]//Rasterize

We can see that the correlation is a random walk on a sphere where the step size is the solid angle α

Graphics3D[Tube[Differences@path]]//Rasterize