I have been struggling with this for several hours now and after reading many threads about ScriptBlocks, Closures, scopes etc I still don't see what's wrong in my code.
Let me explain: I have a main script that dynamically generates an HTML page using PSWriteHTML module and ScriptBlocks.
As I have a lot of PSWriteHTML pages to write, I use an arrayList of ScriptBlocks to generate the code with different set of values each time (corresponding to different servers), these ScriptBlocks being executed into a foreach loop.
This is done using the Save-utilizationReport function (I have only kept the relevant code):
function Save-utilizationReport ($currentDate, $navLinksScriptBlock, $htmlScriptBlockArray, $emeaTotalNumberOfCalls, $namTotalNumberOfCalls, $apacTotalNumberOfCalls, $path, $logFilePath) {
[...]
# Using Script Blocks, add the pages generated during the analysis to the HTML Report
foreach($htmlScriptBlock in $htmlScriptBlockArray){
Invoke-Command -ScriptBlock ($htmlScriptBlock)
}
[...]
}
The ScriptBlock are created using set of values gathered from a list of servers' logs and added to the arrayList of ScriptBlocks in the Create-utilizationReportPage function (again, I've only kept the relevant code):
function Create-utilizationReportPage ($matchedLines, $ipAddress, $hostname, $pageId, $utilisationReportTemplate, $htmlScriptBlockArray) {
# Retrieve the content of the Utilisation Report Template as a RAW string (Here-String)
$htmlPageCodeBlock = Get-Content $utilisationReportTemplate -Raw
# Create the Script Block that contains the HTML page
$htmlPageScriptBlock = {
# Get the "Total number of calls" information
$timeArray = $matchedLines.time
$participantNumberArray = $matchedLines.participantNumber
[...]
# Update the page ID in the template
$htmlPageCodeBlock = $htmlPageCodeBlock -replace '%PAGE_ID%', "$pageId"
# Update the page header information in the template
$htmlPageCodeBlock = $htmlPageCodeBlock -replace '%PAGE_HEADER%', "$ipAddress [$hostname]"
Invoke-Command -ScriptBlock ([scriptblock]::Create($htmlPageCodeBlock))
}.GetNewClosure()
# Add the Page's script block to the Script Blocks array
$htmlScriptBlockArray.Add($htmlPageScriptBlock)
}
These are called in the script below:
$currentDate = Get-CorrectDate $latestFolder
# The global Log file
$logFilePath = "$scriptPath/Logs/logs_$currentDate.txt"
$serversList = "$scriptPath/Config/$configFileName"
# If the Servers list exists, retrieve the Servers list
if (Test-Path $serversList) {
# Get the data from the file
[xml]$servers = Get-Content $serversList
# Select only the Servers information
$nodes = $servers.SelectNodes("//server")
try {
[...]
# Iterate through the Servers list
foreach ($node in $nodes) {
# Get the Server IP Address
$ipAddress = $node.ip
# Get the Server Hostname
$hostname = $node.hostname
# Get the "Debug Utilization" lines from the logbundle's syslog files
$matchedLines = [System.Collections.ArrayList]#(Find-UtilizationLinesInLogs $currentDate "$scriptPath\Data\$currentDate\logs_$ipAddress" "host:server: \[USAGE\] : \{`"1`" : ")[-1]
# Add the Server's participants throughout the day
switch ($hostname) {
{$_.Contains("emea")} {
# Update EMEA total number of participants
Update-ZoneTotalParticipants ([ref]$emeaServer) ([ref]$emeaTotalNumberOfCalls) $matchedLines
}
{$_.Contains("nam")} {
# Update NAM total number of participants
Update-ZoneTotalParticipants ([ref]$namServer) ([ref]$namTotalNumberOfCalls) $matchedLines
}
{$_.Contains("apac")} {
# Update APAC total number of participants
Update-ZoneTotalParticipants ([ref]$apacServer) ([ref]$apacTotalNumberOfCalls) $matchedLines
}
}
# Get the CPU Utilization values lines from the logbundle's sysdebug files
$cpu = Get-CpuUsage "$scriptPath\Data\$currentDate\logs_$ipAddress\sysdebug"
# Get the Memory Utilization values lines from the logs' sysdebug files
$memory = Get-MemoryUsage "$scriptPath\Data\$currentDate\logs_$ipAddress\sysdebug"
# Export the "Debug Utilization" to a CSV file
Save-csvUtilizationReport $matchedLines "$scriptPath\Output\$currentDate" "$ipAddress" "$hostname" $logFilePath
# Draw graphs from the "Debug Utilization" information and then export it to an HTML File
Create-utilizationReportPage $matchedLines "$ipAddress" "$hostname" $pageId $utilisationReportTemplate $htmlScriptBlockArray
# Add the new navigation link to the Navigation Links array
Add-htmlNavLink $navLinksArray "$ipAddress" "$hostname" $pageId $logFilePath
# Increment the Page ID counter
$pageId += 1
}
# Create an Here-String from the Navigation Links array
$OFS = ""
$navLinksCode =#"
$($navLinksArray)
"#
$OFS = " "
# Create a script block from the Navigation Links
$navLinksScriptBlock = [scriptblock]::Create($navLinksCode)
# Save the daily HTML utilization report
Save-utilizationReport $currentDate $navLinksScriptBlock $htmlScriptBlockArray $emeaTotalNumberOfCalls $namTotalNumberOfCalls $apacTotalNumberOfCalls "$scriptPath\Output\$currentDate" $logFilePath
}
catch
{
Write-Logs $logFilePath "Error: $($_.Exception.Message)"
exit 1
}
}
Everything is working fine and as expcted except for the first set of values in the first page which is somehow the sum of all the other set of values...
For example when I have 3 pages, I can see that the collected values are correct when the Find-UtilizationLinesInLogs function is executed:
matchedLines.participantNumber:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 5 5 4 4 4 4 4 12 14 14 15 16 16 16 7 7 7 7 8 14 17 18 19 18 19 19 20 16 16 16 15 7 7 7 7 5 4 4 4 4 4 4 4 4 1 1 0 0 0 3 6 5 5 5 5 9 14 16 18
matchedLines.participantNumber:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 8 9 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 9 4 12 15 11 14 14 13 12 12 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
matchedLines.participantNumber:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 0 0 0 3 9 10 10 9 8 10 9 9 10 10 10 10 10 11 11 11 11 8 7 8 8 7 7 7 7 7 7 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 16 17 16 16
But when the ScriptBlocks are executed using the Invoke-Command in the foreach loop, the first batch of values is systematically the sum of the 3 sets of values while the following ones are correct:
matchedLines.participantNumber inside Create-utilizationReportPage:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 6 6 7 7 5 5 5 8 29 33 34 34 34 36 34 25 26 26 26 27 32 36 37 38 37 35 34 36 32 31 32 26 26 29 25 22 20 18 17 17 17 6 6 6 6 3 3 2 2 2 5 8 7 7 7 10 25 31 32 34
matchedLines.participantNumber inside Create-utilizationReportPage:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 8 9 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 9 4 12 15 11 14 14 13 12 12 12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
matchedLines.participantNumber inside Create-utilizationReportPage:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 0 0 0 3 9 10 10 9 8 10 9 9 10 10 10 10 10 11 11 11 11 8 7 8 8 7 7 7 7 7 7 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 16 17 16 16
I have tried many things without success so if someone has any hint of what can go wrong, it would be great!
Thanks for your help!
So! I finally found out what was wrong.
I suspected that my problem was related to arrayList copies or at least variable copies... so I tried to remove this part of the script where I extract the $macthedLines values and copy them into global arrays using references:
# Add the Server's participants throughout the day
switch ($hostname) {
{$_.Contains("emea")} {
# Update EMEA total number of participants
Update-ZoneTotalParticipants ([ref]$emeaServer) ([ref]$emeaTotalNumberOfCalls) $matchedLines
}
{$_.Contains("nam")} {
# Update NAM total number of participants
Update-ZoneTotalParticipants ([ref]$namServer) ([ref]$namTotalNumberOfCalls) $matchedLines
}
{$_.Contains("apac")} {
# Update APAC total number of participants
Update-ZoneTotalParticipants ([ref]$apacServer) ([ref]$apacTotalNumberOfCalls) $matchedLines
}
}
And bingo, this time the values written down in the first PSWriteHTML page are the correct one!
So I focused on the Update-ZoneTotalParticipants function which is doing the values copy:
function Update-ZoneTotalParticipants ([ref][int]$ServerNumber, [ref][System.Collections.ArrayList]$totalNumberOfCalls, $values) {
# If this is the first server to be analysed in the zone
if ($ServerNumber.value -eq 1) {
# Copy the Utilization lines of the server
$totalNumberOfCalls.value = $values
# Increment the zone's server counter
$ServerNumber.value += 1
}
# If this at least the 2nd server to be analysed in the zone
elseif ($ServerNumber.value -gt 1) {
# Parse the server matched lines, get the participantNumber value and add it to the total
0..($totalNumberOfCalls.value.Count - 1) | ForEach-Object {
if ($_ -le ($values.Count - 1)) {
$totalNumberOfCalls.value[$_].participantNumber = [int]($totalNumberOfCalls.value[$_].participantNumber) + [int]($values[$_].participantNumber)
}
# If there are less objects in the current server matched lines, add a 0 instead
else {
$totalNumberOfCalls.value[$_].participantNumber = [int]($totalNumberOfCalls.value[$_].participantNumber) + 0
}
}
}
}
The only part of the code where $matchedLines is involved is $totalNumberOfCalls.value = $values so it certainly is were the array manipulation goes wrong.
So I dug around ArrayList copies or clones and found out that I was not doing a deep copy of the object and that it could cause issues.
I used Petru Zaharia's solution in this thread to update the function:
# Copy the Utilization lines of the server
$totalNumberOfCalls.value = $values
# replaced with:
# Copy the Utilization lines of the server : Serialize and Deserialize data using PSSerializer:
$_TempCliXMLString = [System.Management.Automation.PSSerializer]::Serialize($matchedLines, [int32]::MaxValue)
$totalNumberOfCalls.value = [System.Management.Automation.PSSerializer]::Deserialize($_TempCliXMLString)
And now everything works as expected.
Thanks guys for your support!
Related
To sort a matrix according to all columns except the first, I used the following code. I do not want sortrows to consider the first column because that is meant to keep track of the row numbers.
B = [1 1 0 0 0 0 0 0 0 1
2 0 1 0 0 0 0 1 0 0
3 0 0 1 0 1 0 0 1 0
4 0 1 0 0 0 1 1 0 0
5 0 0 1 0 0 0 0 1 0
6 0 0 0 0 0 1 1 0 0
7 1 0 0 1 0 0 0 0 0
8 0 0 1 0 1 0 0 0 0];
D = -sortrows(-B,[2:size(B,2)])
What if you want to sort the matrix according to all rows except the first, so the first element of each column would be ignored when sorting them in descending order? Is there any similar function to sortrows?
To clarify, the desired output is
1 0 0 0 0 0 0 1 0 1
2 1 1 0 0 0 0 0 0 0
3 0 0 1 1 1 0 0 0 0
4 1 1 0 0 0 1 0 0 0
5 0 0 1 1 0 0 0 0 0
6 1 0 0 0 0 1 0 0 0
7 0 0 0 0 0 0 1 1 0
8 0 0 1 0 1 0 0 0 0
You can do this via
transposing the input and output
keeping column 1 separate
you can use negative sort indices to avoid what you've done making the input and output negative
A = [B(:,1) sortrows( B(:,2:end).', -(2:size(B,1)) ).'];
>> A
A =
1 0 0 0 0 0 0 1 0 1
2 1 1 0 0 0 0 0 0 0
3 0 0 1 1 1 0 0 0 0
4 1 1 0 0 0 1 0 0 0
5 0 0 1 1 0 0 0 0 0
6 1 0 0 0 0 1 0 0 0
7 0 0 0 0 0 0 1 1 0
8 0 0 1 0 1 0 0 0 0
I have a 10*10 array for a domain
TT =
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
, but I need to exclude a square centered in the domain, so the new domain would be :
TT_new =
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
I will create a for loop for the domain TT_new, how can I do this operation in MATLAB?
Clarification: I need the for loop to go through the "i,j" of the new domain "TT_new".
For example, I have the following mesh plot for steady heat conduction problem for a U-shaped plate:
2D plot
3D plot
I need to exclude the purple rectangular from the plot because it is not within the computational domain.
Have you thought about using a mask?
You can't have matrices with holes in them, but if you element-wise multiply your original array with a matrix containing ones everywhere and zeros in the places you want to remove, your result will be an array containing the original numbers everywhere but zeros where you have zeros in your mask. For example:
my_arr = reshape(1:30, 5, 6);
my_mask = ones(5, 6);
my_mask(1:3, 3:4) = 0;
masked_arr = my_arr .* my_mask;
This gives:
my_arr =
1 6 11 16 21 26
2 7 12 17 22 27
3 8 13 18 23 28
4 9 14 19 24 29
5 10 15 20 25 30
my_mask =
1 1 0 0 1 1
1 1 0 0 1 1
1 1 0 0 1 1
1 1 1 1 1 1
1 1 1 1 1 1
masked_arr =
1 6 0 0 21 26
2 7 0 0 22 27
3 8 0 0 23 28
4 9 14 19 24 29
5 10 15 20 25 30
Whether this is applicable to your situation depends on how you use the boundaries of your computational domain in your computation. Multiplying by a mask containing NaN will change unwanted elements to NaN.
Then, you could apply your mask before (or after, or both) every iteration to ensure that the unwanted elements are set to zero (or NaN, but that can cause problems if you add / subtract / multiply those elements with anything).
domain_mask = ones(size(T));
domain_mask(7:16, 12:31) = 0;
domain_mask_small = domain_mask(2:end-1, 2:end-1);
zero_row = zeros(1, size(T, 2));
zero_col = zeros(size(T, 1), 1);
while error > tol
% Perform operations on the entire array
T_iplus1 = [T(2:end, :); zero_row];
T_iminus1 = [zero_row; T(1:end-1, :)];
T_jplus1 = [T(:, 2:end), zero_col];
T_jminus1 = [zero_col, T(:, 1:end-1)];
T_temp = 0.25 * (T_iplus1 + T_iminus1 + T_jplus1 + T_jminus1);
T(2:end-1, 2:end-1) = T_temp(2:end-1, 2:end-1) .* domain_mask_small;
% Do whatever else
end
I want to concatenate last four bits of binary into a number i have tried the following code
x8=magic(4)
x8_n=dec2bin(x8)
m=x8_n-'0'
which gives me the following output
m =
1 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 0 1 0 0
0 0 0 1 0
0 1 0 1 1
0 0 1 1 1
0 1 1 1 0
0 0 0 1 1
0 1 0 1 0
0 0 1 1 0
0 1 1 1 1
0 1 1 0 1
0 1 0 0 0
0 1 1 0 0
0 0 0 0 1
now i want to take every last 4 bits it each row and convert it into an integer
n = 4; %// number of bits you want
result = m(:,end-n+1:end) * pow2(n-1:-1:0).'; %'// matrix multiplication
Anyway, it would be easier to use mod on x8 directly, without the intermediate step of m:
result = mod(x8(:), 2^n);
In your example:
result =
0
5
9
4
2
11
7
14
3
10
6
15
13
8
12
1
This could be another approach -
n = 4; %%// number of bits you want
out = bin2dec(num2str(m(:,end-n+1:end)))
Output -
out =
0
5
9
4
2
11
7
14
3
10
6
15
13
8
12
1
i have below .dat file, i want matlab reads data in the 'REQUESTS/DURATIONS:' part and save them in a single matrix in size (32,7). i don't know which function to use ,i don't know how to do it. please help me.
file with basedata : j30_17.bas
initial value random generator: 79602564
projects : 1
jobs (incl. supersource/sink ): 32
horizon : 141
RESOURCES
- renewable : 4 R
- nonrenewable : 0 N
- doubly constrained : 0 D
REQUESTS/DURATIONS:
jobnr. mode duration R 1 R 2 R 3 R 4
------------------------------------------------------------------------
1 1 0 0 0 0 0
2 1 1 0 0 0 5
3 1 1 0 3 0 0
4 1 1 8 0 0 0
5 1 7 0 0 2 0
6 1 6 0 0 0 3
7 1 4 1 0 0 0
8 1 5 0 0 10 0
9 1 8 0 0 3 0
10 1 7 0 0 0 1
11 1 8 9 0 0 0
12 1 1 7 0 0 0
13 1 2 0 3 0 0
14 1 3 0 0 0 6
15 1 10 0 7 0 0
16 1 10 3 0 0 0
17 1 2 0 0 3 0
18 1 10 0 0 4 0
19 1 1 0 0 0 3
20 1 1 0 0 7 0
21 1 7 0 2 0 0
22 1 9 0 0 0 10
23 1 9 0 0 7 0
24 1 4 0 4 0 0
25 1 4 0 3 0 0
26 1 1 0 0 4 0
27 1 1 9 0 0 0
28 1 8 0 0 0 9
29 1 1 0 0 0 1
30 1 2 0 8 0 0
31 1 7 0 4 0 0
32 1 0 0 0 0 0
************************************************************************
RESOURCEAVAILABILITIES:
R 1 R 2 R 3 R 4
10 8 13 12
************************************************************************
If you skip the header, textscan() will stop reading the file once the actual type of data does not correspond to the one specified in format, i.e. when all those asterisks begin:
fid = fopen('C:\...\test.txt');
data = textscan(fid, '%f%f%f%f%f%f%f','HeaderLines',15);
fclose(fid);
I'm not sure about older versions, but 2013a can import text files by right clicking a file under the "Current Folder" panel and selecting "Import Data...". The import wizard will open up and allow you to select the range of data to import. Select the matrix option, and click "Import Selection."
To save your matrix, just use the save command.
This approach works well for single files that you just need to read quickly, but not for a large repetetive task.
I have a matrix that contains data of 0 & 1. I want to find groups of ones (not a specific size) in that matrix. Is it possible somehow?
Thanks in advance!
If you mean that you want to find all the "connected components in the matrix, say BW, simply use:
BW = logical([1 1 1 0 0 0 0 0
1 1 1 0 1 1 0 0
1 1 1 0 1 1 0 0
1 1 1 0 0 0 1 0
1 1 1 0 0 0 1 0
1 1 1 0 0 0 1 0
1 1 1 0 0 1 1 0
1 1 1 0 0 0 0 0]);
L = bwlabel(BW,4) %Result
This would yeild:
L =
1 1 1 0 0 0 0 0
1 1 1 0 2 2 0 0
1 1 1 0 2 2 0 0
1 1 1 0 0 0 3 0
1 1 1 0 0 0 3 0
1 1 1 0 0 0 3 0
1 1 1 0 0 3 3 0
1 1 1 0 0 0 0 0
Now if you want to find the size of various groups:
for ii=1:max(L(:))
length_vector(ii)=length(find(L==ii));
end
length_vector
This gives you:
length_vector =
24 4 5