Skip to content
Open
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
10 changes: 8 additions & 2 deletions include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ set(Oasis_HEADERS
Oasis/Variable.hpp)

# Adds a header-only library target called "OasisHeaders"
add_library(OasisHeaders INTERFACE)
add_library(OasisHeaders INTERFACE
Oasis/UnitGraph.hpp
Oasis/GlobalUnitGraph.hpp)
add_library(Oasis::Headers ALIAS OasisHeaders)

target_compile_features(OasisHeaders INTERFACE cxx_std_20)
target_include_directories(
OasisHeaders INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
$<INSTALL_INTERFACE:include>
/opt/homebrew/include
)

target_compile_options(OasisHeaders INTERFACE -Wno-error=language-extension-token)

source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${Oasis_HEADERS})
14 changes: 14 additions & 0 deletions include/Oasis/GlobalUnitGraph.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Created by Jary Chen on 3/10/25.
//

#ifndef GLOBALUNITGRAPH_HPP
#define GLOBALUNITGRAPH_HPP
#include "UnitGraph.hpp"

namespace Oasis {
// Reference to singleton UnitGraph
UnitGraph& getGlobalUnitGraph();
}

#endif //GLOBALUNITGRAPH_HPP
16 changes: 15 additions & 1 deletion include/Oasis/Real.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
#define OASIS_REAL_HPP

#include "LeafExpression.hpp"
#include "Unit.hpp"
#include "GlobalUnitGraph.hpp"
#include <boost/units/systems/si.hpp>
#include <boost/units/io.hpp>
#include <boost/units/cmath.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/systems/si/io.hpp>
#include <boost/units/conversion.hpp>


namespace Oasis {

Expand All @@ -17,19 +26,22 @@ class Real : public LeafExpression<Real> {
Real() = default;
Real(const Real& other) = default;

explicit Real(double value);
explicit Real(double value, Unit unit = Unit::None);

[[nodiscard]] auto Equals(const Expression& other) const -> bool final;

EXPRESSION_TYPE(Real)
EXPRESSION_CATEGORY(UnExp)

auto ConvertTo(Unit targetUnit) const -> Real;
/**
* Gets the value of the real number.
* @return The value of the real number.
*/
[[nodiscard]] auto GetValue() const -> double;

[[nodiscard]] auto GetUnit() const -> Unit;

[[nodiscard]] auto Integrate(const Expression& integrationVariable) const -> std::unique_ptr<Expression> final;

[[nodiscard]] auto Differentiate(const Expression&) const -> std::unique_ptr<Expression> final;
Expand All @@ -38,6 +50,8 @@ class Real : public LeafExpression<Real> {

private:
double value {};
Unit unit = Unit::None;

};

} // Oasis
Expand Down
22 changes: 22 additions & 0 deletions include/Oasis/Unit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Unit.hpp
#ifndef OASIS_UNIT_HPP
#define OASIS_UNIT_HPP

namespace Oasis {

enum class Unit {
None,
Meter,
Kilometer,
Gram,
Kilogram,
Second,
Ampere,
Kelvin,
Mole,
Candela
};

} // Oasis

#endif // OASIS_UNIT_HPP
44 changes: 44 additions & 0 deletions include/Oasis/UnitGraph.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Created by Jary Chen on 3/4/25.
//

#ifndef OASIS_UNIT_GRAPH_HPP
#define OASIS_UNIT_GRAPH_HPP

#include "Unit.hpp"
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <optional>
#include <unordered_map>
#include <boost/graph/graph_traits.hpp>
#include <queue>

namespace Oasis {

struct EdgeProperties {
double factor;
};

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
boost::no_property, EdgeProperties>;

class UnitGraph {
public:
UnitGraph();

void addConversion(Unit from, Unit to, double factor);

std::optional<double> findConversionFactor(Unit from, Unit to) const;

private:
Graph graph_;
std::unordered_map<Unit, int> unitToVertex_;
int nextVertex_;

int getOrAddVertex(Unit unit);
};

} // namespace Oasis

#endif // OASIS_UNIT_GRAPH_HPP

6 changes: 5 additions & 1 deletion include/Oasis/Variable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <string>

#include "LeafExpression.hpp"
#include "Unit.hpp"

namespace Oasis {

Expand All @@ -23,7 +24,7 @@ class Variable : public LeafExpression<Variable> {
Variable() = default;
Variable(const Variable& other) = default;

explicit Variable(std::string name);
explicit Variable(std::string name, Unit unit = Unit::None);

[[nodiscard]] virtual auto Equals(const Expression& other) const -> bool final;

Expand All @@ -37,6 +38,8 @@ class Variable : public LeafExpression<Variable> {
*/
[[nodiscard]] auto GetName() const -> std::string;

[[nodiscard]] auto GetUnit() const -> Unit;

[[nodiscard]] auto Differentiate(const Expression& differentiationVariable) const -> std::unique_ptr<Expression> final;

[[nodiscard]] auto Integrate(const Expression& integrationVariable) const -> std::unique_ptr<Expression> final;
Expand All @@ -47,6 +50,7 @@ class Variable : public LeafExpression<Variable> {

private:
std::string name {};
Unit unit = Unit::None;
};

} // Oasis
Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ set(Oasis_SOURCES
Subtract.cpp
# Summation.cpp
Undefined.cpp
Variable.cpp)
Variable.cpp
UnitGraph.cpp
GlobalUnitGraph.cpp)

# Adds a library target called "Oasis" to be built from source files.
add_library(Oasis ${Oasis_SOURCES})
Expand Down
21 changes: 21 additions & 0 deletions src/GlobalUnitGraph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Created by Jary Chen on 3/10/25.
//

#include "../include/Oasis/GlobalUnitGraph.hpp"

namespace Oasis {

UnitGraph& getGlobalUnitGraph() {
static UnitGraph globalGraph;
static bool initialized = false;
if (!initialized) {
initialized = true;

// Mapping Here
globalGraph.addConversion(Unit::Meter, Unit::Kilometer, 0.001);
}
return globalGraph;
}

} // namespace Oasis
24 changes: 22 additions & 2 deletions src/Real.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

namespace Oasis {

Real::Real(double value)
: value(value)
Real::Real(double value, Unit unit)
: value(value), unit(unit)
{
}

Expand All @@ -28,11 +28,31 @@ auto Real::Equals(const Expression& other) const -> bool
return other.Is<Real>() && value == dynamic_cast<const Real&>(other).value;
}

auto Real::ConvertTo(Unit targetUnit) const -> Real
{
if (unit == targetUnit) {
return *this;
}

const auto& globalGraph = getGlobalUnitGraph();
auto factorOpt = globalGraph.findConversionFactor(unit, targetUnit);
if (!factorOpt.has_value()) {
throw std::runtime_error("Real::ConvertTo: Conversion to unit not found");
}

return Real(value * factorOpt.value(), targetUnit);
}

auto Real::GetValue() const -> double
{
return value;
}

auto Real::GetUnit() const -> Unit
{
return unit;
}

auto Real::Integrate(const Expression& integrationVariable) const -> std::unique_ptr<Expression>
{
if (auto variable = RecursiveCast<Variable>(integrationVariable); variable != nullptr) {
Expand Down
73 changes: 73 additions & 0 deletions src/UnitGraph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// Created by Jary Chen on 3/4/25.
//

#include "../include/Oasis/UnitGraph.hpp"

namespace Oasis {

UnitGraph::UnitGraph() : nextVertex_(0) {}

int UnitGraph::getOrAddVertex(Unit unit) {
auto it = unitToVertex_.find(unit);
if (it != unitToVertex_.end())
return it->second;
int vertex = nextVertex_;
unitToVertex_[unit] = vertex;
++nextVertex_;

// Check if the graph has enough vertices
if (boost::num_vertices(graph_) <= vertex)
boost::add_vertex(graph_);
return vertex;
}

void UnitGraph::addConversion(Unit from, Unit to, double factor) {
//Build Adj Edges
int vFrom = getOrAddVertex(from);
int vTo = getOrAddVertex(to);
boost::add_edge(vFrom, vTo, EdgeProperties{factor}, graph_);
boost::add_edge(vTo, vFrom, EdgeProperties{1.0 / factor}, graph_);
}

std::optional<double> UnitGraph::findConversionFactor(Unit from, Unit to) const {
if (from == to)
return 1.0;
auto itFrom = unitToVertex_.find(from);
auto itTo = unitToVertex_.find(to);
if (itFrom == unitToVertex_.end() || itTo == unitToVertex_.end())
return std::nullopt;

int start = itFrom->second;
int target = itTo->second;

//Standard BFS
std::queue<int> queue;
std::unordered_map<int, double> cumulative;
cumulative[start] = 1.0;
queue.push(start);

while (!queue.empty()) {
int current = queue.front();
queue.pop();

if (current == target)
return cumulative[current];

auto edges = boost::out_edges(current, graph_);
for (auto edgeIter = edges.first; edgeIter != edges.second; ++edgeIter) {
int neighbor = boost::target(*edgeIter, graph_);
double factor = graph_[*edgeIter].factor;
double newFactor = cumulative[current] * factor;

//cycle detection
if (cumulative.find(neighbor) == cumulative.end()) {
cumulative[neighbor] = newFactor;
queue.push(neighbor);
}
}
}
return std::nullopt;
}

} // namespace Oasis
9 changes: 7 additions & 2 deletions src/Variable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

namespace Oasis {

Variable::Variable(std::string name)
: name(std::move(name))
Variable::Variable(std::string name, Unit unit)
: name(std::move(name)), unit(unit)
{
}

Expand All @@ -28,6 +28,11 @@ auto Variable::GetName() const -> std::string
return name;
}

auto Variable::GetUnit() const -> Unit
{
return unit;
}

auto Variable::Integrate(const Expression& integrationVariable) const -> std::unique_ptr<Expression>
{
if (auto variable = RecursiveCast<Variable>(integrationVariable); variable != nullptr) {
Expand Down
25 changes: 25 additions & 0 deletions tests/AddTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,33 @@
#include "Oasis/RecursiveCast.hpp"
#include "Oasis/Variable.hpp"

#include <catch2/catch_approx.hpp>
#include <functional>

TEST_CASE("Conversion: Meter to Kilometer", "[Real][UnitConversion]") {
Oasis::Real meterValue{1000.0, Oasis::Unit::Meter};
auto kmValue = meterValue.ConvertTo(Oasis::Unit::Kilometer);

REQUIRE(kmValue.GetValue() == Catch::Approx(1.0));
REQUIRE(kmValue.GetUnit() == Oasis::Unit::Kilometer);
}

TEST_CASE("Conversion: Kilometer to Meter", "[Real][UnitConversion]") {
Oasis::Real kmValue{2.0, Oasis::Unit::Kilometer};
auto meterValue = kmValue.ConvertTo(Oasis::Unit::Meter);

REQUIRE(meterValue.GetValue() == Catch::Approx(2000.0));
REQUIRE(meterValue.GetUnit() == Oasis::Unit::Meter);
}

TEST_CASE("Conversion: No Conversion Needed", "[Real][UnitConversion]") {
Oasis::Real value{123.45, Oasis::Unit::Meter};
auto converted = value.ConvertTo(Oasis::Unit::Meter);

REQUIRE(converted.GetValue() == Catch::Approx(123.45));
REQUIRE(converted.GetUnit() == Oasis::Unit::Meter);
}

TEST_CASE("Addition", "[Add]")
{
Oasis::Add add {
Expand Down