Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: Bug Report
about: Report a bug to help us improve
title: "[BUG] "
labels: bug
assignees: ""
---

## Description

A clear and concise description of the bug.

## Steps to Reproduce

1. ...
2. ...
3. ...

## Expected Behavior

What you expected to happen.

## Actual Behavior

What actually happened. Include error messages and tracebacks if applicable.

## Environment

- OS: [e.g., macOS 14, Ubuntu 22.04]
- Python version: [e.g., 3.11.5]
- steer-materials version: [e.g., 0.2.11]

## Additional Context

Any other context about the problem.
23 changes: 23 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: Feature Request
about: Suggest an idea for this project
title: "[FEATURE] "
labels: enhancement
assignees: ""
---

## Problem

A clear description of the problem or use case this feature would address.

## Proposed Solution

A clear description of what you'd like to happen.

## Alternatives Considered

Any alternative solutions or features you've considered.

## Additional Context

Any other context, references, or screenshots about the feature request.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ scripts/
dist/
Archive/
app_builds/
.vscode/
.venv/
.pytest_cache/
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ authors:
orcid: https://orcid.org/0000-0002-0755-3981
repository-code: https://github.com/stanford-developers/steer-materials
license: AGPL-3.0-or-later
version: 0.1.39
version: "0.1.44"
doi:
date-released: '2026-03-17'
date-released: '2026-06-10'
keywords:
- battery
- materials
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,4 @@ This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.

## License

By contributing, you agree that your contributions will be licensed under the [AGPL-3.0](LICENCE.txt) license and, per the CLA, may also be distributed under the project's commercial license.
By contributing, you agree that your contributions will be licensed under the [AGPL-3.0](LICENSE) license and, per the CLA, may also be distributed under the project's commercial license.
2 changes: 1 addition & 1 deletion LICENCE.txt → LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
STEER Materials - Dual Licensed

Copyright (c) 2024-2026 Nicholas Siemons
Copyright (c) 2024-2026 Stanford University

1. Open-source license: GNU Affero General Public License v3.0 (AGPL-3.0)
See the full AGPL-3.0 license text below.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pytest --cov=steer_materials

## Documentation

Full documentation for the STEER ecosystem is available at the [STEER documentation site](https://github.com/stanford-developers).
Full documentation for this package is available at [stanford-developers.github.io/steer-materials](https://stanford-developers.github.io/steer-materials/).

## Contributing

Expand All @@ -69,4 +69,4 @@ This project is dual-licensed:
1. **Open source** — [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0) (AGPL-3.0-or-later)
2. **Commercial** — A separate commercial license is available for use without AGPL-3.0 copyleft requirements. Contact [nsiemons@stanford.edu](mailto:nsiemons@stanford.edu) for details.

See [LICENCE.txt](LICENCE.txt) for full terms.
See [LICENSE](LICENSE) for full terms.
16 changes: 16 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Security Policy

## Supported Versions

Only the latest released version of steer-materials receives security updates.

## Reporting a Vulnerability

Please do **not** open a public GitHub issue for security vulnerabilities.

Instead, report them privately by emailing [nsiemons@stanford.edu](mailto:nsiemons@stanford.edu)
with a description of the issue, steps to reproduce, and any relevant details.

You should receive an acknowledgement within a few business days. Once the issue
is confirmed and a fix is available, we will coordinate disclosure and credit
you in the release notes unless you prefer to remain anonymous.
2 changes: 1 addition & 1 deletion docs/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ steer-materials/
├── docs/ # MkDocs documentation (this site)
├── .github/workflows/ # CI: tests + linting
├── pyproject.toml # Build config, dependencies, tool settings
├── LICENCE.txt # Dual license (AGPL-3.0 + commercial)
├── LICENSE # Dual license (AGPL-3.0 + commercial)
├── CITATION.cff # Citation metadata
├── CONTRIBUTING.md # Contribution guidelines
└── CODE_OF_CONDUCT.md # Contributor Covenant v2.1
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "steer-materials"
dynamic = ["version"]
description = "Modelling energy storage from cell to site - STEER OpenCell Design"
description = "Base material classes for the STEER OpenCell platform."
readme = "README.md"
requires-python = ">=3.10"
license = {text = "AGPL-3.0-or-later"}
Expand All @@ -31,7 +31,7 @@ classifiers = [
]

dependencies = [
"steer-core==0.2.12",
"steer-core==0.2.18",
]

[project.optional-dependencies]
Expand All @@ -58,7 +58,7 @@ docs = [
Homepage = "https://github.com/stanford-developers/steer-materials/"
Repository = "https://github.com/stanford-developers/steer-materials/"
"Bug Tracker" = "https://github.com/stanford-developers/steer-materials/issues"
Documentation = "https://github.com/stanford-developers"
Documentation = "https://stanford-developers.github.io/steer-materials/"

[tool.setuptools]
package-dir = {"" = "."}
Expand Down
22 changes: 10 additions & 12 deletions steer_materials/Base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024-2026 Nicholas Siemons
# SPDX-FileCopyrightText: 2024-2026 Stanford University
# SPDX-License-Identifier: AGPL-3.0-or-later

from steer_core.Constants.Units import *
Expand All @@ -9,8 +9,6 @@

from datetime import datetime as dt

import numpy as np


class _Material(
ValidationMixin,
Expand Down Expand Up @@ -64,14 +62,14 @@ def _update_ranges(self):
@property
def density(self) -> float:
"""Density of the material in g/cm³."""
return np.round(self._density * (KG_TO_G / M_TO_CM**3), 2)
return self._density * (KG_TO_G / M_TO_CM**3)

@property
def density_range(self) -> tuple[float, float]:
"""Soft range for density (±10% of current value) in g/cm³."""
return (
np.round(self._density_range[0] * (KG_TO_G / M_TO_CM**3), 2),
np.round(self._density_range[1] * (KG_TO_G / M_TO_CM**3), 2),
self._density_range[0] * (KG_TO_G / M_TO_CM**3),
self._density_range[1] * (KG_TO_G / M_TO_CM**3),
)

@property
Expand All @@ -82,14 +80,14 @@ def density_hard_range(self):
@property
def specific_cost(self):
"""Cost per kilogram in $/kg."""
return np.round(self._specific_cost, 2)
return self._specific_cost

@property
def specific_cost_range(self) -> tuple[float, float]:
"""Soft range for specific cost (0.5x–2x current value) in $/kg."""
return (
np.round(self._specific_cost_range[0], 2),
np.round(self._specific_cost_range[1], 2),
self._specific_cost_range[0],
self._specific_cost_range[1],
)

@property
Expand Down Expand Up @@ -273,23 +271,23 @@ def __init__(
def volume(self) -> float | None:
"""Volume in cm³, or `None` if not set."""
if hasattr(self, '_volume') and self._volume is not None:
return np.round(self._volume * (M_TO_CM**3), 2)
return self._volume * (M_TO_CM**3)
else:
return None

@property
def mass(self) -> float | None:
"""Mass in grams, or `None` if not set."""
if hasattr(self, '_mass') and self._mass is not None:
return np.round(self._mass * KG_TO_G, 2)
return self._mass * KG_TO_G
else:
return None

@property
def cost(self) -> float | None:
"""Cost in dollars, or `None` if not set."""
if hasattr(self, '_cost') and self._cost is not None:
return np.round(self._cost, 2)
return self._cost
else:
return None

Expand Down
4 changes: 2 additions & 2 deletions steer_materials/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-FileCopyrightText: 2024-2026 Nicholas Siemons
# SPDX-FileCopyrightText: 2024-2026 Stanford University
# SPDX-License-Identifier: AGPL-3.0-or-later

__version__ = "0.1.39"
__version__ = "0.1.44"

17 changes: 8 additions & 9 deletions test/test_material.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pytest
import numpy as np
from steer_materials.Base import _Material, Metal, Solvent


Expand All @@ -8,14 +7,14 @@ class TestMaterialConstruction:

def test_metal_valid_construction(self, aluminum):
assert aluminum.name == "Aluminum"
assert aluminum.density == 2.7
assert aluminum.specific_cost == 2.50
assert aluminum.density == pytest.approx(2.7, rel=1e-10)
assert aluminum.specific_cost == pytest.approx(2.50, rel=1e-10)
assert aluminum.color == "silver"

def test_solvent_valid_construction(self, water):
assert water.name == "Water"
assert water.density == 1.0
assert water.specific_cost == 0.01
assert water.density == pytest.approx(1.0, rel=1e-10)
assert water.specific_cost == pytest.approx(0.01, rel=1e-10)
assert water.color == "clear"

def test_negative_density_raises(self):
Expand Down Expand Up @@ -54,11 +53,11 @@ class TestMaterialProperties:
def test_density_returns_g_per_cm3(self):
# Input 2.7 g/cm³ should come back as 2.7
m = Metal(name="Al", density=2.7, specific_cost=1.0, color="grey")
assert m.density == 2.7
assert m.density == pytest.approx(2.7, rel=1e-10)

def test_specific_cost_returns_per_kg(self):
m = Metal(name="Al", density=2.7, specific_cost=3.45, color="grey")
assert m.specific_cost == 3.45
assert m.specific_cost == pytest.approx(3.45, rel=1e-10)

def test_density_round_trip(self):
# Verify internal storage → getter round-trips correctly
Expand All @@ -78,11 +77,11 @@ class TestMaterialSetters:

def test_set_density(self, aluminum):
aluminum.density = 5.0
assert aluminum.density == 5.0
assert aluminum.density == pytest.approx(5.0, rel=1e-10)

def test_set_specific_cost(self, aluminum):
aluminum.specific_cost = 10.0
assert aluminum.specific_cost == 10.0
assert aluminum.specific_cost == pytest.approx(10.0, rel=1e-10)

def test_set_name(self, aluminum):
aluminum.name = "Steel"
Expand Down
36 changes: 18 additions & 18 deletions test/test_volumed_material.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ class TestVolumedMaterialInit:
"""Tests for _VolumedMaterialMixin initialization."""

def test_volume_init_derives_mass_and_cost(self, volumed_al):
assert volumed_al.volume == 100.0
assert volumed_al.mass == 270.0
assert volumed_al.cost == 0.68
assert volumed_al.volume == pytest.approx(100.0, rel=1e-10)
assert volumed_al.mass == pytest.approx(270.0, rel=1e-10)
assert volumed_al.cost == pytest.approx(0.675, abs=1e-5)

def test_mass_init_derives_volume_and_cost(self, mass_al):
assert mass_al.volume == 100.0
assert mass_al.mass == 270.0
assert mass_al.cost == 0.68
assert mass_al.volume == pytest.approx(100.0, rel=1e-10)
assert mass_al.mass == pytest.approx(270.0, rel=1e-10)
assert mass_al.cost == pytest.approx(0.675, abs=1e-5)

def test_no_volume_or_mass_gives_none(self, bare_al):
assert bare_al.volume is None
Expand All @@ -65,15 +65,15 @@ class TestVolumedMaterialSetters:

def test_set_volume(self, bare_al):
bare_al.volume = 100.0
assert bare_al.volume == 100.0
assert bare_al.mass == 270.0
assert bare_al.cost == 0.68
assert bare_al.volume == pytest.approx(100.0, rel=1e-10)
assert bare_al.mass == pytest.approx(270.0, rel=1e-10)
assert bare_al.cost == pytest.approx(0.675, abs=1e-5)

def test_set_mass(self, bare_al):
bare_al.mass = 270.0
assert bare_al.volume == 100.0
assert bare_al.mass == 270.0
assert bare_al.cost == 0.68
assert bare_al.volume == pytest.approx(100.0, rel=1e-10)
assert bare_al.mass == pytest.approx(270.0, rel=1e-10)
assert bare_al.cost == pytest.approx(0.675, abs=1e-5)

def test_set_volume_to_none_clears_all(self, volumed_al):
volumed_al.volume = None
Expand Down Expand Up @@ -110,16 +110,16 @@ class TestVolumedMaterialPropagation:
def test_density_change_updates_mass_and_cost(self, volumed_al):
# volume=100 cm³, density 2.7→5.0 g/cm³
volumed_al.density = 5.0
assert volumed_al.volume == 100.0
assert volumed_al.mass == 500.0
assert volumed_al.cost == 1.25
assert volumed_al.volume == pytest.approx(100.0, rel=1e-10)
assert volumed_al.mass == pytest.approx(500.0, rel=1e-10)
assert volumed_al.cost == pytest.approx(1.25, rel=1e-10)

def test_specific_cost_change_updates_cost(self, volumed_al):
# mass=270g, specific_cost 2.50→5.0 $/kg
volumed_al.specific_cost = 5.0
assert volumed_al.volume == 100.0
assert volumed_al.mass == 270.0
assert volumed_al.cost == 1.35
assert volumed_al.volume == pytest.approx(100.0, rel=1e-10)
assert volumed_al.mass == pytest.approx(270.0, rel=1e-10)
assert volumed_al.cost == pytest.approx(1.35, rel=1e-10)

def test_density_change_no_effect_without_volume(self, bare_al):
bare_al.density = 5.0
Expand Down
Loading