diff --git a/CHANGELOG.md b/CHANGELOG.md index 31003ab..7a4be37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,33 @@ # Changelog All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and +this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.4.0] - 2023-09-03 +### Added +- Support for nested loops where the inner one is a parallel `parfor` loop. Thanks + @tunakasif! ## [3.3.0] - 2021-02-28 ### Added -- Optional switch to completely disable the bar's progress and printing functionality via `IsActive`. If `false`, the progress bar is disabled. The default is `true`. +- Optional switch to completely disable the bar's progress and printing functionality + via `IsActive`. If `false`, the progress bar is disabled. The default is `true`. ## [3.2.0] - 2020-11-13 ### Added -- Optional switch to override default MATLAB font in Windows by `OverrideDefaultFont` property. If `true` and while the bar is not released, MATLAB's code font will be changed programmatically to `Courier New` +- Optional switch to override default MATLAB font in Windows by `OverrideDefaultFont` + property. If `true` and while the bar is not released, MATLAB's code font will be + changed programmatically to `Courier New` - Proper unit testing as a test-case class ### Changed - Demos should now be a bit prettier ### Fixed -- Progress bar in non-finite and iteration mode (not having a konwn total number of iterations) will now show correct unit string `it` instead of toggling wrongly between `it` and `s` +- Progress bar in non-finite and iteration mode (not having a konwn total number of + iterations) will now show correct unit string `it` instead of toggling wrongly between + `it` and `s` ## [3.1.1] - 2019-11-26 @@ -29,7 +39,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [3.1.0] - 2019-11-03 +### Fixed +- Moved setup-related method-calls to the setup phase of the object. In v3.0.0, the + timer object was initialized in the constructor, leading to potential issues in + computing the elapsed time and estimated time until completion. Also, user-set + properties (like, e.g., the bar's title) could have had no influence while calling the + step function due to the same reason. ### Changed +- Renamed private properties +- The class' version is now stated as a constant property - Made some properties constant and method static - Cleaned up comments - Updated README and CHANGELOG @@ -38,15 +56,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [3.0.0] - 2017-05-02 ### Changed The whole class has been re-factored to be a **MATLAB System Object** -- Since updating a progress bar is an iterative process with a setup/reset/release paradigm, this object type fits the purpose best -- Due to the System Object class structure, multiple methods have been renamed and optional input arguments for the `step()` (formerly `update()`) method are now mandatory. See the example in `README.md`. -- The bar's **title** is now mandatory and has a default string: `'Processing'`. Notable change is that if the title exceeds the length of 20 characters the title will act like a banner and cycle with a shift of 3 characters each time the bar is updated. This way, the progress bar can have a constant width (for now, 90 characters seem to fit many screens). - - -## [3.1.0] - 2019-11-03 -### Fixed -- Moved setup-related method-calls to the setup phase of the object. In v3.0.0, the timer object was initialized in the constructor, leading to potential issues in computing the elapsed time and estimated time until completion. Also, user-set properties (like, e.g., the bar's title) could have had no influence while calling the step function due to the same reason. - -### Changed -- Renamed private properties -- The class' version is now stated as a constant property \ No newline at end of file +- Since updating a progress bar is an iterative process with a setup/reset/release + paradigm, this object type fits the purpose best +- Due to the System Object class structure, multiple methods have been renamed and + optional input arguments for the `step()` (formerly `update()`) method are now + mandatory. See the example in `README.md`. +- The bar's **title** is now mandatory and has a default string: `'Processing'`. Notable + change is that if the title exceeds the length of 20 characters the title will act + like a banner and cycle with a shift of 3 characters each time the bar is updated. + This way, the progress bar can have a constant width (for now, 90 characters seem to + fit many screens). diff --git a/LICENSE b/LICENSE index 22d796b..1071665 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause -Copyright (c) 2020, Jens-Alrik Adrian +Copyright (c) 2023, Jens-Alrik Adrian All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/ProgressBar.m b/ProgressBar.m index 58ae852..5b661cb 100644 --- a/ProgressBar.m +++ b/ProgressBar.m @@ -45,7 +45,7 @@ properties (Constant) % Tag every timer with this to find it properly TIMER_TAG_NAME = 'ProgressBar'; - VERSION = '3.3.0'; + VERSION = '3.4.0'; end properties (Nontunable) @@ -279,15 +279,15 @@ obj.CurrentTitleState = [obj.CurrentTitleState, ' -- ']; end - % if the bar is used in a parallel setup start the timer right now - if obj.IsParallel - obj.startTimer(); - end - % if this is a nested bar hit return if obj.IsThisBarNested fprintf(1, '\n'); end + + % if the bar is used in a parallel setup start the timer right now + if obj.IsParallel + obj.startTimer(); + end obj.printProgressBar(); end end diff --git a/README.md b/README.md index cdc6d8d..9287fab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![View MatlabProgressBar on File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://de.mathworks.com/matlabcentral/fileexchange/57895-matlabprogressbar) +[![View MatlabProgressBar on File +Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://de.mathworks.com/matlabcentral/fileexchange/57895-matlabprogressbar) # MatlabProgressBar @@ -18,14 +19,26 @@ - [Unit Tests](#unit-tests) - [License](#license) -This project hosts the source code to the [original MATLAB FileExchange project](https://de.mathworks.com/matlabcentral/fileexchange/57895-matlabprogressbar) and is place of active development. +This project hosts the source code to the [original MATLAB FileExchange +project](https://de.mathworks.com/matlabcentral/fileexchange/57895-matlabprogressbar) +and is place of active development. -A drawback in MATLAB's own `waitbar()` function is the lack of some functionalities and the loss of speed due to the rather laggy GUI updating process. -Therefore, this MATLAB class aims to provide a smart progress bar in the command window and is optimized for progress information in simple iterations or large frameworks with full support of parallel *parfor* loops and asynchronous processing via *parfeval()* provided by the MATLAB Parallel Computing Toolbox. +A drawback in MATLAB's own `waitbar()` function is the lack of some functionalities and +the loss of speed due to the rather laggy GUI updating process. Therefore, this MATLAB +class aims to provide a smart progress bar in the command window and is optimized for +progress information in simple iterations or large frameworks with full support of +parallel *parfor* loops and asynchronous processing via *parfeval()* provided by the +MATLAB Parallel Computing Toolbox. -A design target was to mimic the best features of the progress bar [tqdm](https://github.com/tqdm/tqdm) for Python. Thus, this project features a Unicode-based bar and some numeric information about the current progress and the average iterations per second. +A design target was to mimic the best features of the progress bar +[tqdm](https://github.com/tqdm/tqdm) for Python. Thus, this project features a +Unicode-based bar and some numeric information about the current progress and the +average iterations per second. -Several projects exist on MATLAB's [File Exchange](https://www.mathworks.com/matlabcentral/fileexchange/?term=progress+bar) but none incorporates the feature set shown below. That's why I decided to start this project. +Several projects exist on MATLAB's [File +Exchange](https://www.mathworks.com/matlabcentral/fileexchange/?term=progress+bar) but +none incorporates the feature set shown below. That's why I decided to start this +project. ![Easy progress bar example](images/example2.gif) @@ -33,46 +46,67 @@ Several projects exist on MATLAB's [File Exchange](https://www.mathworks.com/mat - [x] proper unit testing - [x] display the bar name as a ticker. That way, a fixed bar width could be used - [x] inherit from MATLAB System Object to gain benefits from the setup method - - [ ] use [this new functionality](https://de.mathworks.com/help/distcomp/send.html) for the parallel implementation. Introduced in R2017a. + - [ ] use [this new functionality](https://de.mathworks.com/help/distcomp/send.html) + for the parallel implementation. Introduced in R2017a. - [x] TQDM Unicode blocks - [x] optional constructor switch for ASCII number signs (hashes) - those will be used if `ProgressBar()` is used in deploy mode (MATLAB Compiler) - [x] optional bar title - [x] optional visual update interval in Hz [defaults to 10 Hz] -- [x] when no total number of iterations is passed the bar shows the elapsed time, the number of (elapsed) iterations and iterations/s -- [x] nested bars (at the moment only one nested bar is supported [one parent, one child]) +- [x] when no total number of iterations is passed the bar shows the elapsed time, the + number of (elapsed) iterations and iterations/s +- [x] nested bars (at the moment only one nested bar is supported [one parent, one + child]) - [x] `printMessage()` method for debug printing (or the like) - [x] print an info when a run was not successful -- [x] support another meaningful 'total of something' measure where the number of items is less meaningful (for example non-uniform processing time) such as total file size (processing multiple files with different file size). At the moment, the only alternative supported unit is `Bytes` -- [x] when the internal updating process is faster than the actual updates via `update()`, the internal counter and printing of the process bar stops until the next update to save processing time +- [x] support another meaningful 'total of something' measure where the number of items + is less meaningful (for example non-uniform processing time) such as total file size + (processing multiple files with different file size). At the moment, the only + alternative supported unit is `Bytes` +- [x] when the internal updating process is faster than the actual updates via + `update()`, the internal counter and printing of the process bar stops until the next + update to save processing time - [x] linear ETA estimate over all last iterations - [x] support parfor loops provided by the Parallel Computing Toolbox - [x] show s/it if it/sec < 1 -- [x] override MATLAB's default non-UTF font if `OverrideDefaultFont` is `true`. This will switch the font for the command line to `Courier New` for the lifetime of the bar. Default is `false` -- [x] disable the progress bar if `IsActive` is `false`. This will disable the functionality completely and can be used in situations in which the bar is not beneficial (e.g. if the bar is used in a sub-application of a processing cluster). Default is `true`. +- [x] override MATLAB's default non-UTF font if `OverrideDefaultFont` is `true`. This + will switch the font for the command line to `Courier New` for the lifetime of the + bar. Default is `false` +- [x] disable the progress bar if `IsActive` is `false`. This will disable the + functionality completely and can be used in situations in which the bar is not + beneficial (e.g. if the bar is used in a sub-application of a processing cluster). + Default is `true`. -**Note**: -Be sure to have a look at the [Known Issues](#known-issues) section for current known bugs and possible work-arounds. +**Note**: +Be sure to have a look at the [Known Issues](#known-issues) section for current known +bugs and possible work-arounds. ## Dependencies No dependencies to toolboxes. -The code has been tested with MATLAB R2016a, R2016b and R2020b on Windows 10, Xubuntu 16.04.2 LTS and Linux Mint 20. +The code has been tested with MATLAB R2016a, R2016b and R2020b on Windows 10, Xubuntu +16.04.2 LTS and Linux Mint 20. ## Installation -Put the files `ProgressBar.m`, `progress.m` and `updateParallel.m` into your MATLAB path or the directory of your MATLAB project. +Put the files `ProgressBar.m`, `progress.m` and `updateParallel.m` into your MATLAB path +or the directory of your MATLAB project. ## Usage -Detailed information and examples about all features of `ProgressBar` are stated in the demo scripts in the `./demos/` directory. +Detailed information and examples about all features of `ProgressBar` are stated in the +demo scripts in the `./demos/` directory. ### Proposed Usage for Simple Loops -The simplest use in `for`-loops is to use the `progress()` function. It wraps the main `ProgressBar` class and is intended to only support the usual progress bar. Be aware that functionalities like `printMessage()`, printing success information or a step size different to 1 are not supported with `progress.m`. Also, this only works for **non-parallel** loops. +The simplest use in `for`-loops is to use the `progress()` function. It wraps the main +`ProgressBar` class and is intended to only support the usual progress bar. Be aware +that functionalities like `printMessage()`, printing success information or a step size +different to 1 are not supported with `progress.m`. Also, this only works for +**non-parallel** loops. See the example below: ```matlab @@ -87,11 +121,19 @@ end ![Example 2](images/example2.gif) ### Extended Usage with all Features -The basic work flow is to instantiate a `ProgressBar` object and use either the `step()` method to update the progress state (MATLAB <= R2015b) or use the instantiated object directly as seen below. Refer to the method's help for information about input parameters. The shown call is the *default* call and sufficient. If you want to pass information about the step size, the iteration's success or if a new bar should be printed immediately (e.g. when iterations take long time) you can pass these information instead of empty matrices. - -All settings are done using *name-value* pairs in the constructor. It is **strongly encouraged** to call the object's `release()` method after the loop is finished to clean up the internal state and avoid possibly unrobust behavior of following progress bars. - -*Usage* +The basic work flow is to instantiate a `ProgressBar` object and use either the `step()` +method to update the progress state (MATLAB <= R2015b) or use the instantiated object +directly as seen below. Refer to the method's help for information about input +parameters. The shown call is the *default* call and sufficient. If you want to pass +information about the step size, the iteration's success or if a new bar should be +printed immediately (e.g. when iterations take long time) you can pass these information +instead of empty matrices. + +All settings are done using *name-value* pairs in the constructor. It is **strongly +encouraged** to call the object's `release()` method after the loop is finished to clean +up the internal state and avoid possibly unrobust behavior of following progress bars. + +*Usage* `obj = ProgressBar(totalIterations, varargin)` A simple but quite common example looks like this: @@ -105,13 +147,13 @@ progBar = ProgressBar(... numIterations, ... 'Title', 'Awesome Computation' ... ); - + % begin the actual loop and update the object's progress % state for iIteration = 1:numIterations %%% do some processing here % ... - + progBar([], [], []); % or in releases <= R2015b % progBar.step([], [], []); @@ -124,7 +166,9 @@ progBar.release(); ### Parallel Toolbox Support -If you use MATLAB's Parallel Computing Toolbox, refer to the following example or the demo file `k_parallelSetup.m`. Tested parallel functionalities are `parfor` and `parfeval()` for asynchronous processing. +If you use MATLAB's Parallel Computing Toolbox, refer to the following example or the +demo file `k_parallelSetup.m`. Tested parallel functionalities are `parfor` and +`parfeval()` for asynchronous processing. ```matlab numIterations = 10e3; @@ -134,42 +178,61 @@ progBar = ProgressBar(numIterations, ... 'IsParallel', true, ... 'Title', 'Parallel Processing' ... ); - + % ALWAYS CALL THE SETUP() METHOD FIRST!!! progBar.setup([], [], []); parfor iIteration = 1:numIterations pause(0.1); - + % USE THIS FUNCTION AND NOT THE STEP() METHOD OF THE OBJECT!!! updateParallel([], pwd); end progBar.release(); ``` +As of `v3.4.0`, you can also nest the loops so that the inner one uses a `parfor` loop. + ## Known Issues ### Flickering Bar or Flooding of the Command Window -MATLAB's speed to print to the command window is actually pretty low. If the update rate of the progress bar is high the mentioned effects can occur. Try to reduce the update rate from the default 5 Hz to something lower (say 3 Hz) with the `'UpdateRate', 3` name-value pair. +MATLAB's speed to print to the command window is actually pretty low. If the update rate +of the progress bar is high the mentioned effects can occur. Try to reduce the update +rate from the default 5 Hz to something lower (say 3 Hz) with the `'UpdateRate', 3` +name-value pair. ### The Bar Gets Longer With Each Iteration -There seems to be a problem with the default font `Monospaced` in Windows. If this behavior is problematic, change the font for the command window to a different monospaced font, preferably with proper Unicode support. +There seems to be a problem with the default font `Monospaced` in Windows. If this +behavior is problematic, change the font for the command window to a different +monospaced font, preferably with proper Unicode support. -If you do not want to or cannot change the font in the setting, you can set the class's `OverrideDefaultFont` to `true` while you are in the configuration phase. This will change MATLAB's coding font to `Courier New` for the duration in which the bar is alive (until the `release()` method is executed). After the object's lifetime, your previous font will be restored automatically. +If you do not want to or cannot change the font in the setting, you can set the class's +`OverrideDefaultFont` to `true` while you are in the configuration phase. This will +change MATLAB's coding font to `Courier New` for the duration in which the bar is alive +(until the `release()` method is executed). After the object's lifetime, your previous +font will be restored automatically. -For convenience, this property can also be set in the `progress()` wrapper to always trigger for the wrapper if desired. +For convenience, this property can also be set in the `progress()` wrapper to always +trigger for the wrapper if desired. Thanks [@GenosseFlosse](https://github.com/GenosseFlosse) for the fix! ### Strange Symbols in the Progress Bar -The display of the updating progress bar is highly dependent on the **font** you use in the command window. Be sure to use a proper font that can handle Unicode characters. Otherwise be sure to always use the `'Unicode', false` switch in the constructor. +The display of the updating progress bar is highly dependent on the **font** you use in +the command window. Be sure to use a proper font that can handle Unicode characters. +Otherwise be sure to always use the `'Unicode', false` switch in the constructor. ### Remaining Timer Objects in MATLAB's Background -Sometimes, if the user cancels a loop in which a progress bar was used, the destructor is not called properly and the timer object remains in memory. This can lead to strange behavior of the next progress bar instantiated because it thinks it is nested. If you encounter strange behavior like wrong line breaks or disappearing progress bars after the bar has finished, just call the following static method to delete all remaining timer objects in memory which belong(ed) to progress bars and start over. +Sometimes, if the user cancels a loop in which a progress bar was used, the destructor +is not called properly and the timer object remains in memory. This can lead to strange +behavior of the next progress bar instantiated because it thinks it is nested. If you +encounter strange behavior like wrong line breaks or disappearing progress bars after +the bar has finished, just call the following static method to delete all remaining +timer objects in memory which belong(ed) to progress bars and start over. ```matlab ProgressBar.deleteAllTimers(); @@ -177,20 +240,36 @@ ProgressBar.deleteAllTimers(); ### Issues Concerning Parallel Processing -The work-flow when using the progress bar in a parallel setup is to instantiate the object with the `IsParallel` switch set to `true` and using the `updateParallel()` function to update the progress state instead of the `step()` method of the object. If this results in strange behavior check the following list. Generally, it is advisable to **first be sure that the executed code or functions in the parallel setup run without errors or warnings.** If not the execution may prevent the class destructor to properly clean up all files and timer objects. - -- are there remaining timer objects that haven't been deleted from canceled `for`/`parfor` loops or `parfeval()` calls when checking with `timerfindall('Tag', 'ProgressBar')`? +The work-flow when using the progress bar in a parallel setup is to instantiate the +object with the `IsParallel` switch set to `true` and using the `updateParallel()` +function to update the progress state instead of the `step()` method of the object. If +this results in strange behavior check the following list. Generally, it is advisable to +**first be sure that the executed code or functions in the parallel setup run without +errors or warnings.** If not the execution may prevent the class destructor to properly +clean up all files and timer objects. + +- are there remaining timer objects that haven't been deleted from canceled + `for`/`parfor` loops or `parfeval()` calls when checking with `timerfindall('Tag', + 'ProgressBar')`? - use `delete(timerfindall('Tag', 'ProgressBar'))` - does the progress exceed 100%? - - try to call `clear all` or, specifically, `clear updateParallel` to clear the internal state (persistent variables) in the mentioned function. This should have been done by the class destructor but sometimes gets unrobust if there have been errors in parallel executed functions. - - Also try to look into your temp directory (returned by `tempdir()`) if remaining `progbarworker_*` files exist. Delete those if necessary. - - -**TL/DR**: -`clear all` and `delete(timerfindall('Tag', 'ProgressBar'))` are your friend! Be sure that no files following the pattern `progbarworker_*` remain in the directory returned by `tempdir()`. + - try to call `clear all` or, specifically, `clear updateParallel` to clear the + internal state (persistent variables) in the mentioned function. This should have + been done by the class destructor but sometimes gets unrobust if there have been + errors in parallel executed functions. + - Also try to look into your temp directory (returned by `tempdir()`) if remaining + `progbarworker_*` files exist. Delete those if necessary. + + +**TL/DR**: +`clear all` and `delete(timerfindall('Tag', 'ProgressBar'))` are your friend! Be sure +that no files following the pattern `progbarworker_*` remain in the directory returned +by `tempdir()`. ## Unit Tests -You can run all available tests in the project directory by navigating into the `tests` folder and executing `runtests` in MATLAB. However, if you want to omit the parallel tests (e.g. you don't have the Parallel Toolbox installed), just execute +You can run all available tests in the project directory by navigating into the `tests` +folder and executing `runtests` in MATLAB. However, if you want to omit the parallel +tests (e.g. you don't have the Parallel Toolbox installed), just execute ```matlab runtests Tag NonParallel diff --git a/tests/ProgressBar_test.m b/tests/ProgressBar_test.m index 292f6f8..8c0002c 100644 --- a/tests/ProgressBar_test.m +++ b/tests/ProgressBar_test.m @@ -155,7 +155,7 @@ function canPrintSimpleBar(testCase) unit = testCase.getUnit(); firstBar = evalc('unit([], [], [])'); - pause(0.2); + pause(0.5); secondBar = evalc('unit([], [], [])'); testCase.verifyTrue(contains(firstBar, 'Processing'));