## Classroom photo sharing app

For a long time I’ve wanted an app that could

• Allow my students to take a picture of their work and share it with the class
• Certainly my computer should be able to display it, but with thumbnails for all the images
• Bonus if all the images are on everyone’s device

This post is about my attempt to make just such an app in Google Apps Script. It includes detailed instructions for how you can make your own copy. If you want to see it in action, see this vid.

Before jumping in, I want to say thanks to some great twitter friends who had thoughts about other ways to do this. I haven’t tried them all, but I think they all could work really well:

## How to get it working for you

Below I’ll explain a little more about how it works, but one important feature is the use of webSockets. To do that I use a free account on pusher.com. It limits all my apps to 100 total simultaneous connections. For the purposes of this app, that means 100 students all sharing images with each other. That’s fine for me, since I’ve never taught a class that big, but I can’t just let everyone use my copy of the app since then I’ll hit that limit pretty quick. The good news is that if you follow the steps in this section you can have your own version up and running that’ll have its own 100-user limit.

So: Step 1, sign up for a pusher account. I described how in this post.

On to step 2. Make your own copy of this google sheet. Make a google folder to hold the images and make sure its contents are viewable to the world. Then follow the instructions in this vid (:

And you’re done! Have fun!

To start the next class all you have to do is clear out the spreadsheet. You can delete the files in the image folder too if you want, but you wouldn’t have to.

## Ways to use it

I think my ideal situation is when I ask students to work on something, either individually or in groups, and then letting them all review everyone’s work. Without this app I’ve done that in the past by gathering their personal whiteboards and showing the class what I’ve found or asking them to walk around and look at other groups’ whiteboards. This way they see everyone’s and they can zoom in all they want without trying to see past people. It’s somewhat anonymous since the system doesn’t capture who uploads the image (actually it does capture the email and the date but I haven’t bothered to use that info in the UI).

Here’s an example: “Ok everyone, write down something you know about the dot product. When you’re done, submit your photo.” It seems to me you’d get some cosine (ugh) thoughts, some vectors saying “hey you, how much of you is parallel to me”, and some component-by-component scribbles. Imagine the students scrolling through all of that and then asking what patterns they’re seeing!

I also think this could be interesting in other situations. Imagine if your students were out and about doing some observations or something. They could stay on top of what everyone is seeing with this app. In other words, they don’t have to be in the same room to use the app.

## How it works

What’s stopped me in the past from making this app is the annoying habit of phone manufacturers where they make their cameras have incredible pixel densities. That means that the typical file sizes are multiple megabytes. That’s not really a big deal for your phone, but it means your uploads take a while and the server just gets hammered.

This time around I googled a little and found that you can resize images before uploading them. The local machine (your student’s phone) creates a much lower density copy and uploads that instead of the original. This also makes the final product much more responsive.

Next comes the hassle of uploading and saving files in google apps script. Here’s a chapter of my GAS book explaining how I do that, though I did a little different trickery due to the copy of the image that gets created by the local machine.

Next comes making a thumbnail-based image viewer. I just used what I found here. Of course I had to make it a little more dynamic, essentially recreating the thumbnails when a new image comes in. I don’t bother to make tiny images for the thumbnails, they’re just the full image shown small. But since they’re all pretty small to begin with, that works fine.

So all together then, here’s the life cycle:

• A student loads the page
• If they upload a new one:
• Their machine makes the low density version
• and then sends it to google.
• saves the url in the spreadsheet (which is why the page loaded all the old ones to begin with)
• and sends that url to all connected devices through pusher
• When a pusher message is received, the student’s machine requests the new image from the google server
• Then that image is added to the array of images at the top of the page.

Some issues:

• For some crazy reason, this only works on iPhones if they use an incognito window
• If for sure everyone loads the page at the beginning of class, you could skip the spreadsheet. But if they hit reload they’d lose all the old ones
• I gather google isn’t crazy about being an image hoster. It’s possible they’ll throttle you. In my (admittedly small scale) testing, I haven’t seen that. Famous last words, maybe
• I used a cool trick to automatically resize the image and upload it after a student selects the image. There’s no separate “submit” button. I like that
• On both iPhones and android when you select a new image you get the choice of your camera to take a picture. I love that as it reduces the steps for the students.

I’d love to hear what you think. Here are some starters for you:

• I love this. What I particularly like is . . .
• I can’t believe you reinvented the wheel, especially after such great suggestions from twitter. Loser
• I know why it has to use an incognito window on the iPhone . . .
• What’s wrong with cosine?
• Google here: cease and desist
• pusher here: are you ever going to pay for our service (note, I have paid when my school has scaled up some of my projects – they let you go month to month which is great)
• Here’s another cool way you could use it . . .
• I don’t think you need to be so worried about the original sized images and here’s why . . .

## Audience ranking questions

I’m helping to run a workshop this week and we realized that instead of using post-it notes through the day to capture “burning questions” we could use a tool that both collects questions and allows the audience to vote on them.

After the first day of post-it notes I volunteered to get us set up with one of the many systems that exist that do this. I went back to my hotel room and did the usual google searching, finding tools I had used (including the one that the Physics Education Research Conference organizers used earlier this month at their opening session) and others I hadn’t heard of. Unfortunately it seemed that most had a free level that only worked with 7 participants but it got expensive after that. Also most of the tools offered all kinds of extra bells and whistles, like tying in content or finding other ways to foster community, that we didn’t need.

I knew I could use the Q&A feature built into Google Slides (don’t know about that? Check it out!) but I knew there were two major issues: 1) it’s not really built to be open all day. In my experimentation in the past I noticed that the URL changes when you go back to the Q&A window a few hours later. Really that’s because it’s meant to be used in a typical 1-2 hour lecture, rather than a place to keep questions for a longer time. 2) while anyone can submit questions, you have to be logged into your Google account in order to be able to vote on questions. I’m not sure that’s a huge deal, but it always gives me pause when I’m dealing with an audience from diverse places. Certainly since my school has all our email powered by Google, it would be my go-to choice. Really it’s the first issue above that caused me to go down a different road for this workshop.

So as I was taking a shower after that disappointing google searching, it occurred to me that I might have the skills necessary to write my own solution. This post is about that.

Let’s first see it in action (there’s no sound, just trying to show all the features):

What are you seeing?

• It has a placeholder encouraging you to type in your question. I find this much cleaner than a label above the box.
• It clears the box out after the submission has been accepted (typically ~2 seconds)
• A ranked list of questions that gets updated in real time
• If the question is from you, you can’t “upvote” it
• You can only vote once on a question
• You can’t downvote or unvote a question
• A quick description at the bottom indicating the limitations

If you care, the rest of this post explains how you could build this yourself. Why not just use mine? Because of the limitations listed at the bottom of the vid: it can only handle 100 simultaneous users. That’s totally fine for this workshop, but I can’t just provide this tool to everyone. Instead, all you need is:

2. A pusher.com free-level account (this enables the webSocket technology and has the 100 simultaneous user limitation)

## Set up Pusher

• Under “Channels” create an app
• Hit “Create app”
• Navigate to the new app and click on “App Keys”
• You’re done with pusher!

You only two tabs:

• “questions”
• id
• question
• date
• id
• date

Then open the App script window:

In the app script window you should have a code.gs file. Add a main.html file and a pusher.gs file:

Here’s the code for Code.gs:

var funcs=[];
function doGet()
{
var sheet=ss.getSheetByName("questions");
var data=sheet.getDataRange().getValues();
data.shift();
var vdata=vsheet.getDataRange().getValues();
vdata.shift();
var vobj={};
data.forEach(d=>vobj[d[0]]=0);
vdata.forEach(v=>
{
// if(!vobj[v[0]]) vobj[v[0]]=0;
vobj[v[0]]++
})
var arr=data.map(m=>[m[0],m[1],vobj[m[0]]]);

var t=HtmlService.createTemplateFromFile("main");
t.funcs=funcs;
t.funcnames=t.funcs.map(f=>f.name);
return t.evaluate().setTitle("Sorted Questions");
}

const init=()=>{
if (!str)
{
var list=[];
} else {
var list=str[1].split(",").map(m=>Number(m));
}
displayQs();
}
funcs.push(init)

const displayQs=()=>
{
arr.sort((a,b)=>b[2]-a[2]);
// html=<h1>Sorted Questions</h1>;
// html+=<div><textarea rows="4" cols="50" id="newQuestion" placeholder="enter your question"></textarea></div><div><button onclick="newQlocal()">submit</button></div><hr/>;
var html="";
html+=arr.map(a=>
{
var h=<p>(${a[2]} votes)${a[1]};
{
h+= <button onclick="vote(this, ${a[0]})">upvote</button>; } else { h+= You upvoted } h+="</p>"; return h; }).join(" "); document.getElementById("main").innerHTML=html; } funcs.push(displayQs); const newQlocal=()=> { var question=document.getElementById("newQuestion").value; google.script.run.withSuccessHandler(newQBack).sendNewQuestion(question); } funcs.push(newQlocal); const newQBack=(id)=> { myvotes.push(id); var str=myvotes.join(","); document.cookie = "votes="+str+"; SameSite=none; secure"; document.getElementById("newQuestion").value=""; displayQs(); } funcs.push(newQBack); const vote=(el,id)=> { // myvotes.push(id); google.script.run.withSuccessHandler(newVBack).sendVote(id); } funcs.push(vote); const newVBack=(id)=> { myvotes.push(id); var str=myvotes.join(","); document.cookie = "votes="+str+"; SameSite=none; secure"; displayQs(); } funcs.push(newVBack); function sendNewQuestion(question) { var sheet=SpreadsheetApp.getActive().getSheetByName("questions"); var data=sheet.getDataRange().getValues(); data.shift(); if (data.length==0) { var newId=1; } else { var newId=Math.max(...data.map(m=>m[0]))+1; } Logger.log(new id is${newId})
var d=new Date();
sheet.appendRow([newId,question,d]);
sendToPusher("newQ", {row: [newId,question,0]});
return newId
}

function sendVote(id)
{
var d=new Date();
sheet.appendRow([id,d]);
sendToPusher("newV", {id:id});
return id;
}



Here’s the html for main.html:

<!DOCTYPE html>
<html>
<base target="_top">

<script src="https://js.pusher.com/5.1/pusher.min.js"></script>

<div class="container">
<div>
<h1>Sorted Questions</h1>
<div><textarea rows="4" cols="50" id="newQuestion" placeholder="enter your question"></textarea></div><div><button onclick="newQlocal()">submit</button></div><hr/>
</div>
<div id="main">
</div>
<div>
<hr/>
<p>If your vote isn't registered after ~10 seconds, it means
the system lost your vote. Feel free to try again. The system
has a limit of 30 simultaneous votes and 100 connected users.</p>
</div>

<script>
var globals = <?!= JSON.stringify(globals) ?>;
Object.keys(globals).forEach(key=>window[key]=globals[key]);
var funcnames=<?!= JSON.stringify(funcnames) ?>;
var funcs=[<?!= funcs ?>];
funcnames.forEach((fn,i)=>window[fn]=funcs[i]);

var pusher = new Pusher(key, {
cluster: 'us3',
forceTLS: true
});

var channel = pusher.subscribe('my-channel');
channel.bind('newQ', function(data) {
arr.push(data.row)
displayQs();
});
channel.bind('newV', function(data)
{
var row=arr.find(f=>f[0]==data.id);
row[2]++;
displayQs();
})

</script>

</body>
</html>


And here’s the code for pusher.gs:


var app_id = "YOUR APP_ID HERE";
var key = "YOUR KEY HERE";
var secret = "YOUR SECRET HERE";
var cluster = "YOUR CLUSTER HERE";

function sendToPusher(event,data) {
var pvals={
appId: app_id,
key: key,
secret: secret,
cluster: cluster,
encrypted: true
};

var url = https://api-${pvals["cluster"]}.pusher.com/apps/${pvals["appId"]}/events;
var body = {"name":event,"channels":["my-channel"],"data":JSON.stringify(data)};
var bodystring = JSON.stringify(body);
var now=new Date();
var d = Math.round(now.getTime() / 1000);
var auth_timestamp = d;
var auth_version = '1.0';
var bodymd5 = byteToString(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, bodystring));
var wholething = POST
/apps/${pvals["appId"]}/events auth_key=${pvals["key"]}&auth_timestamp=${auth_timestamp}&auth_version=${auth_version}&body_md5=${bodymd5}; var wholethingencrypt = byteToString(Utilities.computeHmacSha256Signature(wholething,pvals["secret"])); Logger.log(wholethingencrypt); var options = { 'method' : 'post', 'contentType': 'application/json', // Convert the JavaScript object to a JSON string. 'payload' : bodystring, 'muteHttpExceptions' : true }; var urltry = UrlFetchApp.fetch(url+?auth_key=${pvals["key"]}&auth_timestamp=${auth_timestamp}&auth_version=${auth_version}&body_md5=${bodymd5}&auth_signature=${wholethingencrypt}, options);

}

function byteToString(byte) {
var signature = byte.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
return signature;
}

Make sure you make the changes in the top 4 lines before moving forward.

Next you have to create a web app. Make sure to set it to execute as you but be available to everyone.

Now you can go to the url provided and it should be working!

## How it works

When someone goes to the url, google sends them all the questions along with the vote tally for each. It also checks the local users cookies to see if they’ve supplied any votes. If they have, it doesn’t let them vote again. This prevents ballot box stuffing, but you should know that it’s pretty easily defeated using incognito windows.

The user can enter questions or “upvote” existing questions. When they do, an AJAX call is made to the google server (any time you see google.script.run… that’s what’s happening) where google either saves the new question (making sure to give it a unique id) or saves the vote. In either case it saves the timestamp.

After updating the spreadsheet, the “sendtopusher” function runs, sending along either the new question (along with its id and an initial vote count of zero) or the id of the new vote. That uses the webSocket that pusher has set up to send that info to all connected devices.

If a device receives a new question from pusher it adds it to the list and re-displays all the questions. If a new vote comes in from pusher it adds to the vote count for that id and re-displays all the questions (this also involves sorting based on the vote count so that the highest voted question is always at the top).

I’d love to hear your thoughts. Here are some starters for you:

• Why do you sometimes capitalize google and other times you don’t capitalize Google?
• This is cool, can it also . . .
• This is a rip-off of my cool idea. You can send checks to . . .
• This is dumb. How does this preserve the time honored tradition of the first person asking not so much a question as a 10 point rebuff of everything they heard?
• You really can’t draw very straight arrows. It’s almost as if you’re writing this post on a chromebook on your lap at LAX
• I was at this workshop and I found this very useful.
• I was at this workshop and this was incredibly distracting
• Gross, I didn’t need to know that you had this idea in the shower.
• Here’s another way to protect against ballot box stuffing . . .
Posted in Google Apps Script, syllabus creation | Tagged | 2 Comments

## Brachistochrone for rolling things

The Brachistochrone curve is the shape of a wire for beads to slide down (friction free) to get from point A to point B the fastest. Note that since I used the word “down” there I’m implying this happens in gravity. Here’s an old post of mine describing how I go about teaching it. This post is all about scratching an itch I’ve had for a while: What if instead of sliding beads we want to roll balls. Is the shape the same? Spoiler: Nope, not the same.

My first thoughts had to do with how you’d factor rolling into the typical analysis. Normally you determine the integral formula for the time to go from A to B on an arbitrary curve given by y(x):

$\text{time}=\int_A^B dt=\int_A^B \frac{ds}{v(s)}=\int_A^B\frac{\sqrt{1+y'^2}dx}{\sqrt{2\text{KE}/m}}$

where y’ is the slope of the curve, s is how far along the curve the bead has gone, v is how fast the bead is traveling, and KE is the kinetic energy which is usually a function of y (since you’re cashing in gravitational potential energy). So if it’s rolling without slipping, my first thought was that all I had to do was add in some rotational kinetic energy:

$\text{KE}=\frac{1}{2}m v^2+\frac{1}{2}I\omega^2$

But then I realized that I had to know exactly where the center of mass was in order to figure out how much potential energy had been cashed in and I went down a rabbit hole.

## Does the center of mass follow the same curve? (No!)

You’ll see that at this point I jumped on twitter for the first time in a while (hey, my job is different now and a lot of what I do is untweetable, give me a break).

If you have a curvy road and you know the mathematical formula for one side (let’s say the road is going left to right along what we’ll call the “x-axis” and that it doesn’t turn back on itself so we can call it a function). Do you know the formula for the other side of the road? Is it just the same function with a shift? Nope. It took me a while to convince myself but this is the figure that sold me:

The blue curve is a pure sine function (why would I ever use cosine?). The orange curve is something like “sin(x)+0.32”. The green curve is what took me a while to derive but it’s really what enables a 0.32 diameter ball to fit between green and blue everywhere. Note that green and orange have the same amplitude and same frequency. Therefore, since they don’t overlap, the green curve is not a sinusoid.

So how do you derive the green curve? Well, here’s how I did it:

This represents a zoomed in version where locally the curve is flat. Also note that if you’re rolling up the other side (when the slope is positive) you need to make some adjustments to the signs in those equations. But that’s really all it takes. If you have a function for y(x) and you can calculate its slope at every location (y’), then you can figure out where the center of mass of the ball will be when you know the contact point with the curve.

Obviously I coded that in and ran it for a sine curve to get the figure above, but my same code would work with any (differentiable) function. Note that if the curve has a constant slope, the adjustments for the center of mass location are constant and then the other side of the road is truly just a shifted function. But that’s the only case that leads to that simple conclusion.

## Alright! Let’s solve a complicated differential equation!

Ok, so now we know how to find the center of mass location when you know the contact point. It seems like we could figure out the potential energy drop (and hence the kinetic energy) since we know the vertical drop of the ball. Seems like we’d be in business! Alas, no, we’re not. The problem is the angular frequency, or $\omega$ in the equation above.

For rolling without slipping on a flat surface, you know that your linear speed and rotational speed are tied together, namely $\omega=v/r$. Unfortunately, that’s not the case when rolling on a curved surface. This web page helped me understand this a little better. When you have a curved surface that has a local radius of curvature, $\rho$ you get this for $\omega$:

$\omega=\frac{\rho-R}{\rho R}v$

where v is the speed of the contact point along the surface.

No big deal, right? it’s just some weird multiplier in front of the speed. That should make solving for the speed from the kinetic energy easy! Well, that’s what I thought, and certainly that’s what led to me erroneous twitter posts (if you scrolled through). Unfortunately, $\rho$, you know that pesky local radius of curvature, is not easy to deal with. From wikipedia I learned that:

$\rho=\frac{\left(1+y'^2\right)^{3/2}}{y''}$

Ugh! Do you see that denominator?! Suddenly you need to know not just the slope of the function but its curvature as well. Let me tell you, that makes things gross.

Ok, gross maybe we can handle. We know how to calculate the kinetic energy and it’ll be all in terms of the (unknown) function, its slope, and its curvature. Maybe we can just close our eyes and throw it to Mathematica. Here’s where we’re at:

$\text{time}=\int_A^B \frac{\sqrt{1+y'^2}}{\frac{\left(1+y'^2\right)^{3/2}R/y''}{\left(1+y'^2\right)^{3/2}/y''-R}\sqrt{\frac{M g \left(y_0-\left(y-\frac{R/y'}{\sqrt{1+1/y'^2}}\right)\right)}{\frac{1}{2}(I+MR^2)}}}dx$

Fun right! Anyways, it’s technically all set to use the calculus of variations, but I’ve tried it, and wasn’t able to make any progress. 😦 I think the biggest problem is the y”s in there because they lead to a third order differential equation, which means I need to supply not only where to start the curve and what direction to head, but also the local curvature right there. Needless to say, I didn’t make much progress. If you have ideas, I’m all ears!

By the way, here’s what it looks like if you’re just doing a bead sliding down a wire:

$\text{time}=\int_A^B\frac{\sqrt{1+y'^2}}{2 g (y_0-y)}dx$

Muuuccch easier, trust me. (Also note that if you thought I’d be using the word “cycloid” by now, you don’t get there this way. You only do if you swap x and y. You know an “obvious” thing surely your students would think to do.)

## When in doubt, check the literature

So I started googling. Here’s an awesome paper from 1946 that helps us put it all together. What they’re saying is that even when rolling on a curved surface, you can use $\omega=v/r$ as long as you’re using the speed of the center of mass, not the speed of the contact point. Alas, even though they’re always moving in parallel, they don’t have the same speed (think about going up and over a hump in a roller coaster, you’re moving faster than the contact point on the track). Note that they’re also saying that the center of mass follows the traditional brachistochrone! So what is this post all about!? Well, we want to know the shape of the track the ball is rolling on, and if you’ve read what I wrote above you’d know that’s different!

How did they prove it was the traditional curve? Because you get the very simple equation above instead of the incredibly ugly one if you use the coordinates of the center of mass and not the contact point. With that same simple equation, you get the same simple result (if you must: a cycloid).

But now we can put it all together. If I have a normal brachistochrone, I can find the curve for the ball to roll on by doing the coordinate shift in the figure above in reverse!

I know, I know, the blue path (the track for the ball to roll on) sure looks like a standard brachistochrone, but it’s not, because of what I was talking about above. Don’t believe me, let me hear it!

## Update!

I don’t know why I didn’t do this last night, but here’s that same image with an added brachistochrone from the start to the finish of the track in green. See, I told you the blue curve wasn’t a brachistochrone:

## Starters

Your thoughts? Here are some starters for you:

• What do you mean all you had to do was say “down” to imply gravity?
• Seriously, I have to read a whole other post of yours just to be able to read this one. No way! I’m unclicking. You can’t count my click.
• What do you have against rabbits? Why does going down their holes feel like an interminable complicated journey?
• What do you have against cosine?
• Duh, of course you needed to know about the curvature. What are you, an idiot?

## Breakouts

I’ve had a growing wish list for breakouts. Here’s the features I’ve been able to build in:

1. Pretty easy to assign students to new breakouts
2. Automatically logs them out of the main room and logs them into the breakout room. They don’t have to press anything.
3. Chat and whiteboard dedicated to each breakout
4. When they’re in a breakout they can still see the main room chat and whiteboard, though if those are updated by the instructor during the breakout they won’t see the changes. This is especially useful for when students can’t remember what they’re supposed to be doing.
5. The instructor is “in” every breakout, though they start with no sound (in or out) to cut down on the cacophony. They can interact with chat and whiteboard right away and can rejoin with sound if they (or the students) want.
6. When students are back in the main room they can still go see the chat and whiteboard of the breakout. The instructor can also share all breakout room boards to everyone if they want.

Things that are still on the wish list:

1. Easy way to save who has been assigned to breakout groups in the past to easily replicate
2. Easier way to have the instructor talk to the students without having to rejoin

## Whiteboards

I spent a lot of time last year learning how to manipulate html canvas elements, including figuring out how to capture where a pen has gone so that I could send those coordinates to everyone. The problem is that the work I did just scratched the surface of what I wanted. I realized that lots of smart people have tackled online whiteboards and maybe I could just dump a useful one in an iframe on my page. Well, yep, that’s exactly what I did.

Mine is a google school, meaning that user@mycoolschool.edu is really a gmail account. That means I can leverage the google infrastructure for user authentication (built in already) and for generating and sharing various documents. That includes the very handy Google Drawings! Yes google also owns and suggests using jamboard for online collaboration, but you can’t (yet?) embed those in iframes. But Google Drawings are nearly as useful, including the ability to put in hyperlinks, and doesn’t mind at all being in an iframe.

Let’s say we’re all in the main room and I want to share a screenshot of the code we’re developing. Here’s what happens:

1. I (as the instructor of the course) hit the “whiteboard” button.
2. A request is sent to the google server asking it to make a copy of my blank drawing template, save it in the google drawings folder of the class (which is shared with everyone in the class), and return the url of it.
3. The url is sent to every participant with a message saying their local javascript should launch an iframe and fill it with that url.
4. Now everyone is staring at the whiteboard on the page (they don’t have to go anywhere else!) and they can interact with it.

Because it’s saved in a folder they have access to (with a handy name indicating what class, room, and date it was used in/on) they can always go back, even outside of class time, to look at it.

If the instructor repeats the process listed above, the iframe currently displayed is set to “style.display=none” and another is generated with the new url as the source. The students can flip back and forth among any of the whiteboards that are launched this way. If the instructor wants to make sure everyone is looking at the same one they can force that. If a student joins late, this process works for them seamlessly as well (in other words they see any that the instructor either hits “see mine” or “new whiteboard” after they’ve joined).

Whiteboards that are used in breakout rooms can be sent to everyone in the main room by the instructor as well.

## Raise hand queues

I’ve talked a lot about this before. I just directly lifted the code from my old version. It goes beyond a normal hand-raise queue (that might, for example, show the names in chronological order) by having two queues: one for follow ups to the current topic and another for new topics. Everyone can see who is in either queue and they can transfer their hands to and from either queue.

To save bandwidth and complexity I no longer store this information on the server for analysis later. I can always add that back in if it seems like it would be useful.

Note this functionality only works in the “main” room.

## Chat

I really dislike how Zoom and Google Meet privilege video over the chat window. My app makes sure that chat is always front and center.

Students can also initiate 1-on-1 chats with anyone else in the same room as them (recall that the instructor is always in all the rooms). I really think this is important as often people would rather get a quick clarification of something from a friend/colleague/classmate than ask the whole class. I’ve seen some folks talking about the loss they and their students feel when they realize that they don’t have this tool, at least not easily.

I’ve made sure to make all chats visible so the users don’t have to click a pulldown to see their various chats. This should dramatically reduce the number of times someone sends a text to the wrong person.

When there are breakouts going on, the instructor can send messages to individual breakout rooms or to all of them at once.

## Polling

I really like using quick polling, whether that’s for Think-Pair-Share/Peer Instruction polls or just to check something quick, like “should we do an open-book test?”

I’ve built in a very simple polling system for the moment. The 4 (for now but easily changeable) choices are checkboxes always on the screen for the student. If I ask a question I’ll just say something like “(1) is for ice cream, (2) is for donuts, and (3) is for broccoli.” The results show up on the fly for the instructor who can then just tell the class the result.

Eventually passing the results to everyone is doable, but I’m not in a rush, as the way I’ve always done peer instruction is exactly as I’ve built it.

## Understanding checks

In my old synchronous dashboard I was proud of the various buttons I put up. Things like yes, no, confused, laughing, cat’s-on-my-computer were, I thought, a fun way to foster interaction. However, after using them for teaching and for meetings with colleagues, I noticed that people very rarely used them at times other than when I asked for a quick poll. So I figured the polling above would be a better solution.

However, there’s something I do in teaching face-to-face all the time that I wanted to replicate here if I could. Quite often I’ll say to students that I want to get feedback from them on a particular scale, like “confidence you can get the Twitter api to work.” Instead of seeking a binary answer, I tell them to use their hand height to indicate their confidence. Putting your hand on (or even below!) your desk indicates a great lack of confidence, while raising it high above your head shows great confidence. I’ve really liked those moments, though sometimes I think people are nervous everyone is looking at them.

So for online I thought I’d use an input type=”range” or slider to accomplish this. I call it an analog slider but it’s really only got 100 steps in it (0-100). Students can set it when I ask such a question and I (as the instructor) immediately see the class average.

I plan to use this a lot in class by asking for “understanding checks” or possibly “confidence checks.” I’m really excited about it!

Well, what do you think? I’d love to get some feedback, especially in this last week before class starts.

Here are some starters for you:

• I’m in a class with you next week and you said I should come read this post before class starts. Ok, done. Do I get points now?
• Where do I go to drop your class?
• I’m going to be in this class and this sound really cool. What I’m most excited about is …
• This really sucks. The worst part is …
• … and I think you should update your online store so that people are warned about the danger of these particular cucumbers…. oops, I thought I was typing in the comment section of a different tab
• How can cucumbers be dangerous?
• Let me tell you my cucumber story …
• Between jamboard and google drawings I think the most interesting differences are …
• This is blatantly stealing from ….
• I love video. How are you planning to do attendance checks if you can’t see their smiling faces?
• I hate video, thanks! However there’s one thing I think I’d miss …
• I think you should add ….
• I think you should change ….

## Physics Teachers Are Awesome

I’ve started a project that brings me joy. I’m hoping to help spread that around!

I was looking around for ways that I could support physics teachers who were working so hard to teach during this pandemic. I was reflecting on how I miss the interactions and feedback I used to be a part of during the Global Physics Department heydays and I settled on trying to get a little taste of our old “submit a video of your teaching and we’ll give you feedback.”

So for over a month now I have committed to spending a part of every week(work) day making a reaction video to a physics teacher’s video they’ve made public. You can see the full playlist (37 long as of this morning) here. I look for videos made by teachers that don’t have students in them (privacy reasons even if they’re public) that are lectures, homework solutions, or worked examples. I don’t tend to react to “welcome to my class videos.” My guiding principles are:

• Lift teachers up
• Share interesting/funny anecdotes about my teaching and physics in general
• Open up opportunities for fun conversations about teaching

For the first one, I will often re-read one of my favorite posts about academic bullying. There I talk about how hard newer teachers have it when they run into online folks who seem to have it all figured out. They can be quite intimidated and find sharing their struggles to be difficult. So I figured that if I’m nearly uniformly positive and supportive of their work I can be helpful. I guess you can judge for yourself.

The second bullet comes pretty naturally. These awesome teachers show me cool solutions to problems I’ve had in the past and I’m happy to share funny stories about lessons I’ve learned. I also find that I often tie in ideas of how physics is used/seen in the wild because the teachers prime me for that.

This post is really about the third bullet above. While I’ve had a little interaction on twitter and youtube comments, I would love to talk with folks about teaching. I tend to seed each video with questions I still have about different ways to approach things and I’d love to hear more about what folks think.

So I thought I’d try a slightly different approach. In addition to randomly searching youtube for vids to react to, I thought I could let people volunteer themselves, both to have me react to them but also to be willing to debrief with up to four other physics teachers who I’ve reacted to. A twitter friend, @TadThurston, did exactly that and we have since had several conversations, both on twitter and through a google meet call, that has been so fun.

So I’m proposing that folks use this google form to volunteer and once I react to them I’ll reach out to them along with the four other people I react to in the same week to try to schedule a “physics debrief” where we can talk about physics teaching and lift each other up.

Thoughts? Here are some starters for you:

• You reacted to me and I thought it was great. What I especially liked was . . .
• You reacted to me and I sent you a cease and desist order, did you get it yet?
• This is cool, but I think you should also consider . . .
• This is a brazen rip off of . . .
• Can you handle vids where I walk students through how to use python? (yes)
• I’ve watched a few of these and your breathing is really loud
• I really like it when you notice things like the tech we’re using
• Get a green screen, will you!
• This doesn’t sound like work, stop using work time to do it (I know what your office looks like)
• Can I request you to react to several vids? (sure!)

## Boltzmann to Blackbody to Electoral College

Ok, I know that’s a weird title, but bear with me, this has some fun stuff in it, including some things I still need help with.

The basic idea is that Planck’s solution to Blackbody radiation is an interesting way to view the quantum problem with the electoral college. We’ll have some fun tangents along the way.

## Ultraviolet Catastrophe

Blackbody radiation is all about describing the (mostly infrared) radiation coming from a hot thing. I’ve written a little about it before but really there’s only a few things you need to know:

• Hot things give off radiation
• A hot thing with a cavity inside with a small hole to the outside is the easiest to model
• There were two 19th century physics ideas that most people brought to the analysis.
• 1. We know how to count how many standing waves can exist inside a cavity (really the number between some very tiny range of frequencies)
• 2. We assume that all modes of energy (including standing waves) play well together so they all end up with the same average energy, namely something proportional to temperature (the proportionality constant is called the Boltzmann constant and we traditionally use ‘k’ for it).
• Putting both of those ideas together led to something very strange. Together they predict that there’s an infinite amount of energy down into the ultraviolet part of the spectrum.
• Since that’s not found experimentally, Planck reasoned that one or both of the 19th century ideas had to be wrong. He decided to go after the second one with a very simple (but very strange at the time) idea. Namely that standing waves couldn’t have any amount of energy. Each one could only have an integer amount of some base energy that happened to be proportional to its frequency: $E=nh\omega$ where n could only be integers and h was eventually named Planck’s constant.
• While this sounds like a fun mathematical approach, it’s interesting to note just how weird it is. It means that when you’re playing jumprope, you can only set the height of the main peak (where the jumper is) to a set of possible amplitues. Weird.

So how did Planck’s approach avoid catastrophe? Well, the answer is what eventually gets us to the electoral college, so thanks for bearing with me. The higher frequency standing waves couldn’t have an average of kT energy in them because that’s not even enough for the integer n to be 1. Basically if you gave them the lowest (non-zero) energy they’re allowed to have, they’d screw up the average. So they get frozen out and don’t get to play. If they can’t play, they don’t cause the catastrophe.

What’s the connection to the electoral college? As things stand now, each electoral vote does not represent the same number of people. The reason is that our election system can’t tolerate the “freezing out” approach and so instead rounds things up to the nearest integer. Basically all the states are considered to be the same type of standing wave, but their base energy (or base population in this analogy) is set so that it’s the whole US population divided by 538 (the total number of senators and members in the house of representatives). These days that’s roughly 600,000 people. The problem is that some states don’t have that many (actually 3x that many since they all get 2 senators and at least one representative). So they round up, and that means those states get a larger impact on the vote. Actually the fact that each state gets 2 for free from their senators already screws things up, but one solution is to change the base count to be much lower so that California gets a ton more and tiny (in population) states keep their 3.

The rest of this post details some of the strange things I ran into when trying to simulate some of this. If all you care about is the electoral college stuff, there’s not a lot more below. However, if you’re into teaching things like quantum physics and Blackbody radiation, read on because I need some help!

## Boltzmann Distribution

The second 19th-century bullet above was a really cool thing when people put it together. The derivation involves a pretty nasty integral (really the ratio of two ugly integrals) but ends up with the amazing result that all energy modes share the same aveage energy: kT. Amazing. But as I was thinking about this post and thinking about doing some simulations, I figured I wouldn’t need to explain the nasty integrals as I likely could just show some fun simulations showing the average energy working out.

That’s when I hit a snag!

I figured I could do some early statistical tests of my simulations by checking that they followed the Boltzmann distribution. What’s that, you ask? Consider a system of lots of particles, each of which can be in a random energy state, except that the sum of their energies needs to be a constant, the total energy in the system is fixed. If you reach in and grab a particle, Boltzmann tells you the probability of finding that particle to have energy E: it’ll be proportional to $e^{-E/kT}$. The proportionalilty constant is found by ensuring the total probability of any energy is 100%, hence the second nasty integral I mentioned above (for normalization).

So a great test of a simulation of particles with random energy (where you fix the total energy all the particles to be a constant) is to make sure that the lowest energy states are the most probable and that their probability distribution is (roughly) exponential decaying.

Well, when I tried to put such a system together, I found that most approaches didn’t follow the Boltzmann distribution!

## Random microstates

Ok, so it’s your job to put together a collection of particles with random amounts of energy so that their total energy adds up to a fixed constant. How do you do it? Note that while you can also tackle this where you let the particles have any energy value, we’re jumping right into the quantum approach where the total energy is a (large) integer and each particle has to have an integer level of energy. Note also that I’ll likely switch back and forth between particles and energy on the one hand and buckets and balls on the other.

Here are the 4 ways I’ve tried to solve this problem:

Method 1: Stars and bars: Imaging laying out the total energy like an array of cells. Now choose N-1 cell borders randomly. Feel free to choose the end points. But note that there’s always one on each end, so that really it’s N+1 boundaries. Between any two successive borders is the energy of a particle. With N+1 boundaries that gives you N particles, all of which have an integer number of cells (or energy) in them.

Method 2: For each unit of energy, randomly select a particle to go to (or ball to a bucket). Then just look at each particle (in each bucket) to see how many are in there.

Method 3: Grab a particle, and randomly give it energy ranging from zero to the max energy. Then move the next particle and give it a random amount from zero to whatever’s left after the first particle. Then repeat until you’re out of energy. If the number of particles considered by that point is less than the total number, just set the remainder to zero. If the number considered is greater than the number, start over.

Method 4: For each particle generate a random integer between zero and the total energy possible. Then add up all the energies. If the total is the total energy allowed, keep it. If not, try again (this one is really slow).

Which one do you like? I’ve been having some fun conversations with folks on twitter about this along with looking up suggestions on various pages online. Google searching seems to run into method 1 a lot, while most of my physics buds like method 2 the best (Thanks To my friend Gillian for first suggesting this way – I felt dumb that I’d spent so much time on method 1 before moving on to that one).

For me I think I like method 2 the best. It seems to be the most random, and it runs nice and fast, though you do have to do some tallying. Method one has a great visual, and is called stars and bars because people have been typing things like |**||***|*|***| for a long time when teaching about probabilities. Method 3 felt like a way to avoid the immense waste of time that Method 4 represents.

So, which follows Boltzmann? That was my big question. Honestly my guess was “all of them!” but, well, I was wrong:

Here’s the code for each method:

def method1(buckets, balls):
# bars and stars method
edges=np.concatenate(([0],np.sort(random.randint(0,balls+1,buckets-1)),[balls]));
return np.diff(edges)
def method2(buckets,balls):
# assign each ball a random bucket
ballassign=random.randint(1,buckets+1,balls);
return np.array([np.count_nonzero(ballassign == i) for i in range(1,buckets+1)])
def method3(buckets,balls):
# randomly put some balls in first bucket, move on until you run out
curballs=balls
cur=np.array([random.randint(0,curballs+1)])
curballs=balls-np.sum(cur)
while curballs>0:
cur = np.append(cur,random.randint(0,curballs+1))
curballs=balls-np.sum(cur)
if (cur.size<buckets):
if (cur.size>buckets):
return method3(buckets,balls)
return cur
def method4(buckets,balls):
# try buckets random balls until sum is correct balls
t=random.randint(0,balls+1,buckets)
while np.sum(t)!=balls:
t=random.randint(0,balls+1,buckets)
return t


What the actual heck?! Why don’t they follow Boltzmann? Only method 4, the slowest (by far!) does it. Most of the rest of them way undercount zeros (meaning that if you randomly grab a particle after running this 100,000 times you find zero less often than you should). Lots of my twitter and fb buds have lots of explanations. Most have to do with counting microstates but not multiplicities (for you real statistics nerds). Here’s an example: Consider method 1 with 5 units of energy and only 3 particles. There are lots of possibilities, but lets only consider these four (where the number is where the two (N-1, remember) boundaries were randomly placed): [0,0], [5,0], [0,5], [5,5]. When you remember the two boundaries that are always added and remember that you actually have to sort the random numbers before doing the differences you get [0,0,0,5], [0,0,5,5], [0,0,5,5] and [0,5,5,5]. That leads to particles with 0,0,5; 0,5,0; 0,5,0; and 5,0,0. Do you see how you’re over-generating (0,5,0)? Yeah, that sucks.

Two fixes my friends have told me about:

1. Fix the stars and bars (method 1) thusly: Make a bag of N-1 bars and Etotal stars. Then randomly draw things out of the bag. Then do the work above. To see the difference, consider a system with 10 particles and only 1 unit of energy. That would mean 9 bars and one star. Method 1 would generate all the bars only on 0 or 1, leading to the one star being somewhere in the middle. In fact, as you can see above in the [0,5] conversation, you’d quite unlikely to find the particle in the first or last bin. But with this correction since the bars and stars are all jumbled together, you’re just as likely to get the star at any location. My brain still hurts about this one but I really appreciate my buddy Craig for helping me see it.
2. Fix method 2 by just making copies of any state you find. The number of copies you need to make is the multiplicity you’d expect from the permutations among all the particles. An easy example: if you you get (0,0,5) make 3 copies of it so that you’ll get the same number of zeros as you would with all the permutations: (0,0,5), (0,5,0), and (0,0,5). I don’t particularly like this solution as you’re making a weird manual correction but I believe it produces the Bolzmann distribution.

## What does nature do?

To me this is the big question. All the methods discussed are ways to produce viable states that make sure to have the right energy. The typical derivation talks about how we assume that any natural system will randomly access all the possible states with equal probability, leading fairly directly to the Boltzmann distribution (or at least an approximation that gets better the bigger your system is). But here’s where I’m stuck. If a “real” system has some distribution of energy into the various particles at one moment of time, what’s the best model to come up with a different distribution in the near future? Honestly for me it’s method 2. You just let every particle go find a new home! But that doesn’t have the right distribution and so wouldn’t lead to all the normal statistical mechanics results we expect.

Method 4? That’s weird. That would be saying that every particle takes on random energies and it all get locks in only when the total energy is right.

Correction 1? I guess that works for me, but I still feel Method 2 makes more sense physically.

Correction 2? That’s just weird. All the energy quanta go find a new particle home and then somehow the system rapidly cycles through all the permutations.

My brain hurts on this one. I’d love some suggestions below.

## Modeling a Blackbody

Ok, I’m not modeling the whole thing. Really I wanted to try modeling a system made up of particles who have different minimum energy spacings. Some can take on 1,2,3,… units of energy while others are limited to 0,4,8,12,… or 0,3,6,9,… or 0,117, 234, 351,… units of energy.

What am I hoping to see? What I’d love to see is an approximation of Planck’s prediction of average energy per type of particle. That’s given by:

$E_\text{avg}\propto\frac{\Delta E}{e^{\Delta E/kT}-1}$

So how can I model that? Well, here’s what I tried: I randomly assign each unit of energy to a particle. Then I round the energy in each particle down to the nearest level it can actually handle. Then I repeat with the leftover energy. I keep doing that until all the energy is used up. Here’s the code:

import numpy as np
from matplotlib import pyplot as plt
from numpy import random
from scipy.optimize import curve_fit
# this gives a list of quanta in each bucket, not balls.
# so [1,2,3] for buckets that can hold [1,2,3] energy means [1, 4, 9] energy in each
def redistribute(current_quantas, current_energy,buckets,total_energy):
# randomly assign all balls to a bucket
nbuckets=len(buckets)
assign=random.randint(0,nbuckets,current_energy)
# find out how many balls in each bucket
t,_=np.histogram(assign,range(nbuckets+1))
#round down for each to the nearest full quanta in each bucket
rounded=t//buckets
# add to the current quantas in each bucket
newbuckets=current_quantas+rounded
# find the leftover energy
leftover=total_energy-np.dot(newbuckets,buckets)
# repeat until all energy is distributed
if leftover==0:
returnvalue=newbuckets
else:
returnvalue=redistribute(newbuckets,leftover,buckets,total_energy)
return returnvalue
def f(x,a,b):
return 0.1*a*x/(np.exp(x/b)-1)
def f2(x,a,b):
return a*np.exp(-x/b)
def wholething(num_buckets,num_balls,max_quanta,loops):
buckets=random.randint(1,max_quanta+1,num_buckets)
#print(buckets)
test=np.array([redistribute(np.full(num_buckets,0),num_balls,buckets,num_balls) for i in np.arange(loops)])
yvals=buckets*np.mean(test,axis=0)/(num_balls/num_buckets)
#print(yvals)
planckfit,_=curve_fit(f,buckets,yvals)
x=np.linspace(1,max_quanta,100)
plt.plot(buckets,yvals,'o')
plt.plot(x,f(x,*planckfit),label='Planck')
plt.legend(loc="upper right")
plt.ylim(bottom=0)


and here’s a few examples looking at the average energy per mode with a Planck fit (everything is scaled so that 1 is the expected kT average energy)

Ok, that’s cool and all, but here’s the weird thing. This is basically based on Method 2, which I’ve shown above doesn’t follow the Boltzmann distribution! My brain hurts yet again. I’ve love some collaborators who could help me reason this out.