Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added InputParser module #40

Merged
merged 5 commits into from
May 12, 2022
Merged

Added InputParser module #40

merged 5 commits into from
May 12, 2022

Conversation

EmilSoleymani
Copy link
Collaborator

InputParser.jl

Added InputParser module. Module contains the InputData struct and Model enum. Parses over input files (I placed it in vdisp/src/.data/input_data.dat, can be changed of course. It is made to be very robust. Empty lines, extra spaces, extra tabs are all fine as long as all the info expected to be read in a certain line is in the same line. There are some comments labelled as TODO which relate to robustness that will have to be addressed.

Vdisp.jl

Added code to include the InputParser module and use it to create a new InputData instance containing the information parsed from file at INPUT_DATA_PATH.

Runtests.jl

Added line that executes the parsing. Added TODO comment reminding us to come up with some test cases for reading inputs, and an idea of how to get started

@EmilSoleymani
Copy link
Collaborator Author

For some reason there was an error when trying to use contains function with Julia 1.3, but updating Documenter and Runtests to setup Julia 1.7 fixed this. When I was copying over the Documenter.yml and Runtests.yml last week from the video without understanding things I guess I overlooked the version. I'm glad to have read over those files again with a much better understanding of what's going on.

@EmilSoleymani
Copy link
Collaborator Author

Interesting note about the first error. Although I defined the function readInputFile in vdisp.jl, which is the function that creates new InputData instance from a given file path, the file path had to be given relative to runtests.jl. This was the change I made in commit bed4195 .

module InputParser

# Julia implicitly numbers Enum items 0,1,2,3,4 (in order)
# I put them in same order as FORTRAN code (i.e. NOPT=0 meant
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct that it definitely doesn't matter. We never want to look at the integer value of an Enum.

return currentLineData
end

# TODO: Search for error handling (throws Exception/ try-catch block)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we want to be as robust as possible. We should eventually add test cases that verify that the error handling works. We will also be adding constraints on the inputs, since some of them will have a range of physically meaningful values. (For instance Poisson's ration has to lie between 0.5 and 1.0. Anything outside that range is an error. We should check the validity of the values on input (and we should have test cases to check we implemented everything correctly.)

# struct for InputData (OOP in Julia)
struct InputData
problemName::String
numProblems::Int
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Julia have a natural number type? (It probably doesn't, but sometimes when we say integer, we really mean a natural number (or whole number), since negative values don't make sense.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is type UInt, I can change all counter variables to UInt

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, lets use an unsigned int when the data cannot take a negative value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UInt seems to print hex values by default

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, just go back to using integers. I did a bit of a google, and it isn't worth it to have to convert to decimal. We won't actually display these values very often, but it will be confusing when we do. I think uint is added to Julia for saving memory, not to provide an actual natural number type. We might as well do what everybody else does and use integers to represent what we know are natural numbers. 😄

problemName::String
numProblems::Int
model::Model
foundation::Bool
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the use of a bool. What does foundation = True mean? I'm wondering if this is a case where it is an enum with two choices, or legitimately a Boolean.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an enum with two choices. Currently true = rectangular slab foundation, false = long strip footing

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I change it to an enum? It was a bit unclear since your first sentence seemed to like it being a boolean

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on your answer to my question, yes, you should change it to an enum. I do like seeing a Boolean, because so many programmers seem to avoid them, especially in research software. I liked seeing something represented as a Boolean, but then when I thought about it, I had to question whether this was actually a Boolean. 😄 We might add a different type of foundation in the future, and then a Boolean would be problematic. An enum is also better for self-documenting the code. Otherwise you have to remember whether True is rectangular or long strip.

Copy link
Owner

@smiths smiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good @EmilSoleymani, but I do have a few questions:

  1. How can I test your code? I would like to test code before I accept a pull request. I can run the InputParser module at the prompt via: julia InputParser.jl. The lack of output lets me know there are no syntax errors, but I don't actually know if it worked. What did you do to convince yourself of correctness? I also tried julia vdisp.jl. The code has print statements, but nothing was printed to my output. (I know you are planning on adding test cases later, but I'd like something (even something ad hoc) to help me with the review now.)
  2. It isn't a priority now, but we'll need documentation in the files that will automatically be added to the web-page. You should get in the habit of incorporating some minimal header information in each code file, including the author's name, last date of revision and the module's purpose.
  3. I wonder if we should split InputParser.jl into two modules. One to read the input from a file and another class that creates instances of InputData. The struct where you define the input data is encapsulated with reading the file, but we might get our input data from another source. (This is why I like to talk about likely changes.) 😄 We could have a graphical interface that gives us a new instance of InputData. We could also have a program that changes InputData systematically. Maybe we modify the size of the footings as part of an optimization algorithm? It would be good to be able to do this.
  4. How does Julia handle changing state variables? I know they are tedious, but I prefer setters and getters to violating information hiding. (I really should look at the Julia sources you listed in the wiki; I just haven't had time.)
  5. It is fine to have the hidden input file right now, but in the long term, the input filename would likely be something that the user selects, rather than a "hard coded" filename.

@smiths
Copy link
Owner

smiths commented May 12, 2022

Interesting note about the first error. Although I defined the function readInputFile in vdisp.jl, which is the function that creates new InputData instance from a given file path, the file path had to be given relative to runtests.jl. This was the change I made in commit bed4195 .

How do I run runtest.jl?

@EmilSoleymani
Copy link
Collaborator Author

Since I only execute the readInputFile function in runtests.jl, you can execute julia runtests.jl. This actually has already been executing during the automatic testing, here is the output:
image
While developing the code I printed out each value that was parsed, and everything was good. And when files are missing info there are graceful terminations with a message of exactly which line was a problem. In the readInputFile function you can add your own println statement to print out any additional variables from the InputData instance that you would like to inspect. I just chose to print the more important ones.

@smiths
Copy link
Owner

smiths commented May 12, 2022

julia runtests.jl does not work for me. I get this output:

(base) smiths@x86_64-apple-darwin13 test % julia runtests.jl
ERROR: LoadError: ArgumentError: Package vdisp not found in current path:
- Run `import Pkg; Pkg.add("vdisp")` to install the vdisp package.

The problem is obviously about importing vdisp. Do I have to do this manually with the package manager before running runtests? (Again, I probably should look at the Julia material, but at the moment, it is quicker to ask you.) 😄

@EmilSoleymani
Copy link
Collaborator Author

I forgot that runtests.jl only can access vdisp after the process of adding all dependencies, activating the project in the Julia environment,.... (This is what our runtests.yml automates). A much easier solution is to just make the readInputFile execute in vdisp.jl and run julia vdisp.jl:
image

Added readInputFile in last line

@smiths
Copy link
Owner

smiths commented May 12, 2022

Yes, I can run the code now by following your last advice. Thank you.

if size(currentLineData)[1] < items
println("Error: Invalid input file!")
println("\t>Line $(index): $(items) values expected")
return -1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to raise an Exception here, rather than use a convention that returning -1 means something went wrong?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I plan on learning proper error handling / exception throwing soon. Part of the TODO comments. Would you like that as the step?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you don't have to add exception handling right now. Rather than just relying on the TODO comment though, please create an issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new issue added: #41

@EmilSoleymani
Copy link
Collaborator Author

Commenting on adding getters and setters. Adding keyword mutable in front of struct allows us to change the values. Getters and setters are given by default in Julia through the getfield and setfield! functions. Here is a simple example I was testing with:

module CircleModule
include("./Point.jl")
using .PointModule
mutable struct Circle
    r::Float64
    center::Point 
    Circle(r::Float64, center::Point) = new(r,center)
end

intersects(a::Circle, b::Circle) = (dist(a.center, b.center) <= a.r + b.r)

A = Circle(1.5, Point(1.2, 1.5))
B = Circle(0.334, Point(1.9, 1.4))
println(intersects(A, B))
println(getfield(A, :r))
println(setfield!(A, :r, 5.4))

end

Note the weird syntax. getfield takes in the instance of a mutable struct we want to read from and then you give it what is called a Symbol, which is simply a colon, :, followed by the property you want to read. Same with setfield with the added argument for the value you want to pass in.

@EmilSoleymani
Copy link
Collaborator Author

Further commenting on getters and setters provided by Julia. There is another pair of functions getproperty and setproperty!. They do the same thing by default, however we are allowed to overload them to do what we want (not sure why this would be useful, read here for more info)

@smiths
Copy link
Owner

smiths commented May 12, 2022

It seems like getproperty is more general. People get in the habit that the getters map directly to field variables in the class. This doesn't have to be the case. In some instances you don't want it to be the case. An example I often use is for polar coordinates. For a class that stores polar coordinates, the state variables (field variables) could be an x and y pair. However, the getters would be getproperty(theta) and getproperty(radius). (I know I used the wrong syntax.) 😄. Since we currently have a one to one mapping between getters and our field variables, we can use the simpler version in Julia.

@smiths
Copy link
Owner

smiths commented May 12, 2022

@EmilSoleymani, I am going to accept this PR. The code is working and it is definitely a step in the right direction. Can you please create issues for the comments I made that haven't been addressed, like splitting the module into two distinct modules. I will not delete the branch. If you would rather move to a new branch, you can delete it.

@smiths smiths self-requested a review May 12, 2022 20:36
Copy link
Owner

@smiths smiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned elsewhere, I'll approve this pull request. Please create issues for the outstanding tasks.

@smiths smiths merged commit 12d6572 into main May 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants