Gas station without pumps

2017 November 28

More SolidWorks

Filed under: Robotics — gasstationwithoutpumps @ 22:24
Tags: , , , , , ,

I spent most of my day today with SolidWorks, fixing the problems noted in Bugs found in first assembly of robot and adding new layers to the robot. I’m getting a bit better at using SolidWorks, but I still find it to be an overly complicated interface with way too many modes.  I’m sure that there is a way to get it to start up with reasonable document parameters (like using mm instead of inches, or using the same settings as already open documents), but I’ve not taken the time to try to track that down.

Here is the model as it stands so far:

View from the front left of the robot. The octagon floating on top is a the beacon-detector board, which will be on standoffs that I didn’t bother to include in the model.

I cut out the three layers of the robot today, making two mistakes in the process. One mistake I caught right away, and just recut the layer after fixing the problem—there was an extra alignment circle that was not supposed to be cut that I had forgotten to erase. The other error was just as serious, but I didn’t notice it until I got home—the top layer did not have the slots cut in it for the spacers from layer 2 to layer 3. It is hard to notice this problem in looking at the SolidWorks model, as the 3D model looks the same whether the slots are cut or not. I should have noticed it when I created the dxf file for cutting the third layer, but by then I was getting pretty tired and careless. I’ll have to cut another copy on Thursday.  Luckily MDF is cheap—each layer costs me about $1, and I still have half a dozen 1-foot squares of MDF left.

Incidentally, I came up with what I think will be a cheap fix for the potential problem of the bumper springs not being stiff enough.  I added another switch front and center, just for the spring in the switch to push the bumper forward.  At 60¢ a switch, this is not a particularly expensive way to add a spring, and it saved me a lot of modeling and building time.  I could even wire up the switch if I can think of a use for it.

One other thing I made today was a “drill” test, to see what size holes were really made by the laser cutter from specifications.  I created the guide in SVG using a short Python program (so that I could tweak things easily. It took me quite a while to get the SVG just right, because of weird limitations of SVG, like that the path commands can’t take units for the coordinates. Also because I was using Inkscape to translate the SVG to the DXF format that the RDWorks laser-cutter software needs, and Inkscape assumes that the “pixels” are 90/inch for that conversion.  It is kind of messed up that SVG works in terms of “pixels”, since it is supposed to be Scalable Vector Graphics.  Inkscape only converts paths to DXF (not other shapes, like circles and text), so I wrote the program to generate paths and used Inkscape’s object-to-path conversion to convert the text.

Here is the piece I cut:

The circles were cut at 14mm/s and 100% (actually clipped at 67%) and the numbers were written at 140mm/s and 20%.

The holes were exactly the right size (to the 0.1mm limitations of my calipers), and the circular pieces that were cut out were 0.35–0.4mm smaller in diameter. That is, the kerf is about 0.19±0.02mm and it is on the inside of arcs.

Here is the code I used for generating the SVG file:

#!/usr/bin/env python

from __future__ import division, print_function

# all sizes are given in units of 0.1 mm

# Inkscape coverts pixels to real-world units in DXF at 90 pixels/in

pix_per_inch = 90
mm_per_inch = 25.4
pix_per_mm = pix_per_inch / mm_per_inch
pix_per_unit = 0.1*pix_per_mm

print('<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
print('<svg width="150mm" height="150mm" xmlns="http://www.w3.org/2000/svg">');

y=100   # y-position of first row of circles

x_space = 60    # spacing between circles
stroke = 1       # stroke-width

xmax = None     # largest value for x

text_space =35  # space from circle to label

for diams in [range(5,50,5), range(50,80,5), range(80,105,5), range(105,130,5)]:
    x=100 # left edge of first circle
    for diam in diams:
        x += diam/2
        print ('<path stroke="red" fill="none" stroke-width="1" d="M {sx},{sy} \
a {r},{r} 0,0,0 {r},{r} \
a {r},{r} 0,0,0 {r},-{r} \
a {r},{r} 0,0,0 -{r},-{r} \
a {r},{r} 0,0,0 -{r},{r} z" />'.format(sx=(x-diam/2)*pix_per_unit, sy=y*pix_per_unit,
                r=diam/2*pix_per_unit))

        print('<text x="{}" y="{}" stroke="blue" fill="blue" text-anchor="middle" font-family="Verdana" font-size="10">'.format(
                x*pix_per_unit,(y+diam/2+text_space)*pix_per_unit))
        print(diam/10)
        print('</text>')
        x += diam/2 + x_space
        last_diam=diam
        if xmax is None or x>xmax:
                xmax = x
    y+=2*last_diam+text_space

print('<path stroke="red" fill="none" stroke-width="1" d="M 0,0 h {} v {} h {} z" />'.format(
        xmax*pix_per_unit,(y-last_diam)*pix_per_unit,
        -xmax*pix_per_unit))

print ('</svg>')
Advertisements

2017 November 10

Single-arm design probably won’t work

Filed under: Robotics — gasstationwithoutpumps @ 11:57
Tags: , ,

In yesterday’s post Preliminary design review in mechatronics, I contemplated using a single arm to put balls in both targets, with the arm vertical for the higher Ren-ship target and past vertical for the AT-M6 target.  I tried doing some calculations with a hand calculator, and it looked like this might work.  I then drew out the geometry, labeling all the dimensions, and wrote a program to check the constraints carefully.   The program did not accept the values I’d computed by hand, so I did some search in the program and had it report what constraints failed.

Here is a sketch of the geometry:

The pivot is X from the edge and Y from the floor. The arm is A long with a cup that is W wide and D deep. The target is S from the edge and between L and H high.
The top and bottom of the cup should fit inside the target range.

The program selected the arm length based on the Ren-ship target, then checked that the arm would meet all constraints for both targets.

Unfortunately, an arm long enough to reach the Ren-ship target is too long to hit the AT-M6 target, no matter where I put the pivot. The problem is that AT-M6 target gets very small if the ball is approaching it with the arm almost horizontal, and the inner edge and outer edge of the ball cup can’t both clear the edges of the target.

I simplified the code to check just one target, and I do have a range of pivot positions that would make the arm work for a each target.  For the Ren-ship target I’d need to have the pivot at least 4.7″ off the floor in order for the arm to be short enough to fit in the initial box, and 6.5″ off the floor if I want the rest position of the arm to be horizontal (assuming the pivot is 1″ from the edge).

For the AT-M6 target, I’d need the pivot to be at least 2″ off the floor and and no more than 5″ off the floor.  The shortest arm would be about 7″ long with the pivot 1″ from the edge and 4″ up, with a separation of 3.8″.  The longest arm would be 10.58″ with the pivot 1″ from the edge and 2″ up, with a separation of 6″.  The best arm length (giving the most flexibility in position of the pivot and separation) seems to be around 7.5″.  I’d have to play around a bit with the computations to take into account other constraints (like interaction with other parts of the robot).  I’ll have to build some track-wired detectors, to see if I can get good sensitivity at large separations (which allows sloppier tape following, since I can be further from going out of bounds).  If sensitivity of the track-wire detector is low, then I can either try to make the robot follow the tape more precisely and risk going out of bounds, or I can deploy the track-wire detector outside the original 11″ box.

Now that the robot is not going to use a single arm for both targets, the don’t have to face the same direction.  The high arm can be on top facing front, and the low arm can be on a lower layer, facing sideways (but there needs to be clearance for the low arm to swing through the higher layers).

Here is the program trying to find a single-arm solution:

#!/usr/bin/env  python
""" arm-swing.py
    Fri Nov 10 07:56:41 PST 2017 Kevin Karplus

        Computations to see whether a single arm can be used for both
        the AT-M6 and Ren-ship targets for the 2017 Mechatronics course.
        
        The arm is
                A inches long with a
                D inch deep cup
                W inches wide (not extending arm)
                R inches above the floor at rest.
        The arm pivots 
                X inches from the edge of the robot, 
                Y inches from the floor.
        The targets are
                S inches from the edge of the robot
                L inches from the floor at the bottom of the hole
                H inches from the floor at the top of the hole
       
        Known values:
                L=6, H=10 for AT-M6
                L=13, H=15 for Ren ship
                
        Computed values:
                B to tip of cup:  sqrt(A^2+D^2)
                C to bottom edge of cup: sqrt((A-W)^2+D^2)
        
        Constraints:
                sqrt(A^2 - (R-Y)^2) + X < 11" to fit in initial cube
                R + (R-Y)D/A  < 11"     height restriction

                sqrt(A^2 - (R-Y)^2) + X approx 9" to load balls
                
                sqrt((S+X)^2 + (L-Y)^2) <= C (bottom edge clears) sqrt((S+X)^2 + (H-Y)^2) >= B  (top edge clears)
                
                S for AT-M6:  2"<S<6"  (probably around 4")
                S for Ren ship:  0<=S<=0.3"
                
                # mounting space for servo
                min_mount_X <=X <= 11"-min_mount_X
                min_mount_Y <=Y <= 11"-min_mount_Y
                
                If 1 1/2" Schedule 40 PVC pipe used for cup, then W=1.9"
"""

from __future__ import division, print_function
from math import sqrt
from numpy import arange

min_mount_X = 0.5
min_mount_Y = 1

def ConstraintsMet(A=7, D=1, W=1.9, R=6, X=2, Y=6, S=0, L=13, H=15,
        reason=False):
    """Check all the constraints, returning True is constraints met,
        False if one fails.
        
        if reason, then print the first constraint that fails
    """
    
    if X<min_mount_X or 11-X<min_mount_X:   
        if reason: print("too close to edge")
        return False    # too close to edge for mounting servo
    if Y<min_mount_Y: if reason: print("pivot too low") return False # too low to mount servo if Y>11-min_mount_Y:
        if reason: print("pivot too high")
        return False    # too high to mount servo
    
    if S<0: if reason: print("overlap target") return False # edge intersects target rest_above_pivot = R-Y rest_width = sqrt(A**2 - rest_above_pivot**2) if rest_width+X >= 11:      
        if reason: print("too wide")
        return False    # too wide for initial box
    
    rest_tip_height = R + rest_above_pivot*D/A  # resting height of tip
    if rest_tip_height >= 11:
        if reason: print("too tall")
        return False    # too tall for initial box
    
    # Note: the ball-loading constraints can be modified by 
    # loading in a different position than the initial rest position
    if rest_width+X < 8 or rest_width+X>=9.5:
         if reason: print("loading at {:.2f}".format(rest_width+X))
         return False    # not enough space for ball loading
    
    # distance from pivot to tip of cup
    tip_to_pivot= sqrt(A**2 + D**2)
    pivot_to_top = sqrt((S+X)**2 + (H-Y)**2)
    if tip_to_pivot > pivot_to_top:
#        print("DEBUG: hits top Y={:.2f}, A={:.2f}, tip_to_pivot={:.2f}, pivot_to_top={:.2f}".format(Y,A,tip_to_pivot,pivot_to_top))
        if reason: print("doesn't clear top, tip_to_pivot={:.2f} pivot_to_top={:.2f}".format(tip_to_pivot,pivot_to_top))
        return False    # doesn't clear top of hole
    
    # distance from pivot to inner lip of cup
    inner_to_pivot= sqrt((A-W)**2 + D**2)
    pivot_to_bottom = sqrt((S+X)**2 + (L-Y)**2)
    if inner_to_pivot < pivot_to_bottom:
#        print("DEBUG: hits bottom Y={:.2f}, A={:.2f}, inner_to_pivot={:.2f}, pivot_to_bottom={:.2f}".format(Y,A,inner_to_pivot,pivot_to_bottom))
        if reason: print("doesn't clear bottom inner_to_pivot={:.2f}, pivot_to_bottom={:.2f}".format(inner_to_pivot,pivot_to_bottom))
        return False    # doesn't clear bottom of hole

    return True
        
# tops and bottoms of targets
H_Ren = 15
L_Ren = 13

H_ATM6 = 10
L_ATM6 = 6

# some fixed values
W = 1.9
D = 1
X = 1

# some values to explore
for Y in arange(2., 9.05, 0.1):
    for R in arange(Y, 11.05, 0.1):
        # set arm length to just clear top of hole when touching Ren ship
        pivot_to_top = sqrt(X**2+(H_Ren-Y)**2)
        A_long = sqrt(pivot_to_top**2 - D**2)
        
        # set arm length to just clear bottom of hole when touching Ren ship
        pivot_to_bottom = sqrt(X**2 + (L_Ren-Y)**2)        
        A_short = sqrt(pivot_to_bottom**2 -D**2) + W
        
        for A in [A_short, A_long]:
            if not ConstraintsMet(A=A, D=D, W=W, R=R, X=X, Y=Y,
                    S=0, L=L_Ren, H=H_Ren):
                continue
            print ("Y={:.2f} met Ren constraints".format(Y))
            for S in arange(2.,6.05, 0.2):
                if ConstraintsMet(A=A, D=D, W=W, R=R, X=X, Y=Y,
                      S=S, L=L_ATM6, H=H_ATM6, reason=True):
                    print ("X={:.2f}, Y={:.2f}, A={:.2f}, R={:.2f}, SATM6={:.2f}".format(X,Y,A,R,S))

2015 September 1

Pedagogy for bioinformatics teaching

Filed under: Circuits course — gasstationwithoutpumps @ 10:48
Tags: , , , , ,

I was complaining recently about the dearth of teaching blogs in my field(s), and serendipitously almost immediately afterwards, I read a post by lexnederbragt Active learning strategies for bioinformatics teaching:

The more I read about how active learning techniques improve student learning, the more I am inclined to try out such techniques in my own teaching and training.

I attended the third week of Titus Brown’s “NGS Analysis Workshop”. This third week entailed, as one of the participants put it, ‘the bleeding edge of bioinformatics analysis taught by Software Carpentry instructors’ and was a unique opportunity to both learn different analysis techniques, try out new instruction material, as well as experience different instructors and their way of teaching. …

I demonstrated some of my teaching and was asked by one of the students for references for the different active learning approaches I used. Rather then just emailing her, I decided to put these in this blog post.

It is good to see someone blogging about teaching bioinformatics—there aren’t many of us doing it, and most of us are more focused on research than on our pedagogical techniques.  For that matter, in my bioinformatics courses, I’ve only been making minor tweaks to my teaching techniques—increasing wait time after asking questions, randomizing cold calls better, being more aware of the buildup of clutter on the whiteboard, … .  Where I’ve been focusing my pedagogic attention is on my applied electronics course and (to a lesser extent) the freshman design seminar.

I’ll be starting my main bioinformatics course in just over 3 weeks, a first-quarter graduate course that is also taken by seniors doing a BS in bioinformatics.  This will be the 14th time I’ve taught the course (every year since 2001, except for one year when I took a full-year sabbatical).  Although the course has evolved somewhat over that time, it is difficult for me to make major changes to something I’ve taught so often—I’ve already knocked off most of the rough edges, so major changes will always seem inferior, even if they would end up being better after a year or two of tweaking.  I think that major changes in the course would require a change of instructor—something that will have to be planned for, as I’ll be retiring in a few years.

My main goals in this core bioinformatics course are to teach some stochastic modeling (particularly the importance of good null models), dynamic programming (via Smith-Waterman alignment), hidden Markov models, and some Python programming.  The course is pretty intense (the Python programming assignments take up a lot of time), but I think it sets the students up well for the subsequent course in computational genomics (which I do not teach) and for general bioinformatics programming in their research labs. I don’t cover de Bruijn graphs or assembly in this course—those are covered in subsequent courses, though both the exercises Lex mentions seem useful for a course that covers genome assembly.

The live-coding approach that Lex mentions in his blog seems more appropriate for an undergrad course than for a grad course.  I do use that approach for teaching gnuplot in my applied electronics course, though I’ve had trouble getting students to bring their data sets and laptops to class to work on their own plots for the gnuplot classes—I’ll have to emphasize that expectation next spring.

It might be possible to use a live-coding approach near the beginning of the quarter in the bioinformatics course—on the first assignment when I’m trying to get students to learn the “yield” statement for make generators for input parsing. I’ve been thinking that a partial worked example would help students get started on the first program, so I could try live coding half the assignment, and having them finish it for their first homework.

One of the really nice things about Python is how easily one can create input handlers that spit out one item at a time and how cleanly one can interface them to one-pass algorithms. Way too many of the students have only done programming in a paradigm that reads all input, does all processing, and prints all output.  Although there are some bioinformatics programs that need to work that way, most bioinformatics tasks involve too much data for that paradigm, and programs need to process data on the fly, without storing it all.  Getting students to cleanly separate I/O from processing while processing only one item at time is the primary goal of the first two “warmup” Python programs in the course.

One thing I will have to demonstrate in doing the live coding is writing the docstring before writing any of the code for a routine.  Students (and professional programmers) have a tendency to code first and document later, which often turns into code-first-think-later, resulting in unreadable, undebuggable code. I should probably make a bigger point of document-first coding in the gnuplot instruction also, though the level of commenting needed in gnuplot is not huge (plot scripts tend to be fairly simple programs).

2015 June 17

PteroDAQ bug fix

Now that my son is home from college, I’m getting him to do some bug fixes to the PteroDAQ data acquisition system he wrote for my class to use. The first fix that we’ve put back into the repository was for a problem that was noticed on the very old slow Windows machines in the lab—at high sampling rates, the recording got messed up.  The recording would start fine, then get all scrambled, then get scrambled in a different way, and eventually return to recording correctly, after which the cycle would repeat.  Looking at the recorded data, it was as if bytes were getting lost and the packets coming from the KL25Z were being read in the wrong frame.  As more bytes got lost the frameshift changed until eventually the packets were back in sync.  There seemed to be 5 changes in behavior for each cycle until things got back in sync.

This happened at a very low sampling rate on the old Windows machines, but even on faster machines still happened at a high enough sampling rate.

What the program was designed to do was to drop entire packets when the host couldn’t keep up with the data rate and the buffer on the KL25Z filled up, but that didn’t seem to be what was happening.  The checksums on the packets were not failing, so the packets were being received correctly on the host, which meant that the problem had to be before the checksums were added.  That in turn suggested a buffer overflow for the queuing on the KL25Z board.  More careful examination of the recordings indicated that when we got back into sync, exactly 4096 packets of 10 bytes each had been lost, which suggested that the 5 changes in behavior we saw during the cycle corresponded to 5 losses of the 8192-byte buffer.

 

We suspected a race condition between pushing data onto the queue and popping it off, so modified the code to turn off interrupts during queue_pop and queue_avail calls (we also made all the queue variables “volatile”, to make sure that the compiler wouldn’t optimize them out reads or writes, though I don’t think it was doing so).  This protection for the queue pop and availability calls changed the behavior to what was expected—at low sampling rates everything works fine, and at high sampling rates things start out well until the queue fills up, then complete packets are dropped when they won’t fit on the queue, and the average sampling rate is constant independent of the requested sampling rate, at the rate that the packets are taken out of the queue.

On my old MacBook Pro, the highest sampling rate that can be continued indefinitely for a single channel is 615Hz (about 6150 bytes/sec transferred).  On the household’s newer iMac, the highest sampling rate was 1572Hz (15720 bytes/sec). (Update, 2015 Jun 18: on my son’s System76 laptop, the highest sampling rate was 1576Hz.)

One can record for short bursts at much higher sampling rates—but only for 819.2 /(f_{s} - \max f_{s}) for a single channel (8192 bytes at 10 bytes/packet is 819.2 packets in the queue).  At 700Hz, one should be able record for about 9.6376 seconds on my MacBook Pro (assuming a max sustained rate of 615 Hz).  Sure enough, the first missing packet is the 6748th one, at 9.6386 s.

I thought that 2 channels (12-byte packets) should be accepted on my MacBook Pro at (10bytes/12bytes)615Hz, or 512.5Hz, but the observed maximum rate is 533Hz, so it isn’t quite linear in the number of bytes in the packet.  Four channels (16-byte packets) run at 418Hz. There is some fixed overhead in addition to the per-byte cost on the host computer.

There is another, more fundamental limitation on the PteroDAQ sampling rate—how fast the code can read the analog-to-digital converter and push the bytes into the queue.  That seems to be 6928Hz, at which speed the longest burst we can get without dropping packets should be just under 130ms (it turned out to lose the 819th packet at 118.22ms, so I’m a little off in my estimate).  I determined the max sampling rate by asking for a faster one (10kHz) and seeing what the actual sampling rate was at the beginning of the run, then trying that sampling rate and again checking the achieved sampling rate. With two channels, the maximum sampling rate is only 3593Hz, suggesting that most of the speed limitation is in the conversion time for the analog-to-digital converter.

The current version of PteroDAQ uses long sample times (so that we can handle fairly high-impedance signal sources) and does hardware averaging of 32 readings (to reduce noise). By sacrificing quality (more noise), we could make the conversions much faster, but that is not a reasonable tradeoff currently, when we are mainly limited by how fast the Python program on the host reads and interprets the input stream from the USB port.  We’ll have to look into Python profiling, to see where the time is being spent and try to speed things up.

2013 March 21

Why Python first?

Filed under: home school,Uncategorized — gasstationwithoutpumps @ 11:21
Tags: , , , , , , ,

On one of the mailing lists I subscribe to, I advocated for teaching Python after Scratch to kids (as I’ve done on this blog: Computer languages for kids), and one parent wanted to know why, and whether they should have used Python rather than Java in the home-school course they were teaching.  Here is my off-the-cuff reply:

Python has many advantages over Java as a first text-based language, but it is hard for me to articulate precisely which differences are the important ones.

One big difference is that Python does not require any declaration of variables. Objects are strongly typed, but names can be attached to any type of object—there is no static typing of variables. Python follows the Smalltalk tradition of “duck typing” (“If it walks like a duck and quacks like a duck, then it is a duck”). That means that operations and functions can be performed on any object that supports the necessary calls—there is no need for a complex class inheritance hierarchy.

Java has a lot of machinery that is really only useful in very large projects (where it may be essential), and this machinery interferes with the initial learning of programming concepts.

Python provides machinery that is particularly useful in small, rapid prototyping projects, which is much closer to the sorts of programming that beginners should start with. Python is in several ways much cleaner than Java (no distinction between primitive types and objects, for example), but there is a price to pay—Python can’t do much compile time optimization or error checking, because the types of objects are not known until the statements are executed. There is no enforcement of information hiding, just programmer conventions, so partitioning a large project into independent modules written by different programmers is more difficult to achieve than in statically typed languages with specified interfaces like Java.

As an example of the support for rapid prototyping, I find the “yield” statement in Python, which permits the easy creation of generator functions, a particularly useful feature for separating input parsing from processing, without having to load everything into memory at once, as is usually taught in early Java courses. Callbacks in Java are far more complicated to program.

Here is a simple example of breaking a file into space-separated words and putting the words into a hash table that counts how often they appear, then prints a list of words sorted by decreasing counts:

def readword(file_object):
    '''This generator yields one word at a time from a file-like object, using the white-space separation defined by split() to define the words.
    '''
    for line in file_object:
        words=line.strip().split()
        for word in words:
             yield word

import sys
count = dict()
for word in readword(sys.stdin):
     count[word] = count.get(word,0) +1
word_list = sorted(count.keys(), key=lambda w:count[w], reverse=True)
for word in word_list:
    print( "{:5d} {}".format(count[word], word) )

Note: there is a slightly better way using Counter instead of dict, and there are slightly more efficient ways to do the sorting—this example was chosen for minimal explanation, not because it was the most Pythonic way to write the code. Note: I typed this directly into the e-mail without testing it, but I then cut-and-pasted it into a file—it seems to work correctly, though I might prefer it if if the sort function used count and then alphabetic ordering to break ties. That can be done with one change:

word_list = sorted(count.keys(), key=lambda w:(-count[w],w))

Doing the same task in Java is certainly possible, but requires more setup, and changing the sort key is probably more effort.

Caveat: my main programming languages are Python and C++ so my knowledge of Java is a bit limited.

Bottom-line: I recommend starting kids with Scratch, then moving to Python when Scratch gets too limiting, and moving to Java only once they need to transition to an environment that requires Java (university courses that assume it, large multi-programmer projects, job, … ). It might be better for a student to learn C before picking up Java, as the need for compile-time type checking is more obvious in C, which is very close to the machine. Most of the objects-first approach to teaching programming can be better taught in Python than in either C or Java. For that matter, it might be better to include a radically different language (like Scheme) before teaching Java.

The approach I used with my son was more haphazard, and he started with various Logo and Lego languages, added Scratch and C before Scheme and then Python.  He’s been programming for about 6 years now, and has only picked up Java this year, through the Art of Problem Solving Java course, which is the only Java-after-Python course I could find for him—most Java courses would have been far too slow-paced for him.  It was still a bit low-level for him, but he found ways to challenge himself by stretching the assigned problems into more complicated ones.  His recreational programming is mostly in Python, but he does some JavaScript for web pages, and he has done a little C++ for Arduino programming (mostly the interrupt routines for the Data Logger code he wrote for me).  I think that his next steps should be more CS theory (he’s just finished an Applied Discrete Math course, and the AoPS programming course covers the basics of data structures, so he’s ready for some serious algorithm analysis), computer architecture (he’s started learning about interrupts on the Arduino, but has not had assembly language yet), and parallel programming (he’s done a little multi-threaded programming with queues for communication for the Data Logger, but has not had much parallel processing theory—Python relies pretty heavily on the global interpreter lock to avoid a lot of race conditions).

Next Page »

Blog at WordPress.com.

%d bloggers like this: