diff --git a/README.md b/README.md
index 49c2a58..134207f 100644
--- a/README.md
+++ b/README.md
@@ -99,10 +99,11 @@ $temperature->getFahrenheit(); // 76
| Gravity | Plato
SpecificGravity
Brix |
| Pressure | Psi
Bar |
| Temperature | Celsius
Fahrenheit |
-| Volumne | Ounce
Gallon
Barrel
Milliliter
Liter
Hectoliter |
+| Volume | Ounce
Gallon
Barrel
Milliliter
Liter
Hectoliter |
| Weight | Ounce
Pound
Gram
Kilogram |
| Color | Srm
Ebc
Lovibond |
| Time | Millisecond
Second
Minute
Hour
Day
Week
Month
Year |
+| Distillate | Proof
Alcohol Percent |
### Preferences
@@ -120,6 +121,7 @@ preference set, but can be overridden when instantiating a new unit.
'Weight' => 'Pound',
'Color' => 'Srm',
'Time' => 'Minute',
+ 'Distillate' => 'Proof',
];
```
@@ -380,3 +382,67 @@ Beer::gravityCorrection(Gravity $gravity, Temperature $temperature, Temperature
##### Returns
- `Gravity` - Corrected Gravity of Sample
+
+### Spirit
+
+This class will calculate Spirit related calculations.
+___
+
+#### Dilute Down To Desired Proof
+
+Dilute Down To Desired Proof is a calculation to determine how much water to add to a spirit to get to a desired proof.
+
+```php
+Spirit::diluteDownToDesiredProof(Proof $currentProof, Proof $desiredProof, Volume $currentVolume): Volume
+```
+
+##### Arguments
+
+- `Proof $currentProof` - Current Proof of the spirit
+- `Proof $desiredProof` - Desired Proof of the spirit
+- `Volume $currentVolume` - Current Volume of the spirit
+
+##### Returns
+
+- `Volume` - Volume of water to add to the spirit
+
+---
+
+#### Distilled Alcohol Volume
+
+Distilled Alcohol Volume is a calculation to determine the volume of alcohol distilled depending on the wash abv and
+still efficiency.
+
+```php
+Spirit::distilledAlcoholVolume(Volume $volume, Distillate $wash, float $stillEfficiencyPercent): Volume
+```
+
+##### Arguments
+
+- `Volume $volume` - Volume of the wash
+- `Distillate $wash` - Distillate of the wash
+- `float $stillEfficiencyPercent` - Still efficiency percentage
+
+##### Returns
+
+- `Volume` - Volume of the distilled alcohol
+
+---
+
+#### Distilled Remaining Water Volume
+
+Distilled Remaining Water Volume is a calculation to determine the volume of water remaining after distilling a spirit.
+
+```php
+Spirit::distilledRemainingWaterVolume(Volume $volume, Distillate $wash, float $stillEfficiencyPercent): Volume
+```
+
+##### Arguments
+
+- `Volume $volume` - Volume of the wash
+- `Distillate $wash` - Distillate of the wash
+- `float $stillEfficiencyPercent` - Still efficiency percentage
+
+##### Returns
+
+- `Volume` - Volume of the remaining water
\ No newline at end of file
diff --git a/src/BaseUnitz.php b/src/BaseUnitz.php
index 839433e..5508aef 100644
--- a/src/BaseUnitz.php
+++ b/src/BaseUnitz.php
@@ -12,6 +12,7 @@ class BaseUnitz
'Weight' => 'Pound',
'Color' => 'Srm',
'Time' => 'Minute',
+ 'Distillate' => 'Proof'
];
private array $preferences;
diff --git a/src/Calculate/Spirit.php b/src/Calculate/Spirit.php
new file mode 100644
index 0000000..ddfe8b0
--- /dev/null
+++ b/src/Calculate/Spirit.php
@@ -0,0 +1,79 @@
+getPercentAlcohol() < $desired->getPercentAlcohol()) {
+ throw new InvalidArgumentException('Current distillate cannot be less than desired distillate.');
+ }
+
+ return new Volume(
+ liter: $distillateVolume->getLiter() * (($current->getPercentAlcohol() / $desired->getPercentAlcohol()) - 1)
+ );
+ }
+
+ /**
+ * Determines the Volume of distillate you will get with a specific wash abv and still efficiency.
+ *
+ * Source - https://www.hillbillystills.com/distilling-calculator
+ *
+ * @param \Unitz\Volume $volume
+ * @param \Unitz\Distillate $wash
+ * @param float $stillEfficiencyPercent
+ * @return \Unitz\Volume
+ * @throws \InvalidArgumentException
+ */
+ public static function distilledAlcoholVolume(
+ Volume $volume,
+ Distillate $wash,
+ float $stillEfficiencyPercent
+ ): Volume {
+ if ($stillEfficiencyPercent === 0.0) {
+ throw new InvalidArgumentException('Still Efficiency cannot be zero.');
+ }
+
+ return new Volume(
+ liter: ((0.95 * $volume->getLiter() * $wash->getPercentAlcohol() / $stillEfficiencyPercent) * 100) / 100
+ );
+ }
+
+ /**
+ * @param \Unitz\Volume $volume
+ * @param \Unitz\Distillate $wash
+ * @param float $stillEfficiencyPercent
+ * @return \Unitz\Volume
+ * @throws \InvalidArgumentException
+ */
+ public static function distilledRemainingWaterVolume(
+ Volume $volume,
+ Distillate $wash,
+ float $stillEfficiencyPercent
+ ): Volume {
+ return new Volume(
+ liter: $volume->getLiter() - self::distilledAlcoholVolume(
+ $volume,
+ $wash,
+ $stillEfficiencyPercent
+ )->getLiter()
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/Distillate.php b/src/Distillate.php
new file mode 100644
index 0000000..12f4185
--- /dev/null
+++ b/src/Distillate.php
@@ -0,0 +1,106 @@
+hasOnlyOneValue([$proof, $percentAlcohol, $userValue])) {
+ throw new InvalidArgumentException('Only one Distillate type can be set at a time.');
+ }
+
+ parent::__construct($preferences);
+
+ if (is_numeric($proof)) {
+ $this->setProof($proof);
+ }
+
+ if (is_numeric($percentAlcohol)) {
+ $this->setPercentAlcohol($percentAlcohol);
+ }
+
+ if (is_numeric($userValue)) {
+ $this->setValue($userValue);
+ }
+ }
+
+ /**
+ * @param float $proof
+ * @return $this
+ * @throws \InvalidArgumentException
+ */
+ public function setProof(float $proof): self
+ {
+ if ($proof > 200) {
+ throw new InvalidArgumentException('Proof cannot be greater than 200');
+ }
+
+ $this->proof = $proof;
+ $this->percentAlcohol = self::convertProofToPercentAlcohol($proof);
+
+ return $this;
+ }
+
+ /**
+ * @param ?int $round
+ * @return float
+ */
+ public function getProof(?int $round = null): float
+ {
+ return $round ? round($this->proof, $round) : $this->proof;
+ }
+
+ /**
+ * @param float $percentAlcohol
+ * @return $this
+ * @throws \InvalidArgumentException
+ */
+ public function setPercentAlcohol(float $percentAlcohol): self
+ {
+ if ($percentAlcohol > 100) {
+ throw new InvalidArgumentException('Percent alcohol cannot be greater than 100');
+ }
+
+ $this->percentAlcohol = $percentAlcohol;
+ $this->proof = self::convertPercentAlcoholToProof($percentAlcohol);
+
+ return $this;
+ }
+
+ /**
+ * @param ?int $round
+ * @return float
+ */
+ public function getPercentAlcohol(?int $round = null): float
+ {
+ return $round ? round($this->percentAlcohol, $round) : $this->percentAlcohol;
+ }
+
+ /**
+ * @param float $proof
+ * @return float
+ */
+ public static function convertProofToPercentAlcohol(float $proof): float
+ {
+ return $proof / 2;
+ }
+
+ /**
+ * @param float $percentAlcohol
+ * @return float
+ */
+ public static function convertPercentAlcoholToProof(float $percentAlcohol): float
+ {
+ return $percentAlcohol * 2;
+ }
+}
\ No newline at end of file
diff --git a/src/UnitzService.php b/src/UnitzService.php
index c5c4794..28fd6c5 100644
--- a/src/UnitzService.php
+++ b/src/UnitzService.php
@@ -146,4 +146,18 @@ public function makeTime(
$this->getPreferences()
);
}
+
+ /**
+ * @param float|null $proof
+ * @param float|null $percentAlcohol
+ * @param float|null $userValue
+ * @return \Unitz\Distillate
+ */
+ public function makeDistillate(
+ ?float $proof = null,
+ ?float $percentAlcohol = null,
+ ?float $userValue = null
+ ): Distillate {
+ return new Distillate($proof, $percentAlcohol, $userValue, $this->getPreferences());
+ }
}
\ No newline at end of file
diff --git a/tests/Calculate/SpiritTest.php b/tests/Calculate/SpiritTest.php
new file mode 100644
index 0000000..4919f07
--- /dev/null
+++ b/tests/Calculate/SpiritTest.php
@@ -0,0 +1,81 @@
+assertEquals($expected, $actual);
+ }
+
+ public function testDiluteDownToDesiredProofThrowsInvalidArgumentExceptionWithReverseDistillateValues(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Current distillate cannot be less than desired distillate.');
+
+ $distillateVolume = new Volume(liter: 2);
+ $current = new Distillate(percentAlcohol: 35);
+ $desired = new Distillate(percentAlcohol: 40);
+
+ Spirit::diluteDownToDesiredProof($current, $desired, $distillateVolume);
+ }
+
+ public function testDistilledAlcoholVolumeCalculatesCorrectly(): void
+ {
+ $volume = new Volume(liter: 20);
+ $wash = new Distillate(percentAlcohol: 9);
+ $stillEfficiencyPercent = 92;
+ $expected = new Volume(liter: 1.858695652173913);
+
+ $actual = Spirit::distilledAlcoholVolume($volume, $wash, $stillEfficiencyPercent);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testDistilledAlcoholVolumeThrowsInvalidArgumentExceptionWithZeroStillEfficiency(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Still Efficiency cannot be zero.');
+
+ $volume = new Volume(liter: 20);
+ $wash = new Distillate(percentAlcohol: 9);
+ $stillEfficiencyPercent = 0;
+
+ Spirit::distilledAlcoholVolume($volume, $wash, $stillEfficiencyPercent);
+ }
+
+ public function testDistilledRemainingWaterVolumeCalculatesCorrectly(): void
+ {
+ $volume = new Volume(liter: 20);
+ $wash = new Distillate(percentAlcohol: 9);
+ $stillEfficiencyPercent = 92;
+ $expected = new Volume(liter: 18.141304347826086);
+
+ $actual = Spirit::distilledRemainingWaterVolume($volume, $wash, $stillEfficiencyPercent);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testDistilledRemainingWaterVolumeThrowsInvalidArgumentExceptionWithZeroStillEfficiency(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Still Efficiency cannot be zero.');
+
+ $volume = new Volume(liter: 20);
+ $wash = new Distillate(percentAlcohol: 9);
+ $stillEfficiencyPercent = 0;
+
+ Spirit::distilledRemainingWaterVolume($volume, $wash, $stillEfficiencyPercent);
+ }
+}
\ No newline at end of file
diff --git a/tests/DistillateTest.php b/tests/DistillateTest.php
new file mode 100644
index 0000000..bacf3e1
--- /dev/null
+++ b/tests/DistillateTest.php
@@ -0,0 +1,92 @@
+getValue();
+ $expected = self::TEST_PROOF;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetProofWillReturnProofWithGetProof(): void
+ {
+ $distillate = new Distillate(proof: self::TEST_PROOF);
+ $actual = $distillate->getProof();
+ $expected = self::TEST_PROOF;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetProofWillReturnPercentAlcoholWithGetPercentAlcohol(): void
+ {
+ $distillate = new Distillate(proof: self::TEST_PROOF);
+ $actual = $distillate->getPercentAlcohol();
+ $expected = self::TEST_PERCENT_ALCOHOL;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetProofWillThrowInvalidArgumentExceptionWithOutOfRangeProof(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Proof cannot be greater than 200');
+
+ new Distillate(proof: 201);
+ }
+
+ public function testSetPercentAlcoholWillReturnProofWithGetValueAndDefaultPreferences(): void
+ {
+ $distillate = new Distillate(percentAlcohol: self::TEST_PERCENT_ALCOHOL);
+ $actual = $distillate->getValue();
+ $expected = self::TEST_PROOF;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetPercentAlcoholWillReturnProofWithGetProof(): void
+ {
+ $distillate = new Distillate(percentAlcohol: self::TEST_PERCENT_ALCOHOL);
+ $actual = $distillate->getProof();
+ $expected = self::TEST_PROOF;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetPercentAlcoholWillReturnPercentAlcoholWithGetPercentAlcohol(): void
+ {
+ $distillate = new Distillate(percentAlcohol: self::TEST_PERCENT_ALCOHOL);
+ $actual = $distillate->getPercentAlcohol();
+ $expected = self::TEST_PERCENT_ALCOHOL;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetUserValueWillReturnProofWithGetValueAndDefaultPreferences(): void
+ {
+ $distillate = new Distillate(userValue: self::TEST_PROOF);
+ $actual = $distillate->getValue();
+ $expected = self::TEST_PROOF;
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testSetPercentAlcoholWillThrowInvalidArgumentExceptionWithOutOfRangeProof(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Percent alcohol cannot be greater than 100');
+
+ new Distillate(percentAlcohol: 101);
+ }
+}
\ No newline at end of file
diff --git a/tests/UnitzServiceTest.php b/tests/UnitzServiceTest.php
index 18615a9..4e7146c 100644
--- a/tests/UnitzServiceTest.php
+++ b/tests/UnitzServiceTest.php
@@ -218,4 +218,35 @@ public function testMakeTimeWillSetTimeWithPreferenceAndNewSetValueAndReturnTheS
$actual = $time->getHour();
$this->assertEquals($expected, $actual);
}
+
+ public function testMakeProofWillReturnProofWithPreference(): void
+ {
+ $proof = 60;
+ $distillate = $this->makeUnitService(['Distillate' => 'Proof'])->makeDistillate(
+ proof: $proof
+ );
+ $expected = $proof;
+ $actual = $distillate->getValue();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testMakeProofWillSetProofWithPreferenceAndReturnTheSame(): void
+ {
+ $proof = 60;
+ $distillate = $this->makeUnitService(['Distillate' => 'Proof'])->makeDistillate(userValue: $proof);
+ $expected = $proof;
+ $actual = $distillate->getValue();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testMakeProofWillSetProofWithPreferenceAndNewSetValueAndReturnTheSame(): void
+ {
+ $proof = 60;
+ $newProof = 70;
+ $distillate = $this->makeUnitService(['Distillate' => 'Proof'])->makeDistillate(userValue: $proof);
+ $distillate->setValue($newProof);
+ $expected = $newProof;
+ $actual = $distillate->getProof();
+ $this->assertEquals($expected, $actual);
+ }
}
\ No newline at end of file