Skip to content

NRRI Mines - #840

Open
kbrunik wants to merge 11 commits into
NatLabRockies:developfrom
kbrunik:mines
Open

NRRI Mines#840
kbrunik wants to merge 11 commits into
NatLabRockies:developfrom
kbrunik:mines

Conversation

@kbrunik

@kbrunik kbrunik commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

NRRI Iron Range Mines Models

This is a different iron mine performance model that splits the mining process into separate steps that tracks the material flows and energy/fuel use on a per process step basis.

The cost model is based on SEC reports for iron ore pellets and crude ore extracted.

Section 1: Type of Contribution

  • Feature Enhancement
    • Framework
    • New Model
    • Updated Model
    • Tools/Utilities
    • Other (please describe):
  • Bug Fix
  • Documentation Update
  • CI Changes
  • Other (please describe):

Section 2: Draft PR Checklist

  • Open draft PR
  • Describe the feature that will be added
  • Fill out TODO list steps
  • Describe requested feedback from reviewers on draft PR
  • Complete Section 8: New Model Checklist (if applicable)

TODO:

  • Step 1
  • Step 2

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback:

Implementation feedback:

Other feedback:

Section 3: General PR Checklist

  • PR description thoroughly describes the new feature, bug fix, etc.
  • Added tests for new functionality or bug fixes
  • Tests pass (If not, and this is expected, please elaborate in the Section 6: Test Results)
  • Documentation
    • Docstrings are up-to-date
    • Related docs/ files are up-to-date, or added when necessary
    • Documentation has been rebuilt successfully
    • Examples have been updated (if applicable)
  • CHANGELOG.md
    • At least one complete sentence has been provided to describe the changes made in this PR
    • After the above, a hyperlink has been provided to the PR using the following format:
      "A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
      XYZ should be replaced with the actual number.

Section 4: Related Issues

Section 5: Impacted Areas of the Software

Section 5.1: New Files

  • iron/nrri_ore/perf_coeffs.csv: has the mass flow per process, energy and fuel consumption per process for each mine.
  • iron/nrri_ore/cost_coeffs.csv: has the SEC costs for each mine per LT.
  • iron/nrri_iron_mine.py
    • NRRIIronMinePerformanceComponent: This is a flexible dispatched iron mine. It calculates the energy and fuel consumption on a per process basis. The system can only be curtailed but does not take a iron_ore_command_value as an input.
    • NRRIIronMineCostComponent: This uses the cost_coeffs.csv to pull in the SEC costs per LT for each mine. It only outputs OpEx since it assumes CapEx is annualized into the operational costs.

Section 5.2: Modified Files

  • docs: Updated the iron_mine.md to include new models. @jmartin4u maybe you can update this based on conversations with NRRI? @kbrunik updated with sources from Kim's doc
  • supported_models.py
  • Renamed original ore model files to remove "Martin" designation and replace with "Simple"
    • converters/iron/martin_ore/: Renamed to `converters/iron/simple_ore/
    • converters/iron/martin_transport/: Renamed to `converters/iron/simple_transport/
    • converters/iron/test/test_martin_mine.py: Renamed to converters/iron/test/test_simple_mine.py
    • converters/iron/martin_mine_cost_model.py: Renamed to converters/iron/simple_mine_cost_model.py
    • converters/iron/martin_mine_perf_model.py: Renamed to converters/iron/simple_mine_perf_model.py

Section 6: Additional Supporting Information

Section 7: Test Results, if applicable

Section 8 (Optional): New Model Checklist

  • Model Structure:
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md
    • Used attrs class to define the Config to load in attributes for the model
      • If applicable: inherit from BaseConfig or CostModelBaseConfig
    • Added: initialize() method, setup() method, compute() method
      • If applicable: inherit from CostModelBaseClass
  • Integration: Model has been properly integrated into H2Integrate
    • Add the new model to the appropriate __init__.py file to ensure it is properly imported and used in supported_models.py
    • Added to supported_models.py
    • If a new commodity_type is added, update create_financial_model in h2integrate_model.py
  • Tests: Unit tests have been added for the new model
    • Pytest-style unit tests
    • Unit tests are in a "test" folder within the folder a new model was added to
    • If applicable add integration tests
  • Example: If applicable, a working example demonstrating the new model has been created
    • Input file comments
    • Run file comments
    • Example has been tested and runs successfully in test_all_examples.py
  • Documentation:
    • Write docstrings using the Google style
    • Model added to the main models list in docs/user_guide/model_overview.md
      • Model documentation page added to the appropriate docs/ section
      • <model_name>.md is added to the _toc.yml
    • Run generate_class_hierarchy.py to update the class hierarchy diagram in docs/developer_guide/class_structure.md

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@kbrunik
kbrunik marked this pull request as ready for review August 13, 2026 16:54
@kbrunik
kbrunik requested a review from jmartin4u August 13, 2026 16:54

@elenya-grant elenya-grant left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Pretty high-level review and mostly small stuff! I think this looks good! Suggestions were mainly for clarity and perhaps some simplicity - nothing crazy though! Will review again if you want!

@@ -0,0 +1,13 @@
units,process,Tilden,Hibbing,United,Minorca,Northshore
WLT/Yr,ROM Ore,60360000,60466666.67,38012500,16566666.67,28960000
WLT/Yr,Crushed Ore,20500000,28083000,14920000,8460000,16860000

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would it make sense to have these units expressed in OpenMDAO units? Or to have another units column that has the openmdao units? (long ton = 2240 lbs, just a note)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should this go in a doc page?

Comment on lines +49 to +50
prob.set_val("comp.electricity_in", [annual_electricity] * 8760, units="kW")
prob.set_val("comp.fuel_in", [annual_fuel] * 8760, units="MMBtu/h")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should these be divided by 8760? Like [annual_fuel/8760]*8760

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I changed the name, it was just a holdover from the other iron test

Comment on lines +55 to +56
int(plant_config["plant"]["plant_life"])
int(plant_config["plant"]["simulation"]["n_timesteps"])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

remove?

# check pellet production
with subtests.test("iron_ore_out"):
iron_ore_out = prob.get_val("comp.iron_ore_out", units=commodity_rate_units)
assert np.sum(iron_ore_out) == pytest.approx(7457805 * 0.98 * 1.016, rel=1e-3)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could ya comment somewhere what 0.98 and 1.016 are?

"""

max_ore_production_rate_tonnes_per_hr: float = field()
mine: str = field(validator=contains(["Hibbing", "Northshore", "United", "Minorca", "Tilden"]))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would it be easier to do lower-case and then have a converter converter=(str.lower, str.strip)

mine: str = field(converter=(str.lower, str.strip), validator=contains(["hibbing", "northshore", "united", "minorca", "tilden"]))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If you'd prefer it that way or have a justification for why this is better, I could change it? I don't really have strong feelings on the matter, it's just how it was done in the other mine model

Comment on lines +205 to +211
if system_capacity * 8760 > ref_pellets:
msg = (
f"System capacity of {system_capacity} t/h exceeds the reference pellet"
f" production of {ref_pellets} t/h."
f" This may lead to unrealistic results."
)
warnings.warn(msg, UserWarning)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would it not make sense to just scale the consumptions based on the ratio of system capacity and ref_pellets? Like scale_fac = (system_capacity/(8760)) / ref_pellets)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It does scale based on the system capacity, this is just a user warning if someone's trying to exceed the mines capacity for some reason, since I was assuming that wouldn't really be possible

Comment on lines +277 to +282
processed_ore_production = np.minimum.reduce(
[
processed_ore_from_fuel,
processed_ore_from_electricity,
]
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this kind of logic normally exists within technologies with a control classifier of dispatchable - but I think that it makes sense here even though this is a flexible tech. Just noting.

)

# mass flow through mining process
outputs["raw_ore"] = processed_ore_production * ref_raw_ore / ref_pellets

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is raw ore consumed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, raw ore, crushed ore, and concentrated ore are consumed within the model, that's why they follow a different convention instead of {commodity}_out. The tailings are available hence following convention

@kbrunik kbrunik added ready for review This PR is ready for input from folks and removed in progress labels Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review This PR is ready for input from folks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants