日出前行走时,我会携带两个手电筒;每个都有大约 5 小时的电池,步行持续大约一个小时。 我尝试使用同一个手电筒,直到它耗尽为止,大约每隔五天我就会在步行过程中切换灯。 旧灯开始充电,我试着记住第二天要使用哪一盏。 这个计划实际上效果很好,即使我第二天忘记关灯。
但如果我每天随机选择一个手电筒会发生什么? 我预计多久会有两次 15 分钟的灯光,而后半段步行没有灯光? 所附的 JSL 是一个试图回答这个问题的模拟;首先我们来看看答案:
The red dots below 0 show where the two flashlights both failed, and how badlyThe red dots below 0 show where the two flashlights both failed, and how badlyThe red dots below 0 show where the two flashlights both failed, and how badlyThe red dots below 0 show where the two flashlights both failed, and how badly0 以下的红点显示两个手电筒均出现故障的位置以及严重程度
标题栏显示此模拟适用于两个灯,一个具有 4 小时以上的电池,另一个具有 5 小时以上的电池。 大多数日子里,这对灯还剩下几个小时。 JSL 以用户函数开始,制作表,因为下面将重用该代码来制作另一个图表。
maketable = function({capacity},// an array with the battery capacities expressed as time
{dt,charge,ontime,today,remainingWalkTime,message,light,usage,deadBatteries},//locals
第一组 { 大括号 } 保存函数的参数,容量,这将是一个小矩阵,其中包含每个手电筒的小时容量。 第二组保存函数的局部变量;如果调用该函数的 JSL 碰巧使用相同的变量名,则该函数不会更改调用者的变量。
charge = capacity; // charge[] tracks the remaining charge
// expression that computes random on time for flashlight
ontime = Expr(
As Date( In Hours( Min( 2, Max( .5, Random Normal( 1, .1 ) ) ) ) ) // usually about 1-hour walk
);
// table to collect results
dt = New Table( "Light Schedule",
Add Rows( 1 ),
New Column( "day", Numeric, "Ordinal", Format( "m/d/y", 10 ), Input Format( "m/d/y" ), Set Values( {1feb2020} ) ),
New Column( "hoursAvailable", Numeric, "Continuous", Format( "hr:m", 12 ), Set Values( {sum(charge)} ) ),
New Column( "notes", Character, Set Values( {"Two fully charged lights"} ), Set Display Width( 500 ) ),
Set Label Columns( :notes )
);
上面,电池通过复制最初充满电容量进入收费。 然后将表达式分配给准时,它代表我步行时间的分布……一个小时,或多或少。 最后, dt设置为一个新的数据表,该函数最终将返回给调用者。 该表包括一个日计数器栏、一栏显示步行后手电筒可用的时间以及一栏,其中包含计算机生成的有关所发生事件的注释。
// run the simulation for about 10 years
For( today = 2feb2020, today <= 2dec2029, today += In Days( 1 ),
remainingWalkTime = ontime; // gets a new random duration for the length of an early morning walk
message = Format( remainingWalkTime, "hr:m" ) || " --"; // total walk duration
While( remainingWalkTime > 0 & Sum( charge ) > 0, // also give up when there are no charged lights remaining
// choosing randomly might be too slow if there are many uncharged lights, but there are only 2 or 3...
light = Random Integer( N Rows( capacity ) ); // choose from available lights
If( charge[light] > 0, // if it is a charged light, use it
usage = Min( remainingWalkTime, charge[light] ); // only take what is needed, up to available charge
remainingWalkTime -= usage;
charge[light] -= usage;
message ||= " light " || Char( light ) || "(" || Format( usage, "hr:m" ) || ")";
);
);
上面的模拟循环的第一部分循环从 2020 年到 2030 年。 (除了标记图表之外,日期并未真正使用;持续时间大约为 10*365 天......稍后您将在表中看到大约 3500 行。)剩余步行时间设置为大约一个小时并成为初始值信息,其中将有更多文本成为当天的笔记。 For 循环内部有一个 While 循环,它会随机选择手电筒作为步行的一部分。 For 循环继续选择手电筒,直到步行结束或没有剩余电量的手电筒。 所选的灯光要么已用完,要么已足以完成步行。 然后消息会更新,说明使用了哪种灯以及使用了多长时间。
// update data table
dt << addrows( 1 );
If( remainingWalkTime > 0,
Row State() = Color State( "red" );
// the next line makes the charge go below zero on the first battery;
// that makes column 1 have negative numbers, and makes the graph do
// a better job of visualizing how long the walk in the dark lasted.
message ||= " walked in the dark(" || Format( remainingWalkTime, "hr:m" ) || ")";
charge[1] -= remainingWalkTime;
remainingWalkTime = 0; // maybe the sun is coming up
);
dt:day = today;
dt:hoursAvailable = sum(charge);
dt:notes = message;
// recharge any dead batteries
deadBatteries = Loc( charge <= 0 );
charge[deadBatteries] = capacity[deadBatteries];
message ||= " recharged" || Char( deadBatteries );
);
上面模拟的最后一部分向表中添加一行来表示今天,如果有则将其染成红色剩余步行时间剩下的,并设置天,可用时间, 和笔记对于新行。 最后,对电池进行充电;这洛克函数查找电荷矩阵中低于零的索引或位置。 然后,仅对于这些位置,电荷值被重置为容量值。
dt; // return value
);
这样(上面)就结束了这个函数;最后一条语句的值返回给调用者。 dt本身就是一个非常简单的语句,其计算结果为... dt 。 您还可以使用返回函数:返回(dt);函数中的任意位置。
// the following line determines how many flashlights are in the simulation
capacity = matrix({In Hours( 4.3 ),In Hours( 5.6 )}); // one battery is much older, less capacity.
dt = maketable(capacity);
// graph the results
dt << Graph Builder(
Size( 1388, 500 ),
Show Control Panel( 0 ),
Show Legend( 0 ),
Variables( X( :day ), Y( :hoursAvailable ) ),
Elements( Points( X, Y, Legend( 9 ) ) ),
SendToReport(
Dispatch( {}, "Graph Builder", OutlineBox, {Set Title( title="h:m battery capacities: "; for(icap=1,icap<=nRows(capacity),icap+=1,title||=format(capacity[icap],"hr:m")||" ";); title )} ),
Dispatch({},"day",ScaleBox, {Format( "m/d/y", 10 ), Min( 3660681600 ), Max( 3976300800 ), Interval( "Year" ), Inc( 2 ), Minor Ticks( 0 )}),
Dispatch( {}, "graph title", TextEditBox, {Set Text( "Available flashlight time at end of walk" )} ),
Dispatch( {}, "Y title", TextEditBox, {Set Text( "Hours:Minutes" )} ),
Dispatch( {}, "400", LegendBox, {Set Title( "" )} )
)
);
最后,上面调用制作表与一个容量定义了两个电池。 这dt = 左边是全局变量,赋值为 = 右边函数的结果。 这dt在函数中是在函数开头的局部列表中,并且不会改变全局dt 。 创建数据表后,图形生成器就可以制作图形(如上所述)。不要从头开始编写图形生成器脚本! 使用 gui 获取所需内容,然后保存脚本。 我编辑了这个以添加带有电池容量的自定义大纲标题。
图形生成器(上面)使用的表格如下所示:
Output from maketable() function showing (negative) hours and notesOutput from maketable() function showing (negative) hours and notesOutput from maketable() function showing (negative) hours and notesOutput from maketable() function showing (negative) hours and notesmaketable() 函数的输出显示(负)时间和注释
由于 maketable() 采用电池容量参数,因此我可以多次运行它。 我并不打算携带额外的手电筒,但我确实想知道多少个手电筒、多大容量的手电筒最能解决问题。 下面是一些从循环中调用 maketable 的 JSL,并从 maketable 返回的表中收集另一个答案表:
// make a table-of-tables; use from 1 to 10 flashlights with batteries from 1/16 to 32 hours
dtreport = New Table( "lights",
New Column( "nBats", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) ),
New Column( "batSize", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) ),
New Column( "nfailures", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) ),
New Column( "darkHours", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) )
);
数据报告是将保存聚合答案的数据表。
For( nLights = 1, nLights <= 10, nLights += 1,
For( batLife = 1 / 16, batLife <= 32, batLife *= 2,
dt = maketable( Repeat( inhours(batLife), nLights ) );
上面,一对循环将运行 100 种组合,从而形成一个新的dt每一次。
// count the failures and duration of failures
remaining = dt[0, "hoursAvailable"];
failures = Loc( remaining < 0 );
nfail = N Rows( failures );
darkSum = As Date( -Sum( remaining[failures] ) );
dtreport<<ADDROWS></ADDROWS>
多于,其余的抓住了可用时间列作为矩阵,这样我可以使用洛克()再次定位失败。我尝试了失败次数和失败总时间,并在图表中得到了类似的结果。 添加一行到数据报告表并填写。
Close( dt, "nosave" );
)
);
当不再需要时关闭循环中的 dt 表,这样就不会打开 100 个窗口。
现在刚刚创建的表如下所示:
.0625 hour will probably never be enough, even with 10 flashlights.0625 hour will probably never be enough, even with 10 flashlights.0625 hour will probably never be enough, even with 10 flashlights.0625 hour will probably never be enough, even with 10 flashlights.0625 小时可能永远不够,即使有 10 个手电筒
我发现更多的灯和更好的电池会有所帮助。 JSL(从图形生成器的 GUI 中保存)看起来像这样
dtreport<<GRAPH builder=""></GRAPH>
以及结果图:
More flashlights or more batteries keeps me out of the darkMore flashlights or more batteries keeps me out of the darkMore flashlights or more batteries keeps me out of the darkMore flashlights or more batteries keeps me out of the dark更多手电筒或更多电池让我远离黑暗
等高线图显示了 100 次模拟的结果;看来三个具有 17 小时容量的手电筒或四个具有 8 小时容量的手电筒就可以解决问题。 但如果只有两个手电筒,随机选择一盏灯,并且只有在没电时才充电,可能没有足够的容量让灯保持亮着。