NetLogo: reading data from input files with variable numbers of lines - file-io

I've been running game theory simulations in NetLogo and I now have lots data files, containing cross-tabulated data - each column store a value of a different variable, and there are c. 1000 rows containing the data. I'm trying to write a programme that will take these files and calculate the mean value for each column.
I have a programme that works as long as there is a constant number of data rows in each file. The programme uses a loop of file-read commands to calculate running totals, which are then divided by the nuber of rows read once all of the rows have been read.
However, my real data files have variable numbers of rows. I have been trying to modify my code using file-at-end? to get it to exit the running total loop after the last line, but I haven't been able to find any way of employing it that works - I just get an error saying that the file is at an end.
Please could someone suggest a way of dealing with this? I've pasted the working code below.
--
globals [target-file-name
current-tally-file
lines-read
coops-fixed-run cheats-fixed-run either-fixed-run coop-freq-min-run
coop-freq-max-run coop-freq-mean-run ticks-run
num-lines
]
to setup
set target-file-name user-input "Type a name for the target file"
file-open target-file-name
file-print("TallyFile Reps pFixCoop pFixCheat pFixEither MeanMinCoop MeanMaxCoop MeanMeanCoop")
file-close
set num-lines read-from-string user-input "How many lines in the file to be processed?"
end
to go
set current-tally-file user-file
file-open current-tally-file
set lines-read 0
while [lines-read < num-lines][
let in1 file-read set coops-fixed-run (coops-fixed-run + in1)
let in2 file-read set cheats-fixed-run (cheats-fixed-run + in2)
let in3 file-read set either-fixed-run (either-fixed-run + in3)
let in4 file-read set coop-freq-min-run (coop-freq-min-run + in4)
let in5 file-read set coop-freq-max-run (coop-freq-max-run + in5)
let in6 file-read set coop-freq-mean-run (coop-freq-mean-run + in6)
let in7 file-read set ticks-run (ticks-run + in7)
set lines-read (lines-read + 1)
]
stop-and-clear
end
to stop-and-clear
let pfixcoop (coops-fixed-run / lines-read)
let pfixcheat (cheats-fixed-run / lines-read)
let pfixeither (either-fixed-run / lines-read)
let mean-of-mins (coop-freq-min-run / lines-read)
let mean-of-maxs (coop-freq-max-run / lines-read)
let mean-of-means (coop-freq-mean-run / lines-read)
let mean-of-ticks (ticks-run / lines-read)
file-open target-file-name
file-print (word current-tally-file " " lines-read " " pfixcoop " " pfixcheat "
" pfixeither " " mean-of-mins " " mean-of-maxs " " mean-of-means " "
mean-of-ticks)
file-close
set coops-fixed-run 0
set cheats-fixed-run 0
set either-fixed-run 0
set coop-freq-min-run 0
set coop-freq-max-run 0
set coop-freq-mean-run 0
set ticks-run 0
set lines-read 0
stop
end

A method to read all lines from a file looks like:
to read-file [filename]
file-open filename
while [not file-at-end?][
;read one line
let in1 file-read
let in2 file-read
;and so one, at the end you will probably want to put these into some global variable
set global-in1 fput in1 global-in1
]
file-close filename
end
This assumes that all rows have exactly the name number of data items, and you know what that number is. Otherwise just use file-read-line instead of file-read

Related

Printing input statements to a specific row(?)

Doing a project for a qbasic class, and i need the 1st row to ask for input i.e. " Enter projected depletion rate: ", after doing that it will run a loop under it, wherein i need it to print another input statement on that same 1st row, " Enter another projected depletion rate or 0 to quit :" the issue i'm having is that if i use LOCATE it will print the next results of the loop directly under that statement when id like it to print below the last results in the list, at the lowest unused space, and it doesn't clear the top row of old text. I know part of it is that the LOCATE is getting repeated because of the loop but i'm genuinely stuck. sorry for format i'm new :)
CLS
DIM percent AS DOUBLE
DIM ozLevel AS DOUBLE
DIM counter AS INTEGER
DIM change AS DOUBLE
INPUT "enter a projected depletion rate, or 0 to quit: ", percent
PRINT
PRINT TAB(2); "Loss"; TAB(17); "Final Ozone"
PRINT TAB(2); "Rate"; TAB(10); "Years"; TAB(17); "Concentration"
change = (percent / 100)
DO WHILE percent <> 0
counter = 0
ozLevel = 450
DO UNTIL ozLevel < 200
counter = counter + 1
ozLevel = ozLevel - (ozLevel * change)
LOOP
PRINT USING "##.##%"; TAB(2); percent;
PRINT TAB(10); counter;
PRINT USING "###.##"; TAB(17); ozLevel;
LOCATE 1, 1
INPUT "enter new projection: ", percent
change = (percent / 100)
LOOP
LOCATE 1, 35
PRINT "DONE"
END
QBasic has the CRSLIN function that tells you where the cursor is.
Make sure that printing the 3rd result does a carriage return and linefeed. Just remove the ;
Now store the index to the next available row in a suitable variable like TableRow.
Input as before on the 1st row of the screen.
Position the cursor on the next available row using this variable after each following input.
...
PRINT USING "###.##"; TAB(17); ozLevel
tablerow = CRSLIN
LOCATE 1, 1
INPUT "enter new projection: ", percent
change = (percent / 100)
LOCATE tablerow, 1
LOOP
...

VBA returning different results between setting breakpoint and running without

I am writing a module to fix overlapping dataLabels in a chart in Excel. Since it is to be used in a macro that loops through 200 files, updating the numbers for the chart each time, it has to be done via VBA.
When I am testing it, if I simply call the "fixPieLabels" sub from my testChart sub, it gives the desired results. If I have a breakpoint on the line that calls "fixPieLabels", as well as on the "next x" line (so I can check each iteration) from the testAll sub that loops through different sets of values, it gives the desired results. If I only have a breakpoint on the "next x" line after the fixPieLabels call, it gives wrong results.
I thought the chart wasn't updating in time for the "fixPieLabels" sub, but I added some debug.print statements to find out where it was off...and I have the same values for all pertinent variables between the "off" runs and the "correct" runs, including the same section of the if/else condition being run, but somehow it is calculating differently.
For example, here is the debug printout of a wrong test:
Fixed Income Investments
height: 25.5312598425197
percSide: 0.45 gT: 82.5076377952756 gH: 153.454803149606
40%-<60%
postTop: 153.09811023622
And the correct test:
Fixed Income Investments
height: 25.5312598425197
percSide: 0.45 gT: 82.5076377952756 gH: 153.454803149606
40%-<60%
postTop: 138.796692913386
Here is the calculation it is running:
currLabel.Top = gT - currLabel.Height / 2 + percSide * gH
As you see, all the variables are the same, but in the "wrong" run, the currLabel.Top after running the calculation ("postTop") is mathematically incorrect. And in other tests, I had it print the currLabel.Top before running the calculation, and it is changing, so the problem is not that it simply hasn't assigned the new top.
Here are the subs calling the "fixPieLabels" sub:
Private Sub testChart()
fixPieLabels ActiveSheet.ChartObjects(1)
End Sub
Private Sub testAll()
Dim testArea As range
Dim x As Integer
With ActiveSheet
Set testArea = range("D1").CurrentRegion
For x = 1 To testArea.Columns.Count
.range("ChartNum").Value = testArea.Columns(x).Value
.ChartObjects(1).Chart.Refresh
fixPieLabels .ChartObjects(1)
Next x
End With
End Sub
Anyone know why this is happening or how to fix it?? Why does a breakpoint cause it to calculate correctly?
EDIT:
I tried to debug.print the calculation, and it is correct, so I tried assigning the calculation to a variable newTop and then setting the dataLabel top property to newTop...and it still has the wrong value! (And yes, the one I used this time is in a different bracket, so the calculation is different...
(totSoFar is simply a total of the values of the data points before this one, thisPerc is the value of this data point divided by totSoFar)
percOfSide = (totSoFar + thisPerc / 2) * 2
Debug.Print currLabel.Formula & " height: " & currLabel.Height
Debug.Print "percSide: " & percOfSide & " gT: " & grphTop & " gH: " & grphHeight
.....
ElseIf percOfSide > 0.75 Then
Debug.Print ">75%-90%"
Debug.Print "Answer: " & grphTop + percOfSide * grphHeight + currLabel.Height / 2
newTop = grphTop + percOfSide * grphHeight + currLabel.Height / 2
end if
Debug.Print newTop
currLabel.Top = newTop
Debug.Print "postTop: " & currLabel.Top & " preLeft: " & currLabel.Left
And the printout:
Alternative Investments
2.000% height: 28.8554330708661
percSide: 0.88 gT: 82.5076377952756 gH: 153.454803149606
>75%-90%
Answer: 231.975580370633
231.975580370633
postTop: 244.684251968504 preLeft: 278.302362204724
My workaround right now is to simply set currLabel.top = newTop a second time immediately after, which works so far. Hopefully it will also work on my client's computer!

Generic Way to Determine the Maximum Allowed Length a of String

Take a look at this property(Given you have a table on the first worksheet):
Application.Sheets(1).ListObjects(1).name
How many characters can this property contain? Well, after testing out a few strings I've come to the conclusion that its 255, any string with more than 255 characters causes an error to be thrown:
Run-Time Error 5 - Invalid procedure call or arguement
Take a look at this property:
Application.Sheets(1).ListObjects(1).Summary
How many characters can this property contain? Again, test several strings and you'll come out with a number that's around 50,000, You set it any higher and you get the same error, except in this case excel will sometimes crash or spit out a different error(after multiple attempts):
Dim i As Integer
Dim a As String
For i = 1 To 5001
a = a & "abcdefghih"
Next i
Application.Sheets(1).ListObjects(1).Summary = a
Method "Summary" of object 'ListObject' failed
This sort of "hidden" character limit comes up all over the place(here, here, less specifically here, and so classically here), and it doesn't seem like they're documented anywhere, for example take a look at the page for ListObject.Name, its not noted how many characters you can store in that variable...
So is there a better way to determine this? Are the strings you are setting in properties being stored in a fixed length string somewhere that can be accessed to determine what their maximum length is, or is there some other form of documentation that can be leveraged in order to obtain this information?
It strikes me as odd these character limits that are set on most strings within standard VBA objects, I wonder what their purpose is, why the designers choose to limit "ListObjects.name" to 255 characters and whether that was an arbitrary default limit or whether that was a conscious decision that was made. I believe that the standard string length is this, I wonder why the deviation from this standard.
To summarize the points I've made above and to condense this question into one sentence:
Is there a generic way to determine the maximum length of a string that can be set within an object's property, without first testing that string's property by giving it another value and ignoring errors/checking for character truncation?
First of all, if your intention is to store meta information about objects, you could maybe make use of CustomDocumentProperties. You can find examples on their usage here and here and some nice wrappers by Chip Pearson here.
Since they are still very limited (255 chars) in length (thanks for pointing that out!), the best solution might be to use CustomXMLParts like described here. The hard part would then be building correct XML using VBA, but maybe not impossible, if you add a reference to Microsoft XML.
But to provide some help with your question concerning maximum lengths for string properties, too, here is a test setup you can use to (relatively) quickly find these limits for arbitrary properties.
Just replace the ActiveWorkbook.Sheets(1).Name on line 19 with the property you want to test and run TestMaxStringLengthOfProperty():
Option Explicit
Const PRINT_STEPS = True ' If True, calculation steps will be written to Debug.Print
Private Function LengthWorks(ByVal iLengthToTest As Long) As Boolean
Dim testString As String
testString = String(iLengthToTest, "#") ' Build string with desired length
' Note: The String() method failed for different maximum string lengths possibly
' depending on available memory or other factors. You can test the current
' limit for your setup by putting the string assignment in the test space.
' In my tests I found maximum values around 1073311725 to still work.
On Error Resume Next
' ---------------------------------------------------------------------------------
' Start of the Test Space - put the method/property you want to test below here
ActiveWorkbook.Sheets(1).Name = testString
' End of the Test Space - put the method/property you want to test above here
' ---------------------------------------------------------------------------------
LengthWorks = Err.Number = 0
On Error GoTo 0
End Function
Private Sub TestMaxStringLengthOfProperty()
Const MAX_LENGTH As Long = 1000000000 ' Default: 1000000000
Const MAXIMUM_STEPS = 100 ' Exit loop after this many tries, at most
' Initialize variables for check loop
Dim currentLength As Long
Dim lowerBoundary As Long: lowerBoundary = 0
Dim upperBoundary As Long: upperBoundary = MAX_LENGTH
Dim currentStep As Long: currentStep = 0
While True ' Infinite loop, will exit sub directly
currentStep = currentStep + 1
If currentStep > MAXIMUM_STEPS Then
Debug.Print "Exiting because maximum number of steps (" & _
CStr(MAXIMUM_STEPS) & _
") was reached. Last working length was: " & _
CStr(lowerBoundary)
Exit Sub
End If
' Test the upper boundary first, if this succeeds we don't need to continue search
If LengthWorks(upperBoundary) Then
' We have a winner! :)
Debug.Print "Method/property works with the following maximum length: " & _
upperBoundary & vbCrLf & _
"(If this matches MAX_LENGTH (" & _
MAX_LENGTH & "), " & _
"consider increasing it to find the actual limit.)" & _
vbCrLf & vbCrLf & _
"Computation took " & currentStep & " steps"
Exit Sub
Else
' Upper boundary must be at least one less
upperBoundary = upperBoundary - 1
PrintStep upperBoundary + 1, "failed", lowerBoundary, upperBoundary, MAX_LENGTH
End If
' Approximately halve test length
currentLength = lowerBoundary + ((upperBoundary - lowerBoundary) \ 2)
' "\" is integer division (http://mathworld.wolfram.com/IntegerDivision.html)
' Using `left + ((right - left) \ 2)` is the default way to avoid overflows
' when calculating the midpoint for our binary search
' (see: https://en.wikipedia.org/w/index.php?title=Binary_search_algorithm&
' oldid=809435933#Implementation_issues)
If LengthWorks(currentLength) Then
' If test was successful, increase lower boundary for next step
lowerBoundary = currentLength + 1
PrintStep currentLength, "worked", lowerBoundary, upperBoundary, MAX_LENGTH
Else
' If not, set new upper boundary
upperBoundary = currentLength - 1
PrintStep currentLength, "failed", lowerBoundary, upperBoundary, MAX_LENGTH
End If
Wend
End Sub
Private Sub PrintStep(ByVal iCurrentValue As Long, _
ByVal iWorkedFailed As String, _
ByVal iNewLowerBoundary As Long, _
ByVal iNewUpperBoundary As Long, _
ByVal iMaximumTestValue As Long)
If PRINT_STEPS Then
Debug.Print Format(iCurrentValue, String(Len(CStr(iMaximumTestValue)), "0")) & _
" " & iWorkedFailed & " - New boundaries: l: " & _
iNewLowerBoundary & " u: " & iNewUpperBoundary
End If
End Sub
The short answer is no.
Regards, Zack Barresse

How do I program a loop into a DDEPoke call on VBA?

I am attempting to program a loop into a DDEPoke call to a VBA-supported function known as OPC. This will enable me to write to a PLC (RSLogix 500) database from an excel spreadsheet.
This is the code:
Private Function Open_RsLinx()
On Error Resume Next
Open_RsLinx = DDEInitiate(RsLinx, C1)
If Err.Number <> 0 Then
MsgBox "Error Connecting to topic", vbExclamation, "Error"
OpenRSLinx = 0 'Return false if there was an error
End If
End Function
Sub CommandButton1_Click()
RsLinx = Open_RsLinx()
For i = 0 To 255
DDEPoke RsLinx, "N16:0", Cells(1 + i, 2)
Next i
DDETerminate RsLinx
End Sub
This code works and will, if there is a link set up with an OPC server (in this case through RSLinx) write data to the PLC.
The problem is that I can't get the part DDEPoke RsLinx, "N16:0", Cells(1 + i, 2) to write data, sequentially, from one excel cell to one element of the PLC's data array.
I tried to do DDEPoke RsLinx, "N16:i", Cells(1 + i, 2) and DDEPoke RsLinx, "N16:0+i", Cells(1 + i, 2) but neither has any effect and the program doesn't write anything at all.
How can I set up the code to get N16:0 to increment all the way up to N16:255 and then stop?
Break the variable i out of the string. Be careful for the implicit type conversion though, depending on which (Str() or CStr()), you'll wind up with a leading space. Thus, convert the number Str(i), then wrap with Trim() to make sure there's no extra spaces, and concatenate that result back to your "N" string:
RsLinx = Open_RsLinx()
For i = 0 To 255
DDEPoke RsLinx, "N16:" & Trim(Str(i)), Cells(1 + i, 2)
Next i
The reason the i didn't work when it's inside the string is because that in VBA, anything within a set of quotes is considered a literal string. Unlike some other languages (PHP comes to mind) where variables can be resolved within a string like that, VBA must have variables concatenated. Consider the following:
Dim s As String
s = "world"
Debug.Print "Hello s!"
This outputs the literal of Hello s! to the immediate window, because s is treated not as a variable, but as part of the literal string. The correct way is through concatenation:
Dim s As String
s = "world"
Debug.Print "Hello " & s & "!"
That outputs the expected Hello World! to the immediate window, because s is now treated as a variable and is resolved and concatenated.
If that were not the case, the following might be difficult to deal with:
Dim i As Integer
For i = 0 to 9
Debug.Print "this" & i
Next i
You would then have:
th0s0
th1s1
th2s2
th3s3
th4s4
'etc
That'd make things pretty difficult to manage in a lot of cases.
With all that said, there are some languages - notably PHP - where, when using a certain set of quotes (either "" or '' - I don't recall which offhand), in fact does resolve the variable when embedded into the string itself:
$i = 5;
echo "this is number $i";
VBA does not have this feature.
Hope it helps...

Rounding When Last Number is Zero - Applescript

I've been using a simple little script for rounding numbers to the hundredths decimal place, however I've noticed that when the number ends in zero, it rounds up to the next decimal place. I need to preserve this decimal place.
For example: 7.49908302 would be rounded to 7.5 instead of 7.50.
How can I keep the hundredths decimal with this subroutine? Should I try something Perl or Objective C, as I've been told Applescript isn't the best for this sort of thing.
Here's the call:
set finalAge514years to (roundThis(age514years, 2))
Here's my rounding subroutine:
on roundThis(n, numDecimals)
set x to 10 ^ numDecimals
(((n * x) + 0.5) div 1) / x
end roundThis
The numbers 7.5 and 7.50 are exactly the same, so Applescript truncates any unnecessary information when displaying the number. What you're really looking for is how to format that information when it is displayed. For that, you need to turn the number into a string, specifying the amount of decimal places you want to see during the conversion.
This method of formatting a number is actually considered one of the Essential sub-routines.
round_truncate(7.49908302, 2)
--> "7.50"
on round_truncate(this_number, decimal_places)
if decimal_places is 0 then
set this_number to this_number + 0.5
return number_to_text(this_number div 1)
end if
set the rounding_value to "5"
repeat decimal_places times
set the rounding_value to "0" & the rounding_value
end repeat
set the rounding_value to ("." & the rounding_value) as number
set this_number to this_number + rounding_value
set the mod_value to "1"
repeat decimal_places - 1 times
set the mod_value to "0" & the mod_value
end repeat
set the mod_value to ("." & the mod_value) as number
set second_part to (this_number mod 1) div the mod_value
if the length of (the second_part as text) is less than the decimal_places then
repeat decimal_places - (the length of (the second_part as text)) times
set second_part to ("0" & second_part) as string
end repeat
end if
set first_part to this_number div 1
set first_part to number_to_string(first_part)
set this_number to (first_part & "." & second_part)
return this_number
end round_truncate
on number_to_string(this_number)
set this_number to this_number as string
if this_number contains "E+" then
set x to the offset of "." in this_number
set y to the offset of "+" in this_number
set z to the offset of "E" in this_number
set the decimal_adjust to characters (y - (length of this_number)) thru ¬
-1 of this_number as string as number
if x is not 0 then
set the first_part to characters 1 thru (x - 1) of this_number as string
else
set the first_part to ""
end if
set the second_part to characters (x + 1) thru (z - 1) of this_number as string
set the converted_number to the first_part
repeat with i from 1 to the decimal_adjust
try
set the converted_number to ¬
the converted_number & character i of the second_part
on error
set the converted_number to the converted_number & "0"
end try
end repeat
return the converted_number
else
return this_number
end if
end number_to_string
You specifically asked for a Perl or Objective-C alternative to Applescript.
Therefore, here is a Perl solution:
use strict;
use warnings;
print round_this(7.49908302, 2), "\n";
sub round_this {
my ($n, $decimals) = #_;
sprintf '%.*f', $decimals, $n;
}
output
7.50
Well AppleScript does many things for you. Because normally 1.5 is not the same as 0.75 * 2 (or at least not stable) in a boolean expression. To make real comparison easy for you the 1.50 (the result of 0.75 * 2) will be coerced into 1.5 for you. To make it work in AppleScript you need a string representation of the real number. However coercing a real into a string, AppleScript will take the system's localization into consideration. So the decimal mark and thousand separator can be different between machines depending on it's system preferences. The rounding is done by AppleScript when coercing a real to an integer. So something like this can work for you:
set n to 7.49958302
set i to round n rounding toward zero
set f to (n - i) * 100 as integer
set f to text 1 thru 2 of (f & "00" as string)
set r to (i as string) & "." & f
When you need this string back to a real again you can easily use the run script command. The run script command is like an eval. In AppleScript code the real numbers are not localized.
run script "7.50"