Skip to content

Commit 39a4087

Browse files
authored
Merge pull request #55 from Saransh-cpp/saransh/more-docs
docs: update some docstrings and readme structure
2 parents a900ba1 + 11a9ee5 commit 39a4087

11 files changed

Lines changed: 144 additions & 82 deletions

File tree

.clang-tidy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ Checks: >
1515
google-*
1616
CheckOptions:
1717
- key: readability-function-cognitive-complexity.Threshold
18-
value: '37'
18+
value: '40'
1919
ExtraArgs: ['-std=c++20']
2020
WarningsAsErrors: '*'

README.md

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
ROOT, but not the [particle physics one](https://github.com/root-project/root). Project submission for MATH-458: Programming concepts in scientific computing.
99

10-
## Project structure and dependencies
10+
The project bundles a header-only C++ library (`libROOT`) implementing root-finding algorithms and a CLI application (`root_cli`) to read and parse input, run the algorithms implemented in libROOT, and write the output to a file of specific format.
11+
12+
## Project structure
1113

1214
The project uses the recommended [Canonical Project Structure](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1204r0.html) for C++ projects.
1315

@@ -37,40 +39,66 @@ The project uses the recommended [Canonical Project Structure](https://www.open-
3739
└── tests # Tests for the ROOT library
3840
```
3941

40-
### Dependencies
42+
Apart from being divided into a library and a user-facing application/executable, the design on the project
43+
is concretely split into four phases.
44+
45+
### CLI
46+
47+
The CLI application is written (and should be written) within the `main` function. The `main` function further calls the `Reader`, `Solver`, and `Writer` classes (in this order) on the input passed through the CLI application.
48+
49+
### Readers and Parsers
50+
51+
Reading and parsing is handled by the `ReaderBase` and `FunctionParserBase` daughter classes. Adding a new reading method should include writing a new `ReaderBase` daughter class and adding functionality to parse a new type of function should include writing a new `FunctionParserBase` daughter class. The information read is stored by the `ConfigBase` daughter classes (these are data classes to be specific, and can ideally by just structs, but they use some object-oriented features, requiring them to be classes). Adding a new stepper type should include adding a new `ConfigBase` daughter class. The `read` method of the `ReaderBase` daughter classes accept a pointer of the type `CLI::App` and return a pointer to an object of the type of one of the daughter classes of `ConfigBase`. The `Reader` classes further implement helper methods for reading and parsing different things, and functions for constructing the `ConfigBase` object itself. Similarly, the `parse` function of the `FunctionParser` classes takes in a `string` and returns a C++ function (parses a specific type of function). The classes also include helper methods for parsing functions, and a method (in `FunctionParserBase`) to infer the type of the function (from the string) and dispatch the appropriate daughter class objects (and methods) to parse the function. In the future, it would make sense to add a wrapper around `Reader` classes to abstract `Config` creation and pointer manipulation from the `main` function (just how it is done by the `Solver` and `Writer` classes).
52+
53+
### Solver and Steppers
54+
55+
Solving non-linear equation is completely handled by two classes: `Solver` and `StepperBase`. `StepperBase` has specialized child classes for each method (for now: Newton-Raphson, Bisection, Chords, Fixed Point).
56+
The `Solver` class is constructed with the data stored in `ConfigBase` child classes, and has methods to manage the high-level API involved in solving an equation. The `solve` method of the `Solver` class comprises of multiple internal calls, mainly involving convergence check, results saving, instantiating an object of one of the specialized `StepperBase` child classes, and calling the relevant method to compute single step of the numerical method.
57+
58+
`Solver` has no child classes but it could be refactored to be child of a `SolverBase` class (refactoring and abstracting common steps, such as the convergence check and the solve loop). The refactored `SolverNonLinear` class would inherit all the methods from the abstract class and add arguments for the functions and the boolean to require Aitken's acceleration. The new `SolverNonLinear` could have child classes for solving single equations (our current `Solver`) or systems of equations, which would differ just in the type of the arguments saved (e.g. derivative/jacobian for Newton-Raphson). This draft idea, which could be substituted by a fully templated version of the `SolverNonLinear` class, comes from the fact that templating is already used to define the different kinds of initial guesses allowed, and it is not possible (in C++) to partially specialize different templates. Another more brute-force idea could be to define all the different arguments as matrices and then use them as 1 X 1 matrices (or vectors) for the single equation case, without creating two daughter classes. All of these ideas would have to be adapted for the `Stepper` classes too.
4159

42-
#### Dependencies for the project
60+
`Solver::solve` declares a `StepperBase` pointer and later instantiates it to point to an object of one of its child class, passing down all the required arguments to use for a single step computation. The only public method executed by the `Stepper`s is `compute_step`, which computes a single step of the numerical method and returns the results. To allow more numerical methods, it is possible to simply define new child classes with different `compute_step` algorithms and potentially different arguments to store.
4361

44-
##### Required
62+
### Writer and Printers
63+
64+
The writing part of the project is handled by two classes: `Writer` and `PrinterBase`, with `PrinterBase` having child classes for each output type. The output type and other relevant information is carried down through the `ConfigBase` classes (defined by the `Reader`s). Importantly, these classes are not only defined for our specific project, but can write anything correctly passed (potentially with slight refactoring of the code). The classes' methods are implemented just for the type required in our project, but different typed versions would be easy to add.
65+
66+
`Writer` stores what to write and how, the writing method, and implements a high-level `write` method to instantiate an object of one of the `PrinterBase` child classes. `PrinterBase` has child classes specialized for certain output types, all of which have an overwritten `write_values` method which prints a given input on a stored output. To allow different writing destinations, new child classes can be defined inheriting from the existing ones.
67+
68+
## Dependencies
69+
70+
### Dependencies for the project
71+
72+
#### Required
4573

4674
The required dependencies are included within the project as git submodules and are pinned to specific
4775
versions for reproducibility.
4876

4977
- `CLI11` (`v2.6.1`): for constructing the CLI interface for the user-facing `root_cli` application.
5078
- `Eigen3` (`v5.0.1`): for matric and vector usage / calculations.
5179

52-
##### Optional
80+
#### Optional
5381

5482
These can be installed by a user and are not installed through the project's build system.
5583

5684
- `gnuplot`: for plotting results
5785

58-
#### Required dependencies for the tests
86+
### Required dependencies for the tests
5987

6088
`gnuplot` must be installed before building the project with `-DTEST=ON`. `GoogleTest` is installed automatically if the project is built with `-DTEST=ON`.
6189

6290
- `GoogleTest` (`v1.17.0`): for all tests.
6391
- `gnuplot`: for testing `gnuplot` related code.
6492

65-
#### Dependencies for the documentation
93+
### Dependencies for the documentation
6694

6795
These can be installed by a user and are not installed through the project's build system.
6896

69-
##### Required
97+
#### Required
7098

7199
- `doxygen`: for generating the documentation.
72100

73-
##### Optional
101+
#### Optional
74102

75103
- `graphviz`: for generating hierarchy and flow diagrams in the documentation.
76104

@@ -142,7 +170,8 @@ root_cli <arguments>
142170
In order to print out more information about the arguments and the subcommands:
143171

144172
```
145-
root_cli <arguments>
173+
root_cli --help
174+
root_cli <subcommand> --help
146175
```
147176

148177
Every additional needed function must be added together with the function to find the root of.
@@ -154,60 +183,63 @@ Here's a list of examples of possible execution syntax:
154183
root_cli --wcli cli --function "x^2-4" newton --initial 1.0 --derivative "2*x"
155184
```
156185
157-
- DAT input file called input.dat with first row not being header and " " separating different values, .dat file output called output.dat, Bisection method to find the root of x^3-1, with initial interval [-2,2], verbose output (given tolerance and maximum iterations):
186+
- DAT input file called input.dat, DAT output file called output.dat, Bisection method to find the root of x^3-1, with initial interval [-2,2], and verbose output (given tolerance and maximum iterations):
158187
159188
```
160-
root_cli --verbose --wdat output dat input
189+
root_cli --verbose --wdat output dat --file input.dat
161190
```
162191
163192
where input.dat is:
164193
165194
```
166195
function = x^3-1
167196
method = bisection
168-
initial = -1
197+
interval_a = -2
198+
interval_b = 2
169199
tolerance = 1e-5
170200
max-iterations = 100
171201
derivative = 2*x
172202
```
173203
174-
- CSV input file called input.csv with first row which is a header and "," separating different values, .csv file ouput
175-
called output.csv, Fixed Point Method to find the root of cos(x), with
176-
initial guess 0.5, fixed point function cos(x):
204+
- CSV input file called input.csv with first row which is a header and "," separating different values, CSV output file called output.csv, Fixed Point Method to find the root of x^2-x, with initial guess 0.5, fixed point function x^2, and verbose output (given tolerance and maximum iterations)::
177205
178206
```
179-
root_cli --wcsv output --ocsvsep , csv input --sep , --header
207+
root_cli --verbose --wcsv output --ocsvsep , csv --file input.csv --sep ,
180208
```
181209
182210
where input.csv is:
183211
184212
```
185213
function,method,initial,tolerance,max_iterations,g-function
186-
'cos(x)',fixed_point,0.5,1e-5,100,'cos(x)'
214+
x^2-x,fixed_point,0.5,1e-65,100,x^2
187215
```
188216
189-
- CLI input, .dat output file called output.dat and moreover a GNU Plot is created from it as output.png. Chords method to solve
190-
the equation x^3-8 starting from the two initial points 1 and 3:
217+
- Same as above but with aitken acceleration (will converge faster):
191218
192219
```
193-
root_cli --wdat output --wgnuplot output cli --function x^3-8 chords --x0 --x1 3
220+
root_cli --verbose --wcsv output --ocsvsep , csv --file input.csv --sep ,
194221
```
195222
196-
The installed CLI application can simply be used by:
223+
where input.csv is:
197224
198-
```
199-
$ <install_path>/bin/root_cli
200-
# or just root_cli if installed in /usr/local/bin/ on unix for instance
201-
```
225+
```
226+
function,method,initial,tolerance,max_iterations,g-function,aitken
227+
x^2-x,fixed_point,0.5,1e-5,100,x^2,true
228+
```
202229
203-
And the shared library can be used inside `cxx` files using:
230+
- CLI input, DAT output file called output.dat, gnuplot writing method (a GNU Plot named output.png is created), Chords method to solve the equation x^3-8 starting from the two initial points 1 and 3:
204231
205-
```
206-
# pass the path of headers
207-
g++ <file>.cpp -o <executable_name> -I<install_path>/include
208-
```
232+
```
233+
root_cli --wgnuplot output cli --function x^3-8 chords --x0 1 --x1 3
234+
```
235+
236+
## Typical program execution
209237
210-
All of which can also be set in `CMakeLists.txt`.
238+
Input reading is handled by a CLI implemented using `CLI11`, which passes the read options to the appropriate `ReaderBase` daughter class. The `read` method of the `ReaderBase` daughter classes construct and return a `ConfigBase` daughter class object. The `ReaderBase` daughter classes also use the `FunctionParserBase` daughter classes internally to parse the function (and derivation + g function) inputted by user (string to a C++ function). The information stored in `ConfigBase` daughter classes is then passed down to the `Solver` class to run the algorithm.
239+
240+
The `solve` method of `Solver` constructs a `StepperBase` child class object, handles its methods calls, and finally returns the matrix of the results of the computation performed. `compute_step` method of each `StepperBase` child class gets the previous iteration and computes and returns the new guess, which will be saved and checked by `Solver`'s methods. The final results returned by `solve` are then passed down to the `Writer` class to write them.
241+
242+
The `write` method of `Writer` construct a `PrinterBase` child class object, and handles its methods calls. `write_values` method of each `StepperBase` child class gets a certain value to be printed and prints it out in a defined destination.
211243
212244
## Tests
213245
@@ -315,4 +347,14 @@ which will write the HTML files to `docs/html`.
315347

316348
### Building docs on GH Pages
317349

318-
The documentation is automatically built (on every PR) and deployed (on every push to `main`) to GH Pages using the `build-and-deploy-docs` workflow.
350+
The documentation is automatically built (on every PR) and deployed (on every push to `main` here - [https://saransh-cpp.github.io/ROOT/](https://saransh-cpp.github.io/ROOT/)) to GH Pages using the `build-and-deploy-docs` workflow.
351+
352+
## Limitations and problems
353+
354+
Most of the limitations and problems can be found as independent issues in the [issue tracker on GitHub](https://github.com/Saransh-cpp/ROOT/issues), or in the previous Project Structure section.
355+
356+
## Authors and their contributions
357+
358+
- **Andrea Saporito** ([@andreasaporito](https://github.com/andreasaporito)): Stepper, Solver, Writer, Printer classes/functionalities, most of the integration tests and some unit tests, and some fixes/refactoring here and there (touching Reader and Parser classes/functionalities, and the build system).
359+
360+
- **Saransh Chopra** ([@Saransh-cpp](https://github.com/Saransh-cpp)): Top-level CLI executable/application, Reader and Parser classes/functionalities, Project infrastructure (build system {code, docs, tests}, project structure, CI/CD), most of the unit tests and some integration tests, and some refactoring here and there (touching Stepper, Solver, Writer, and Printer classes/functionalities).

ROOT/ROOT/config.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
* including Bisection, Newton, Secant, and Fixed Point methods. Each configuration
77
* class encapsulates the parameters required for its respective method.
88
*
9+
* This file was written with constant LLM assistance (vibe coded). I built
10+
* the structure and logic, and the LLM helped fill in the details.
11+
*
912
* @author Saransh-cpp
1013
*
1114
*/

ROOT/ROOT/function_parser.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
* including polynomial and trigonometric functions. The parsers convert string representations
77
* of functions into callable std::function<double(double)> objects.
88
*
9+
* This file was written with constant LLM assistance (vibe coded). I built
10+
* the structure and logic, and the LLM helped fill in the details.
11+
*
912
* @author Saransh-cpp
1013
*/
1114
#ifndef FUNCTION_HPP

ROOT/ROOT/main.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ int main(int argc, char** argv) {
5959
csv->add_option("--sep", csv_sep, "Separator character for CSV file")->capture_default_str();
6060
char csv_quote = '"';
6161
csv->add_option("--quote", csv_quote, "Quote/delimiter character for CSV file")->capture_default_str();
62-
bool csv_header = true;
63-
csv->add_option("--header", csv_header, "Indicates whether the first row is a header row")->capture_default_str();
6462

6563
// DAT
6664
auto* dat = app.add_subcommand("dat", "Use DAT input");

ROOT/ROOT/reader.cpp

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ std::unique_ptr<ConfigBase> ReaderBase::make_config_from_map(
208208
std::exit(EXIT_FAILURE);
209209
}
210210
double initial = 0.0;
211+
if (!parseDouble(it_x0->second, initial)) {
212+
std::cerr << "\033[31mmake_config_from_map: invalid initial\033[0m\n";
213+
std::exit(EXIT_FAILURE);
214+
}
211215
auto g_function = FunctionParserBase::parseFunction(it_g->second);
212216
return std::make_unique<FixedPointConfig>(tolerance, max_iter, aitken, function, initial, g_function,
213217
verbose);
@@ -255,7 +259,6 @@ std::unique_ptr<ConfigBase> ReaderCSV::read(CLI::App* app, bool verbose) {
255259
this->filename = app->get_option("--file")->as<std::string>();
256260
this->sep = app->get_option("--sep")->as<char>();
257261
this->quote = app->get_option("--quote")->as<char>();
258-
this->has_header = app->get_option("--header")->as<bool>();
259262
std::ifstream ifs(filename);
260263
if (!ifs) {
261264
std::cerr << "\033[31mReaderCSV: failed to open file: " << filename << "\033[0m\n";
@@ -265,32 +268,19 @@ std::unique_ptr<ConfigBase> ReaderCSV::read(CLI::App* app, bool verbose) {
265268
std::string headerLine;
266269
std::string valueLine;
267270

268-
if (this->has_header) {
269-
if (!std::getline(ifs, headerLine)) {
270-
std::cerr << "\033[31mReaderCSV: empty file (expecting header)\033[0m\n";
271-
std::exit(EXIT_FAILURE);
272-
}
273-
if (!std::getline(ifs, valueLine)) {
274-
std::cerr << "\033[31mReaderCSV: missing value row\033[0m\n";
275-
std::exit(EXIT_FAILURE);
276-
}
277-
} else {
278-
if (!std::getline(ifs, valueLine)) {
279-
std::cerr << "\033[31mReaderCSV: empty file\033[0m\n";
280-
std::exit(EXIT_FAILURE);
281-
}
282-
headerLine.clear();
271+
if (!std::getline(ifs, headerLine)) {
272+
std::cerr << "\033[31mReaderCSV: empty file (expecting header)\033[0m\n";
273+
std::exit(EXIT_FAILURE);
274+
}
275+
if (!std::getline(ifs, valueLine)) {
276+
std::cerr << "\033[31mReaderCSV: missing value row\033[0m\n";
277+
std::exit(EXIT_FAILURE);
283278
}
284279

285280
std::vector<std::string> headers;
286-
if (this->has_header) {
287-
headers = splitCsvLine(headerLine);
288-
for (auto& header : headers) {
289-
header = trim(header);
290-
}
291-
} else {
292-
std::cerr << "\033[31mReaderCSV: no headers provided\033[0m\n";
293-
std::exit(EXIT_FAILURE);
281+
headers = splitCsvLine(headerLine);
282+
for (auto& header : headers) {
283+
header = trim(header);
294284
}
295285

296286
auto values = splitCsvLine(valueLine);
@@ -311,13 +301,8 @@ std::unique_ptr<ConfigBase> ReaderCSV::read(CLI::App* app, bool verbose) {
311301
config_map[key] = values[i];
312302
}
313303
} else {
314-
// positional mapping documented here:
315-
std::vector<std::string> posnames = {"method", "tolerance", "max-iterations", "aitken", "function",
316-
"derivative", "interval_a", "interval_b", "function-g", "initial",
317-
"x0", "x1"};
318-
for (size_t i = 0; i < values.size() && i < posnames.size(); ++i) {
319-
config_map[posnames[i]] = values[i];
320-
}
304+
std::cerr << "\033[31mReaderCSV: empty header row\033[0m\n";
305+
std::exit(EXIT_FAILURE);
321306
}
322307

323308
if (verbose) {

ROOT/ROOT/reader.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
* which are responsible for reading configuration data from various file formats
77
* (e.g., CSV, DAT) and producing ConfigBase objects.
88
*
9+
* This file was written with constant LLM assistance (vibe coded). I built
10+
* the structure and logic, and the LLM helped fill in the details.
11+
*
912
* @author Saransh-cpp
1013
*/
1114
#ifndef READER_HPP
@@ -31,7 +34,6 @@ class ReaderBase {
3134
std::string filename; //!< The input filename to read from.
3235
char sep; //!< Field separator character.
3336
char quote; //!< Quote/delimiter character.
34-
bool has_header; //!< Indicates whether the first row is a header row.
3537
/**
3638
* @brief Virtual destructor for ReaderBase.
3739
*

0 commit comments

Comments
 (0)