As mentioned in the last post, I would like to have a clear line of separation between the command line spirograph app and the web spirograph app. That means I need to look at copying any variables and functions I am using from the command line app to the web app.

I decided to see what this might involve by searching through the web app modules looking for references to the command line app modules. Here’s a lengthy list of what I found.

Searching for: splt\.
sp_app_animate.py(37): rs = splt.f(0)
sp_app_animate.py(85): t_lim = sum(splt.r_rad) + 0.15
sp_app_animate.py(121): r_pts = splt.f(t[t_ndx])
sp_app_animate.py(128): circs = [Circle((0, 0), .1, fill=False, alpha=0) for i in range(splt.nbr_w)]
sp_app_animate.py(130): r_pts = splt.f(i)
sp_app_animate.py(136): for i in range(1, splt.nbr_w):
sp_app_data.py(429): splt.set_spiro(g.freqs, g.wds, g.hts, nbr_t=g.t_pts)
sp_app_data.py(435): t_xs, t_ys = splt.mk_curve(g.rds, shp=g.su)
sp_app_data.py(436): shape = f"{splt.shp_nm[g.su].lower()} ({g.su})"
sp_app_data.py(445): t_xs, t_ys, shape = splt.mk_rnd_curve(g.rds, su=shape)
sp_app_data.py(462): if l_shps[i] not in splt.shps:
sp_app_data.py(468): all_shp = list(splt.shps.keys())
sp_app_data.py(549): g.su = sau.rng.choice(list(splt.shp_nm.keys()))
sp_app_data.py(552): elif f_shp in splt.shp_nm:
sp_app_data.py(557): if f_shp == 'x' or f_shp in splt.shp_nm:
sp_app_data.py(560): g.su = sau.rng.choice(list(splt.shp_nm.keys()))
sp_app_data.py(565): # all_shp = list(splt.shps.keys())
sp_app_data.py(851): splt.set_spiro(g.freqs, g.wds, g.hts, nbr_t=g.t_pts)
sp_app_util.py(79): shape = f"{splt.shp_nm[g.su].lower()} ({g.su})"
sp_app_util.py(86): 'npts': g.t_pts, 'e_wds': splt.e_wds, 'e_hts': splt.e_hts
Found 42 occurrence(s) in 4 file(s), 13 ms

Searching for: (get_radii|get_freqs)
sp_app_data.py(12): from spiro_get_rand import get_radii, get_freqs
sp_app_data.py(418): g.wds = get_radii(g.n_whl)
sp_app_data.py(420): g.hts = get_radii(g.n_whl)
sp_app_data.py(425): g.cgv, g.freqs = get_freqs(nbr_w=g.n_whl, kf=g.k_f, mcg=g.cgv)
sp_app_lib.py(14): from spiro_get_rand import get_radii, get_freqs
sp_app_lib.py(1068): g.wds = get_radii(g.n_whl)
sp_app_lib.py(1070): g.hts = get_radii(g.n_whl)
sp_app_lib.py(1075): g.cgv, g.freqs = get_freqs(nbr_w=g.n_whl, kf=g.k_f, mcg=g.cgv)
Found 8 occurrence(s) in 2 file(s), 9 ms

I do think this refactor is going to cover more than moving some variable and functions to the web app modules. I think I might look at taking the module variables used in spiro_plotlib (i.e. splt) to my global variables module. And refactor the functions I copy over to use those variables rather than a more or less duplicate set in multiple modules.

A Look at the Current Process

The command line modules of interest are spiro_plotlib alias splt and spiro_get_rand. In the latter, specifically the functions get_radii and get_freqs (see search results above).

Other aliases used below. sad -> sp_app_data. sap -> sp_app_plot.

Okay, for most routes it goes something like this:

  1. generate basic curve data, usually a call to sp_app_data.get_curve_data.
  2. call to sad.setup_image()
    • call to sad.proc_curve_form()
    • call to sad.init_curve(), where for the general case
      • a number of curve parameters are generated
      • call to splt.set_spiro(), that’s one of the command line app modules
      • if single wheel shape: call to splt.mk_curve()
        • this in turn calls the appropriate function for the given wheel shape, once for each wheel
        • there is a function for each wheel type
      • else if multiple wheel shapes: call to splt.mk_rnd_curve()
        • this in turn calls the appropriate function for the given wheel shapes, once for each wheel/shape
      • returns curve data
  3. call to sap.setup_image()
    • sets some global values
    • sets image margin with call to sap.set_margins()
  4. then there is a call to an appropriate plotting function depending on the image type/route. For the cycling line width using colour between that would be sap.cycle_lw_btw() - none of these functions appear to call/use anything in spiro_plotlib

There’s a lot of code in these two modules that I would need to copy/move to new modules. So I think I will continue to use the spiro_plotlib modules which I originally copied into the web app’s directory. I will rename it as sp_app_curve. I may have also modified it, so a good reason to keep it as is.

And, I will move the two functions in spiro_get_rand to sp_app_util; and refactor them to use the rng object.

I will try to refactor sp_app_curve to use the variables in the global variables module rather than the current module variables. Doesn’t make sense to me to have the same information in two places. Though keeping the module variables may make the code easier to write and make it clearer to read. Finally, I will remove any functions and variables not used by the web app that may be being used by the command line app.

New Module

I renamed a copy of spiro_plotlib to sp_app_curve. So I now need to find all references to the former (imports and prefixes for function calls) to use the new module. Removing the old imports and adding a new one, import sp_app_curve as sacu, as necessary. Replaced 15 occurrences in main, 6 in sp_app_animate, 12 in sp_app_data and 3 in sp_app_util. That’s many more than I expected. And, a quick test says things appear to work as before my refactoring.

Move Functions in spiro_get_rand

Okay, let’s get those two functions into sp_app_util. I had thought about putting them in sp_app_data as they generate data to be used in rendering spirograph curves. But, in the end went with the app utility module. Figured it would be okay either way. And this way, I have the rng object as a module variable. No module referencing required.

There were a series of variables moved as well.

# specify min/max multipliers for circles each position, starting at the 2nd (i.e. index 1)
nw_min = [.5, .5] + [.5] * 13
nw_max = [.8, .75] + [1] * 13
min_rad = 1/8

Now the functions currently use calls to the np.random object to generate the desired random values. I am refactoring all such calls to use the rng object equivalent. In the name of consistency. Will only show one of the refactorings, the one with the most calls.

The original version is as follows.

def get_freqs(nbr_w, kf, mcg=None):
  speeds = []
  # mod congruency value
  m_add = None
  if mcg:
    m_add = mcg
  else:
    # m_add = np.random.randint(1, kf - 2)
    m_add = np.random.randint(1, kf)
  speeds = [np.random.choice([m_add - kf, m_add, m_add + kf])]
  for _ in range(1, nbr_w):
    m_mult = np.random.randint(-4, 5)
    speeds.append(m_mult * kf + m_add)
  # I want to know m_add for plot titles
  return m_add, speeds

And the refactored version, simple changes, follows.

def get_freqs(nbr_w, kf, mcg=None):
  speeds = []
  # mod congruency value
  m_add = None
  if mcg:
    m_add = mcg
  else:
    # m_add = np.random.randint(1, kf - 2)
    # m_add = np.random.randint(1, kf)
    m_add = rng.integers(1, kf)
  # speeds = [np.random.choice([m_add - kf, m_add, m_add + kf])]
  speeds = [rng.choice([m_add - kf, m_add, m_add + kf])]
  for _ in range(1, nbr_w):
    # m_mult = np.random.randint(-4, 5)
    m_mult = rng.integers(-4, 5)
    speeds.append(m_mult * kf + m_add)
  # I want to know m_add for plot titles
  return m_add, speeds

set_spiro()

Now set_spiro() really only sets a number of spiro_plotlib module variables which are used by other functions to generate curve data.

def set_spiro(freqs, wds, hts, nbr_t=500, clrs=None, fig=None, ax=None):
  global nbr_w, nbr_pts, sp_frq, sp_rds, r_rad, sp_wds, sp_hts, r_wds, r_hts, cycle, t, ax_a, fig_a, x_sp, y_sp, ln_sp, ln_rs
  nbr_w, nbr_pts, sp_frq, sp_rds, sp_wds, sp_hts = len(wds), nbr_t, freqs, wds, wds, hts
  r_rad = [max(np.real(rd), np.imag(rd)) for rd in sp_rds]
  r_wds = r_rad
  r_hts = [max(np.real(rd), np.imag(rd)) for rd in sp_hts]
  t = np.linspace(0, 2*np.pi, nbr_t)
  # print(f"\tset_spiro(nbr_t={nbr_t}) -> nbr_pts: {nbr_pts}")
  if clrs:
    cycle = clrs
  if fig:
    fig_a = fig
  if ax:
    ax_a = ax
  x_sp = []
  y_sp = []
  ln_sp = None
  ln_rs = None

What I want to do is use the variables that already exist in the global variables module. Add any that are missing. Then copy set_spiro() to sp_add_data() and refactor to use the variables in the global variables module.

I am sure you can already see this is going to a slow and painful refactoring given how many spiro_plotlib functions use those module variables. I may not even be able to do it truly incrementally.

Done

After writing everything but this closing section, I started on some other bits of refactoring and/or enhancement. And sort of forgot all about what I was planning to do with respect to refactoring the web app modules.

So, for the past and present this post is done. I have left all those globals used in set_spiro in the sp_app_curve module for now.

May eventually get back to this particular refactoring idea/attempt.