Cloud apps for mobile learning of Calculus and  Engineering Design

José Luis Gómez-Muñoz, Tecnológico de Monterrey, México
Luis Vargas-Mendoza, Tecnológico de Monterrey, México
Rubén Darío Santiago-Acosta, Tecnológico de Monterrey, México

Join the Conversation #WolframTechConf

ABSTRACT

There are many commercial apps for mobile learning, however, when the instructor has a very specific need, the flexibility and power of the Wolfram Language and its cloud-deploy capabilities can be used to create ad hoc solutions. That was the case for a course of “Machine Elements in Mechanical Design” at our University. Engineering Design requires many iterations of tedious calculations, and students must have time to analyze the result of each one, and take decisions to perform the next iteration. We used the Wolfram Language to develop calculation apps for gears and belt drives, specifically designed for the lectures.  Since the apps we created are deployed in the Wolfram Cloud, students only need an internet browser in their smartphone, tablet or computer. Students that used them to solve homework and exam assignments were able to generate several different successful designs. Then we moved on to implement Adaptive Learning Apps for Calculus. Adaptive learning is an education technology that can respond to a student’s interactions in real-time by automatically providing her or him with individual support. Our Adaptive Learning App continuously saves the progress of the user, therefore each student can come back another day to continue working, and the instructor can monitor her or his efforts. Questions and answers can have random parameters, so that different students see different versions of the question, and the correct answer appears in a different position in the multiple-choice list for each user.

Mobile Learning and the Wolfram Language

There are many commercial apps for mobile learning, however, when the instructor has a very specific need, the flexibility and power of the Wolfram Language and its cloud-deploy capabilities can be used to create ad hoc solutions.

That was the case for a course of “Machine Elements in Mechanical Design” at our University. Engineering Design requires many iterations of tedious calculations, and students must have time to analyze the result of each one, and take decisions to perform the next iteration.

Learning Engineering Design

Engineering Design requires many iterations of tedious calculations, and students must have time to analyze the result of each one, and take decisions (Evaluation) to perform the next iteration.

Output of FormFunction

A problem: the output of next code is supposed to be a Grid with pink and yellow background, however when deployed in the cloud, the colors are not shown (2018, Mathematica 11.2) in the web-browser result:

fofu=
FormFunction[{{"n","An integer between 5 and 20:"}->Restricted["Integer",{5,20}]},Function[Grid[Prepend[Table[{k,Prime[k]},{k,1,#n}],{"Index","Prime"}],Dividers->All,Background->{None,{{Pink,Yellow}}}]],AppearanceRules-><|"Title"->"Some prime numbers","ItemLayout"->"Vertical"|>];
CloudDeploy[fofu,Permissions->"Public"]

See the result here http://tinyurl.com/community20170530a

GIF Output of FormFunction

More information in our post in http://community.wolfram.com/groups/-/m/t/1111213?p_p_auth=AKczR4tg

Now there is an easy fix if you want to see the pink and yellow background: Use the third argument of FormFunction to specify that the output must be a GIF file:

fofu=
FormFunction[{{"n","An integer between 5 and 20:"}->Restricted["Integer",{5,20}]},Function[Grid[Prepend[Table[{k,Prime[k]},{k,1,#n}],{"Index","Prime"}],Dividers->All,Background->{None,{{Pink,Yellow}}}]],
"GIF", (* THIS IS THE DIFFERENCE, GENERATE GIF AS OTUPUT *)
AppearanceRules-><|"Title"->"Some prime numbers","ItemLayout"->"Vertical"|>];
CloudDeploy[fofu,Permissions->"Public"]

See the result here http://tinyurl.com/community20170530b

The same is true for mathematical notation, the best way we found to get it right is to show the final output as a GIF image.

Multipage FormFunction

Since each iteration consists of many calculations, some of them depending on a previous calculation, or the reading and interpretation of data in a graph, we used multipage FormFunction for our Engineering Design Apps. Some times the values of the variables are lost from one page to the next one, it seems that each page works in a new Kernel. Therefore we used a With[] command to recalculate the variables at each page. A better technique would be to use the AskFunction[], but we avoided it because it was marked as Experimental in the documentation (2018, Mathematica 11.2)

Unprotect[$Permissions];
$Permissions = "Public";
ff=
With[{
input="xInput", (* input="Input" para campos con prellenado para pruebas rápidas, tanto locales como en la nube; 
input="xInput" para campos vacíos en publicación final en la nube para uso de los estudiantes  *)
cloudimport=CloudImport, (* cloudimport=xCloudImport para No descargar las imágenes de la nube y usar imágenes locales en pruebas rápidas ejecutadas en forma local,  cloudimport=CloudImport para usar imágenes de la nube en publicación final para uso de los estudiantes *)

(* Constantes: *)
pasosDiametrales={1.25, 1.5,1.75,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,13,14,15,16,18,20,24,32,48,64,72,80},
steel=1,malleableIron=2,nodularIron=3,castIron=4,aluminumBronze=5,tinBronze=6,
factorElastico=
{
{2300,2180,2160,2100,1950,1900},
{2180,2090,2070,2020,1900,1850},
{2160,2070,2050,2000,1880,1830},
{2100,2020,2000,1960,1850,1800},
{1950,1900,1880,1850,1750,1700},
{1900,1850,1830,1800,1700,1650}
},

(* cálculos y pantallas de resultados: *)
calculosCinematica=HoldComplete[
mdeseada=((#we)/(#wsdeseada)); (* mdeseada es la relación de transmisión deseada, con base en la velocdad de salida deseada *)
ns=Floor[mdeseada*#ne]; (* el número de dientes de salida ns se calcula como el entero más cercano a dar la relación de transmisión deseada, usé Floor en lugar de Round para obtener mismos resultados que software comercial *)
m=N[(ns/(#ne))];  (*m es la verdadera relación de transmisión que se logra con el número de dientes de salida entero...*)
ws=N[((#we)/m)]; (*...por eso la velocidad real de salida ws es un poco diferente de la deseada *)
de=N[((#ne)/(#pd))]; (* El diámetro de la rueda queda determinado por los dientes del piñon y el paso diametral, ambos proporcionados por el usuario *)
ds=N[(ns/(#pd))]; (* El diametro de salida queda determinado por el paso diametral proporcionado por el usuario, y por los dientes de salida ns calculados arriba *)
vt=N[((π*de*#we)/12)] (* Velocidad del punto de paso [ft/min], es la misma en piñón y en rueda *)
],
datosCinematica=HoldComplete[
{"DATOS "," CINEMÁTICA"},
{"Velocidad de entrada [rpm] = ",#we},
{"Paso diametral [teeth/in] = ",#pd},
{"Número de dientes del piñón Ne = ",#ne}
],
resultadosCinematica=HoldComplete[
{"RESULTADOS "," CINEMÁTICA"},
{"Relación real del engranaje m = ",m},
{"Velocidad real de salida [rpm] = ",ws},
{"Número de dientes de la rueda Ns = ",ns},
{"Diámetro de paso del piñón de [in] = ",de},
{"Diámetro de paso de la rueda ds [in] = ",ds},
{"Velocidad del punto de paso [ft/min] = ",vt}
],
datosResistencia=HoldComplete[
{"DATOS "," RESISTENCIAS"},
{"Tipo = ",If[TrueQ[#grado1Q],"Grado 1","Grado 2"]},
{"Dureza a la masa del piñón [HB] = ",#dure},
{"Dureza a la masa de la rueda [HB] = ",#durs},
{"Vida útil [hr] = ",#ele}
],
calculosResistenciaFlexion=HoldComplete[
sfe=If[TrueQ[#grado1Q],
		0.0773*(#dure)+12.800,
		0.1020*(#dure)+16.400];
sfs=If[TrueQ[#grado1Q],
		0.0773*(#durs)+12.800,
		0.1020*(#durs)+16.400];
nciclose=N[Round[60*#ele*#we,10000]];
ncicloss=N[Round[60*#ele*ws,10000]];
kLe=If[TrueQ[#grado1Q],
		1.3558*(nciclose^(-0.0178)),
		1.6831*(nciclose^(-0.0323))];
kLs=If[TrueQ[#grado1Q],
		1.3558*(ncicloss^(-0.0178)),
		1.6831*(ncicloss^(-0.0323))];
sigmaperme=(kLe/(#kr))*sfe;
sigmaperms=(kLs/(#kr))*sfs
],
resultadosFactoresResistencaFlexion=HoldComplete[
{"FACTORES "," RESISTENCIA A LA FLEXIÓN"},
{"Ciclos de carga de entrada [millones] = ",nciclose/1000000.},
{"Ciclos de carga de salida [millones] = ",ncicloss/1000000.},
{"Factor de vida por flexión de entrada kLe = ",kLe},
{"Factor de vida por flexión de salida kLs = ",kLs},
{"Factor de confiabilidad kr= ",#kr}
],
resultadosResistenciaFlexion=HoldComplete[
{"RESULTADOS "," RESISTENCIA A LA FLEXIÓN"},
{"Resistencia sin corregir a la flexión del piñón [ksi] = ",sfe},
{"Resistencia ya corregida a la flexión del piñón [ksi] = ",sigmaperme},
{"Resistencia sin corregir a la flexión de la rueda [ksi] = ",sfs},
{"Resistencia ya corregida a la flexión de la rueda [ksi] = ",sigmaperms}
],
calculosResistenciaSuperficial=HoldComplete[
sfce=If[TrueQ[#grado1Q],
		0.327*(#dure)+26.000,
		0.364*(#dure)+27.000];
sfcs=If[TrueQ[#grado1Q],
		0.327*(#durs)+26.000,
		0.364*(#durs)+27.000];
cLe=If[TrueQ[#grado1Q],
		1.4488*(nciclose^(-0.023)),
		2.466*(nciclose^(-0.056))];
cLs=If[TrueQ[#grado1Q],
		1.4488*(ncicloss^(-0.023)),
		2.466*(ncicloss^(-0.056))];
a=Piecewise[{
{0.00698,((#dure)/(#durs)) > 1.7},
{8.98*(10^(-3))*((#dure)/(#durs))-8.29*(10^(-3)), 1.2≤ ((#dure)/(#durs)) ≤ 1.7}
},0.0]; (* Sección 14-12 ecuación 14-36 del Shigley's Ninth Edition in SI units *)
ch=Max[1.0, 1.0+a*(m-1.0)];
cr=#kr;
sigmaCperme=((cLe*ch)/cr)*sfce;
sigmaCperms=((cLs*ch)/cr)*sfcs
],
resultadosFactoresResistencaSuperficial=HoldComplete[
{"FACTORES "," RESISTENCIA SUPERFICIAL"},
{"Factor de vida superficial del piñón cLe = ",cLe},
{"Factor de vida superficial de la rueda cLs = ",cLs},
{"Factor de dureza relativa ch = ",ch}
],
resultadosResistenciaSuperficial=HoldComplete[
{"RESULTADOS "," RESISTENCIA SUPERFICIAL"},
{"Resistencia superficial sin corregir del piñón [ksi] = ",sfce},
{"Resistencia superficial ya corregida del piñón [ksi] = ",sigmaCperme},
{"Resistencia superficial sin corregir de la rueda [ksi] = ",sfcs},
{"Resistencia superficial ya corregida de la rueda [ksi] = ",sigmaCperms}
],
calculosEsfuerzos=HoldComplete[
kme=Piecewise[{
{1.6,#fe <= 2},
{1.7,2<#fe <= 6},
{1.8,6<#fe <= 9},
{1.9,9<#fe <= 20},
{2.0,20 < #fe}
},2.0];
kms=Piecewise[{
{1.6,#fs<= 2},
{1.7,2<#fs <= 6},
{1.8,6<#fs <= 9},
{1.9,9<#fs <= 20},
{2.0,20 < #fs}
},2.0];
kSe=Piecewise[{
{1.0,5<=#pd },
{1.05,4<=#pd<5 },
{1.15,3<=#pd <4},
{1.25,2<=#pd < 3},
{1.40, #pd<2}
},1.40];
kSs=Piecewise[{
{1.0,5<=#pd },
{1.05,4<=#pd<5 },
{1.15,3<=#pd <4},
{1.25,2<=#pd < 3},
{1.40, #pd<2}
},1.40];

bkv=0.25*((((12-#q))^2)^(1/3));
akv=50+56(1-bkv);
kv=(((((akv+(vt^(1/2)))/akv)))^bkv);

esfuerzoflexionE=((#fuerzat* #pd * #kA * kme)/(1000*#fe*#je * kv))*kSe * #kBe ; 
esfuerzoflexionS=((#fuerzat* #pd * #kA * kms)/(1000*#fs*#js * kv))*kSs * #kBs ;

factorseguridadflexionE=(sigmaperme/esfuerzoflexionE);
factorseguridadflexionS=(sigmaperms/esfuerzoflexionS);

esfuerzosuperfE=(cp/1000)*((((#fuerzat)/(#fe*#ie*de ))((#kA*kme*kSe*#cf )/kv))^(1/2));
esfuerzosuperfS=(cp/1000)*((((#fuerzat)/(#fs*ds ))((#kA*kms*kSs*#cf )/kv))^(1/2));

factorseguridadsuperfE=((((sigmaCperme/esfuerzosuperfE)))^2);
factorseguridadsuperfS=((((sigmaCperms/esfuerzosuperfS)))^2)
],
resultadosFactoresEsfuerzos=HoldComplete[
{"FACTORES "," ESFUERZOS FLEXIÓN Y SUPERFICAL"},
{"Fuerza tangencial en el diente [lb] = ",#fuerzat},
{"Paso diametral = ",#pd},
{"Ancho de cara del piñón fe = ",#fe},
{"Ancho de cara de la rueda fs = ",#fs},
{"Factor geométrico a la flexión del piñón je = ",#je},
{"Factor geométrico a la flexión de la rueda js = ",#js},
{"Factor de aplicación de carga = ",#kA},
{"Factor de distribución de carga del piñón kme = ",kme},
{"Factor de distribución de carga de la rueda kms = ",kms},
{"Factor dinámico = ",kv},
{"Factor de tamaño del diente del piñón kme = ",kSe},
{"Factor de tamaño del diente de la rueda kms = ",kSs},
{"Factor de espesor de aro del piñón kBe = ",#kBe},
{"Factor de espesor de aro de la rueda kBs = ",#kBs},
{"Factor geométrico superficial del piñón I = ",#ie},
{"Factor elástico cp [psi] = ",cp},
{"Factor de acabado = ",#cf}
]
},
FormFunction[
{{(* Pantalla input cinemática *)
{"we","Velocidad de entrada (piñón) [rpm]:"} -> <|"Interpreter"-> "Number",input->3000|>,
{"pd","Paso diametral [teeth/in]:"} -> <|"Interpreter"-> pasosDiametrales,input->48|>,
{"ne","Número de dientes del piñón:"} -> <|"Interpreter"-> "Integer",input->18|>,
{"wsdeseada","Velocidad de salida deseada [rpm]:"} -> <|"Interpreter"-> "Number",input->550|>
},
Function[{(* Pantalla output cinemática *)
{"dataCinematica",
Column[
Map[Row,Join[Apply[List,datosCinematica],{{" "},{"¿Volver a mostrar estos datos en la pantalla final?"}}]]
]} ->{"Si"->True,"No"->False},
{"mostrarCinematica",
ReleaseHold[calculosCinematica];
Column[
Map[Row,Join[Apply[List,resultadosCinematica],{{" "},{"¿Volver a mostrar estos resultados en la pantalla final?"}}]]
]} ->{"Si"->True,"No"->False} 
}],
Function[{ (* Pantalla input resistencias a la flexión y superficial *)
{"kr","Seleccione la confiabilidad:"}-> {"90%"->0.85,"99%"->1.00,"99.9%"->1.25},
{"grado1Q","Seleccione el tipo de engranaje:"}-> {"Grado 1"->True,"Grado 2"->False},
{"dure","Dureza a la masa del piñón [HB]:"} -> <|"Interpreter"-> Restricted["Integer",{180,400}],input->300|>,
{"durs","Dureza a la masa de la rueda [HB]:"} -> <|"Interpreter"-> Restricted["Integer",{180,400}],input->300|>
}],
Function[{ (* Pantalla input vida útil *)
{"ele",
grafele=cloudimport["gele"];
Column[{
grafele,
"Vida útil [hr]:"}]}-> <|"Interpreter"-> "Integer",input->20000|>
}],
Function[{ (* Pantalla output datos resistencia *)
{"dataResistencia",
Column[
Map[Row,Join[Apply[List,
datosResistencia],{{" "},{"¿Volver a mostrar estos datos en la pantalla final?"}}]]
]}-> {"Si"->True,"No"->False}
}],
Function[{ (* Pantalla output resistencias a la flexión *)
{"mostrarResistenciaFlexion",
ReleaseHold[calculosCinematica];
ReleaseHold[calculosResistenciaFlexion];
ReleaseHold[calculosResistenciaSuperficial];
Column[
Map[Row,Join[Apply[List,
Join[resultadosFactoresResistencaFlexion,
resultadosResistenciaFlexion]],{{" "},{"¿Volver a mostrar los factores y resultados de RESISTENCIA A LA FLEXIÓN en la pantalla final?"}}]]
]}-> {"Si"->True,"No"->False}
}] ,
Function[{ (* Pantalla output resistencia superficial *)
{"mostrarResistenciaSuperficial",
ReleaseHold[calculosCinematica];
ReleaseHold[calculosResistenciaFlexion];
ReleaseHold[calculosResistenciaSuperficial];
Column[
Map[Row,Join[Apply[List,
Join[resultadosFactoresResistencaSuperficial,
resultadosResistenciaSuperficial]],{{" "},{"¿Volver a mostrar los factores y resultados de RESISTENCIA SUPERFICIAL en la pantalla final?"}}]]
]}-> {"Si"->True,"No"->False}
}],
Function[{ (* Pantalla input ángulo *)
{"angulo","Seleccione el ángulo del diente: "}-> {"20 grados"->20,"25 grados"->25}
}],
Function[{ (* Pantalla input factor geométrico J *)
{"je",
grafj=Switch[#angulo,
20,cloudimport["ja20"],
_,cloudimport["ja25"]];
Column[{
grafj,
"Factor geométrico de resistencia a la flexión del piñón Je:"}]}-> <|"Interpreter"-> "Number",input->0.30|>,
{"js","Factor geométrico de resistencia a la flexión de la rueda Js:"}-> <|"Interpreter"-> "Number",input->0.30|>
}],
Function[{ (* Pantalla input factor aplicacion de carga Ka *)
{"kA",
grafka=cloudimport["gka"];
Column[{
grafka,
"Factor de aplicación de carga kA:"}]}-> <|"Interpreter"-> "Number",input->1.0|>
}],
Function[{ (* Pantalla input factor espesor de aro kB*)
{"kBe",
grafkb=cloudimport["gkb"];
tr=1.2*((1./(#pd))+(1.25/(#pd))+(0.25/(#pd)));
Column[{
grafkb,
"El espesor de aro mínimo para que kB=1 es "<>ToString[tr]<>" in.",
"Factor de espesor de aro del piñón kBe:"}]}-> <|"Interpreter"-> "Number",input->1.0|>,
{"kBs","Factor de espesor de aro de la rueda kBs:"}-> <|"Interpreter"-> "Number",input->1.0|>
}],
Function[{ (* Pantalla input esfuerzo a la flexión *)
{"fuerzat","Fuerza tangencial en el diente [lb] :"}-> <|"Interpreter"-> "Number",input->92.2|>,
{"fe","Ancho de cara del piñón [in]:"} -> <|"Interpreter"-> Restricted["Number",{(4./(#pd)),(16./(#pd))}],"Input"->(10.0/(#pd))|>, (* Input anterior si se queda en versión final *)
{"fs","Ancho de cara de la rueda [in]:"} -> <|"Interpreter"-> Restricted["Number",{(4./(#pd)),(16./(#pd))}],"Input"->(10.0/(#pd))|>, (* Input anterior si se queda en versión final *)
{"q","Indice de calidad del engranaje Q:"}-> <|"Interpreter"-> "Number",input->1.0|>
}],
Function[{ (* Pantalla input coeficiente elástico cp *)
{"pinionMaterial","Seleccione el material del piñón: "}-> 
{"Acero"->steel,
"Hierro maleable"->malleableIron,
"Hierro nodular"->nodularIron,
 "Hierro fundido"->castIron,
"Bronce al aluminio"->aluminumBronze,
"Bronce al estaño"->tinBronze
},
{"gearMaterial","Seleccione el material de la rueda: "}-> 
{"Acero"->steel,
"Hierro maleable"->malleableIron,
"Hierro nodular"->nodularIron,
 "Hierro fundido"->castIron,
"Bronce al aluminio"->aluminumBronze,
"Bronce al estaño"->tinBronze
}
}],
Function[{ (* Pantalla input factor geométrico superficial del piñón I *)
{"ie",
grafi=Switch[#angulo,
20,cloudimport["ia20"],
_,cloudimport["ia25"]];
Column[{
grafi,
"Factor geométrico superficial del piñón I:"}]}-> <|"Interpreter"-> "Number",input->0.1|>
}],
Function[{ (* Pantalla input factor acabado superficial *)
{"cf","Seleccione el acabado superficial: "}-> 
{"Rectificado o pulido"->0.9,
"Maquinado"->1.0,
"Acabados asperos"->1.2}
}],
Function[{ (* Pantalla output factores esfuerzos *)
{"mostrarFactoresEsfuerzos",
ReleaseHold[calculosCinematica];
ReleaseHold[calculosResistenciaFlexion];
ReleaseHold[calculosResistenciaSuperficial];
cp=factorElastico[[#pinionMaterial,#gearMaterial]];
ReleaseHold[calculosEsfuerzos];
Column[
Map[Row,Join[Apply[List,resultadosFactoresEsfuerzos],{{" "},{"¿Volver a mostrar esta información en la pantalla final?"}}]]
]}-> {"Si"->True,"No"->False}
}]
}, 
Function[ (* pantalla final de resultados *)
ReleaseHold[calculosCinematica];
ReleaseHold[calculosResistenciaFlexion];
ReleaseHold[calculosResistenciaSuperficial];
cp=factorElastico[[#pinionMaterial,#gearMaterial]];
ReleaseHold[calculosEsfuerzos];
resultados={{Item[Style["Factor seguridad a la flexión de la rueda ",FontWeight->Bold],Background->LightBlue],
Item[Style[N[factorseguridadflexionS],FontWeight->Bold],Background->LightBlue]
},
{Item[Style["Factor seguridad a la flexión del piñón ",FontWeight->Bold],Background->LightBlue],
Item[Style[N[factorseguridadflexionE],FontWeight->Bold],Background->LightBlue]
},
{Item[Style["Factor seguridad superficial de la rueda ",FontWeight->Bold],Background->LightBlue],
Item[Style[N[factorseguridadsuperfS],FontWeight->Bold],Background->LightBlue]
},
{Item[Style["Factor seguridad superficial del piñón ",FontWeight->Bold],Background->LightBlue],
Item[Style[N[factorseguridadsuperfE],FontWeight->Bold],Background->LightBlue]
}
};
If[#dataCinematica,resultados=Join[resultados,Apply[List,datosCinematica]]];
If[#mostrarCinematica,resultados=Join[resultados,Apply[List,resultadosCinematica]]];
If[#dataResistencia,resultados=Join[resultados,Apply[List,datosResistencia]]];
If[#mostrarResistenciaFlexion,
resultados=Join[resultados,Apply[List,resultadosFactoresResistencaFlexion]];
resultados=Join[resultados,Apply[List,resultadosResistenciaFlexion]]
];
If[#mostrarResistenciaSuperficial,
resultados=Join[resultados,Apply[List,resultadosFactoresResistencaSuperficial]];
resultados=Join[resultados,Apply[List,resultadosResistenciaSuperficial]]
];
If[#mostrarFactoresEsfuerzos,
resultados=Join[resultados,Apply[List,resultadosFactoresEsfuerzos]]
];
Style[Grid[resultados,
Dividers->All,Alignment->{{Right,Left}}],FontFamily->"Verdana",Larger]
],
"GIF",
(* Para editar me funcionó mejor FormTheme->"Black"
Para publicar me funcionó mejor sin FormTheme 
FormTheme->"Black",*)
AppearanceRules-> 
<|"Title" -> "Transmisión de engranes", 
"ItemLayout" -> "Vertical"|>
]];

Our Gear Design App

We used the Wolfram Language to develop calculation apps for design of gears. Since the apps we created are deployed in the Wolfram Cloud, students only need an internet browser in their smartphone, tablet or computer. 
Link to our gear design app: https://www.wolframcloud.com/objects/bcadf128-8955-4f19-8cdb-340e99ebd250

Belt Drive Design App

We used the Wolfram Language to develop calculation apps for the design of belt drives, specifically designed for the lectures of the course.
Link to our belt drive design app: https://www.wolframcloud.com/objects/1503dc79-925c-459f-9392-50627f6dcbe8

Screenshot of one of the multiple pages of our belt drive design app:

Instructor and Students Feedback

The instructor was surprised that the students were able to generate several different successful designs in their homework and exam assignments. Students feedback was also positive

Our First Approach to Adaptive Learning

Adaptive content: When a student gets something wrong, the tool responds with feedback that’s based on that student’s specific mistake. The tool provides materials to review the relevant skill. It also breaks down skills into smaller pieces for students without changing the overall sequence of skills.
Adaptive assessment: These tools change the questions a student sees based on their response to the previous question. The difficulty of questions will increase as a student answers them accurately. If the student struggles, the questions will get easier.

FormPage for Multiple-Choice Quiz

More information in our post in http://community.wolfram.com/groups/-/m/t/1360762?p_p_auth=AKczR4tg

Next code demonstrates the CloudDeploy of a FormPage for an online quiz.  It can be taken by different students at the same time, provided that each one enters a different student id number. Notice that student’s progress will be stored in the Wolfram Cloud (CloudPut) using string labels (uri) that include the student id number; an improvement to this approach would be to use the command URLBuild[{studentid,info}] as a second argument of CloudPut[], so that each student has a folder (directory) in the Wolfram Cloud. In this source code, the correct answer to each question is always the first choice in the list, however different students will see the multiple choice answers in different order (RandomPermutation) when the quiz is online:

fopa=
With[{
numchoices=5,
idini="ID000000",
letrasporlinea=30,
estilobase={Large, FontFamily->"Calibri"},
estiloincisos={FontColor->},
estilotituloprincipal={FontWeight->Bold},
estiloautores={FontSize->Medium},
opcionestitulos=Sequence[
Medium,FontColor-> ,Background->],
opcionesplot=Sequence[
ImageSize-> Medium,
FrameTicksStyle->Italic,
PlotStyle->Map[Function[{Thick,#}],{,,,,,,}],
PlotTheme->"FullAxesGrid"]
},
listaDeSecciones={
(*In this source code the correct answer to each question is always the first choice. Options will be shown in a random oder to each student, see the RandomPermutation command at the end of this code *)
{"Integration by parts",
{Column[{
InsertLinebreaks["Select the evaluation of the integral:",letrasporlinea],
TraditionalForm[HoldForm[Integrate[x*(E^(-x)),x]]]
}],
{TraditionalForm[Expand[Integrate[x*(E^(-x)),x]]],
TraditionalForm[Expand[Integrate[x,x]*Integrate[(E^(-x)),x]]],
TraditionalForm[Expand[Integrate[x*(E^(-(x^2))),x]]],
TraditionalForm[Expand[Integrate[(x^2)*(E^x)/2,x]]],
TraditionalForm[Expand[Integrate[(x^2)*(E^(-x)),x]]]}}},
{"Function tanslations",
{Column[{
Plot[Exp[-(((x-3))^2)],{x,0,6},
PlotLabel->Style["Graph of f(x)",Large],
PlotStyle->Black,
opcionesplot],
InsertLinebreaks["Select the graph of f(x-1)",letrasporlinea]}],
{Plot[Exp[-((((x-1)-3))^2)],{x,0,2Pi},opcionesplot],
Plot[Exp[-((((x-2)-3))^2)],{x,0,2Pi},opcionesplot],
Plot[Exp[-((((x+1)-3))^2)],{x,0,2Pi},opcionesplot],
Plot[Exp[-((((x+2)-3))^2)],{x,0,2Pi},opcionesplot],
Plot[Exp[-((((x+3)-3))^2)],{x,0,2Pi},opcionesplot]}}},
{"Graphs of functions",
{InsertLinebreaks["Select the graph of sin(x)",letrasporlinea],
{Plot[Sin[x],{x,0,2Pi},opcionesplot],
Plot[Cos[x],{x,0,2Pi},opcionesplot],
Plot[Log[x+1],{x,0,2Pi},opcionesplot],
Plot[Exp[-x],{x,0,2Pi},opcionesplot],
Plot[Exp[-(x^2)/6],{x,0,2Pi},opcionesplot]}}},
{"Graphs of functions",
{InsertLinebreaks["Select the graph of sin(x) and cos(x)",letrasporlinea],
{Plot[{Sin[x],Cos[x]},{x,0,2Pi},opcionesplot],
Plot[{Sin[x],-Sin[x]},{x,0,2Pi},opcionesplot],
Plot[{Cos[x],-Cos[x]},{x,0,2Pi},opcionesplot],
Plot[{-Sin[x],-Cos[x]},{x,0,2Pi},opcionesplot],
Plot[{-Sin[x],Cos[x]},{x,0,2Pi},opcionesplot]}}},
{"Integration by parts",
{Column[{
InsertLinebreaks["Select the evaluation of the integral:",letrasporlinea],
TraditionalForm[HoldForm[Integrate[x*Sin[x],x]]]
}],
{TraditionalForm[Expand[Integrate[x*Sin[x],x]]],
TraditionalForm[Expand[Integrate[x,x]*Integrate[Sin[x],x]]],
TraditionalForm[Expand[Integrate[x*Sin[(x^2)],x]]],
TraditionalForm[Expand[Integrate[(x^2)*Sin[x]/2,x]]],
TraditionalForm[Expand[Integrate[(x^2)*Cos[x],x]]]}}}
};
FormPage[{
{"choice","Options:"}-><|"Interpreter"->Join[MapIndexed[Function[FromCharacterCode[#2[[1]]+96]->#1],Range[numchoices]],{"update"->-1,"clear quiz history"->-2}], "Control" -> RadioButtonBar,"Input" -> -1|>,
{"matricula","Student id number:"}-><|"Interpreter" -> "String","Input" -> idini,"Help"->"If you change the student id number you must select UPDATE" |>
},
Function[
ExportForm[
With[{id=ToUpperCase[StringReplace[RemoveDiacritics[#matricula,Language->"English"],Whitespace->""]]},
With[
{respcorrecta=StringJoin[id,"respcorrecta"],
fallos=StringJoin[id,"fallos"],
seccion=StringJoin[id,"seccion"]
},
Column[Flatten[{

Style["Online Quiz",estilotituloprincipal],
Style["Jose Luis Gomez-Munoz",estiloautores],

(* Message *)
Style[Framed["Messages",
FrameStyle->Opacity[0]],opcionestitulos],
Which[ (* update student progress and write messages *)
#matricula==idini,
InsertLinebreaks["Select update, write your student ID number and press the button",letrasporlinea],

TrueQ[Or[
#choice==-2,
FailureQ[Quiet[CloudGet[respcorrecta]]],
FailureQ[Quiet[CloudGet[fallos]]],
FailureQ[Quiet[CloudGet[seccion]]]]],
(* reset student progress *)
CloudPut[0,respcorrecta];
CloudPut[0,fallos];
CloudPut[1,seccion];
InsertLinebreaks["The quiz history of "<>ToString[id]<>" was initialized",letrasporlinea],

TrueQ[And[#choice>0,
CloudGet[seccion]<=Length[listaDeSecciones],
#choice==CloudGet[respcorrecta]
]], (* correct answer: update student progress *)
With[{valseccion=CloudGet[seccion]},
CloudPut[1+valseccion,seccion]; 
Column[{"Your answer was correct",ToString[id]}]
],

TrueQ[And[#choice>0,
CloudGet[seccion]<=Length[listaDeSecciones],
#choice≠ CloudGet[respcorrecta]
]], (* incorrect answer: update student progress *)
With[{valfallos=CloudGet[fallos],valseccion=CloudGet[seccion]},
CloudPut[1+valfallos,fallos]; 
CloudPut[1+valseccion,seccion];
Column[{"Your answer was incorrect",ToString[id]}]
],

True, (*default: do not change student progress *)
InsertLinebreaks["Updated "<>DateString[]<>" "<>ToString[id],letrasporlinea]
],

With[{ (* CloudGet student progress *)
valseccion=CloudGet[seccion],
valfallos=CloudGet[fallos]},
Which[(* Next question, or end-of-quiz message *)
#matricula==idini,Nothing,

TrueQ[valseccion>Length[listaDeSecciones]],
(* end-of-quiz message *)
{InsertLinebreaks["You have finished this quiz. If you want to do it again, select clear history",letrasporlinea],
InsertLinebreaks["Errors: "<>ToString[valfallos],letrasporlinea]},

True,
{
Style[Framed["History", 
FrameStyle->Opacity[0]],opcionestitulos],
InsertLinebreaks["Errors: "<>ToString[valfallos],letrasporlinea],
InsertLinebreaks["Question: "<>ToString[valseccion]<>" of "<>ToString[Length[listaDeSecciones]],letrasporlinea],
Style[Framed[ToString[valseccion]<>". "<>listaDeSecciones[[valseccion,1]], (*Topic*)
FrameStyle->Opacity[0]],opcionestitulos], 
listaDeSecciones[[valseccion,2,1]], (*Question*)
(*Multiple choice options, in random order:*)
permutacion=RandomPermutation[numchoices];
opciones=Take[Permute[listaDeSecciones[[valseccion,2,2]],permutacion],numchoices];
correcta=PermutationReplace[1,permutacion];
CloudPut[correcta,respcorrecta];
Column[MapIndexed[Function[Row[{Style[FromCharacterCode[#2[[1]]+96]<>") ",estiloincisos],#1}]],opciones],Dividers->All]}
]]
}],BaseStyle->estilobase]]],
"GIF"]
],"ResultAbove"
]];

The quiz, which is a FormPage stored in the variable “fopa”, is published using CloudDeploy, and it is open in the internet browser using SystemOpen. Furthermore, URLShorten generates a small hyperlink to the quiz that can be shared with students. Each student must provide a different student id number to the quiz. It looks very nice in my iPhone, but students can also work in a computer or tablet:

$Permissions = "Public";
cdeploy=CloudDeploy[fopa]; (* publica la app *)
SystemOpen[cdeploy]; (* Abre la app en el navegador para probarla *)
URLShorten[cdeploy]

Our Adaptive Learning App

Link to our adaptive learning app (BETA version) https://wolfr.am/vZpNNKKH

Conclusions

We used the WL commands FormFunction and FormPage in order to implement Cloud apps for mobile learning. We avoided the use of the more advanced AskFunction[] family of commands, because they were marked as “Experimental” at the moment.

Our apps for learning Mechanical Engineering Design had a positive effect, with students being able to find several successful designs, when traditionally they struggled to find only one.

It was also possible to implement an adaptive learning framework using the FormPage commands.

During the process, we were surprised to find very few examples at the Wolfram Community and Mathematica Stack Exchange, we did contribute with our own findings.

Future Work: Machine Learning

Adaptive sequence: These tools are continuously collecting and analyzing student data to automatically change what a student sees next; from the order of skills a student works on, to the type of content a student receives. A solid adaptive learning system uses data collection and applies it to cognitive sciences.

Therefore we want to increase the complexity of our adaptive apps, taking advantage of the Machine Learning capabilities of the Wolfram Language